├── .env.template ├── plotpy ├── tests │ ├── features │ │ ├── __init__.py │ │ ├── test_resize.py │ │ ├── test_no_auto_tools.py │ │ ├── test_fit.py │ │ ├── test_plot_yreverse.py │ │ ├── test_auto_curve_image.py │ │ ├── test_plot_log.py │ │ ├── test_cursors.py │ │ ├── test_loadsaveitems_hdf5.py │ │ ├── test_dicom_image.py │ │ ├── test_plot_types.py │ │ ├── test_computations.py │ │ ├── test_loadsaveitems_json.py │ │ ├── test_image_superp.py │ │ ├── test_fit_locked_params.py │ │ ├── test_builder.py │ │ ├── test_image_filter.py │ │ └── test_image_coords.py │ ├── items │ │ ├── __init__.py │ │ ├── test_image.py │ │ ├── test_histogram.py │ │ ├── test_image_rgb.py │ │ ├── test_image_xy.py │ │ ├── test_image_contour.py │ │ ├── test_svgshapes.py │ │ ├── test_image_aspect_ratio.py │ │ ├── test_annotated_range.py │ │ ├── test_annotations.py │ │ ├── test_image_masked.py │ │ ├── test_image_masked_xy.py │ │ ├── test_curves_highdpi.py │ │ ├── test_curves.py │ │ └── test_to_bins.py │ ├── tools │ │ ├── __init__.py │ │ ├── test_cyclic_import.py │ │ ├── test_cross_section_oblique.py │ │ ├── test_get_rectangle.py │ │ ├── test_cross_section.py │ │ ├── test_actiontool.py │ │ ├── test_zaxislog.py │ │ ├── test_downsample_curve.py │ │ ├── test_get_rectangle_with_svg.py │ │ ├── test_get_point.py │ │ ├── test_customize_shape_tool.py │ │ └── test_get_segment.py │ ├── unit │ │ ├── __init__.py │ │ ├── test_fontparam.py │ │ ├── test_line_cross_section_tool.py │ │ ├── test_seg_dist.py │ │ ├── test_oblique_cross_section_tool.py │ │ ├── test_tools_export.py │ │ ├── test_aspect_ratio_tool.py │ │ ├── test_cursor_tools.py │ │ ├── test_line.py │ │ ├── test_builder_shape.py │ │ └── test_display_coords_tool.py │ ├── widgets │ │ ├── __init__.py │ │ ├── test_qtdesigner.ui │ │ ├── test_qtdesigner.py │ │ └── test_fliprotate.py │ ├── data │ │ ├── brain.png │ │ ├── mr-brain.dcm │ │ ├── brain_cylinder.png │ │ ├── svg_tool.svg │ │ └── svg_target.svg │ ├── benchmarks │ │ ├── __init__.py │ │ └── test_bigimages.py │ ├── __init__.py │ └── vistools.py ├── mathutils │ ├── __init__.py │ ├── arrayfuncs.py │ └── scaler.py ├── widgets │ ├── __init__.py │ ├── colormap │ │ └── __init__.py │ └── about.py ├── data │ └── icons │ │ ├── apply.png │ │ ├── axes.png │ │ ├── busy.png │ │ ├── copy.png │ │ ├── edit.png │ │ ├── exit.png │ │ ├── file.png │ │ ├── font.png │ │ ├── funct.png │ │ ├── hflip.png │ │ ├── max.png │ │ ├── min.png │ │ ├── move.png │ │ ├── none.png │ │ ├── print.png │ │ ├── shape.png │ │ ├── trash.png │ │ ├── vflip.png │ │ ├── xmax.png │ │ ├── xmin.png │ │ ├── arredit.png │ │ ├── center.png │ │ ├── delete.png │ │ ├── eraser.png │ │ ├── export.png │ │ ├── filenew.png │ │ ├── hcursor.png │ │ ├── python.png │ │ ├── refresh.png │ │ ├── vcursor.png │ │ ├── xcursor.png │ │ ├── arrow_down.png │ │ ├── arrow_up.png │ │ ├── autoscale.png │ │ ├── cell_edit.png │ │ ├── cmap_edit.png │ │ ├── contrast.png │ │ ├── csapplylut.png │ │ ├── csection.png │ │ ├── csection_a.png │ │ ├── csperimage.png │ │ ├── fileclose.png │ │ ├── fileimport.png │ │ ├── fileopen.png │ │ ├── filesave.png │ │ ├── filesaveas.png │ │ ├── full_range.png │ │ ├── imagestats.png │ │ ├── item_list.png │ │ ├── items │ │ ├── grid.png │ │ ├── curve.png │ │ ├── image.png │ │ ├── label.png │ │ ├── legend.png │ │ ├── errorbar.png │ │ ├── histogram.png │ │ ├── annotation.png │ │ ├── histogram2d.png │ │ └── polygonmap.png │ │ ├── magnifier.png │ │ ├── not_found.png │ │ ├── on_curve.png │ │ ├── quickview.png │ │ ├── save_all.png │ │ ├── settings.png │ │ ├── snapshot.png │ │ ├── styles │ │ ├── dot.png │ │ ├── dash.png │ │ ├── solid.png │ │ ├── dashdot.png │ │ └── dashdotdot.png │ │ ├── autorefresh.png │ │ ├── csautoscale.png │ │ ├── editors │ │ ├── edit.png │ │ ├── plot.png │ │ ├── imshow.png │ │ ├── insert.png │ │ ├── rename.png │ │ ├── edit_add.png │ │ ├── editcopy.png │ │ ├── editpaste.png │ │ ├── filesave.png │ │ ├── editdelete.png │ │ └── fileimport.png │ │ ├── filetypes │ │ ├── ps.png │ │ ├── doc.png │ │ ├── gif.png │ │ ├── html.png │ │ ├── jpg.png │ │ ├── pdf.png │ │ ├── png.png │ │ ├── pps.png │ │ ├── tar.png │ │ ├── tgz.png │ │ ├── tif.png │ │ ├── txt.png │ │ ├── xls.png │ │ └── zip.png │ │ ├── markers │ │ ├── star.png │ │ ├── cross.png │ │ ├── diamond.png │ │ ├── ellipse.png │ │ ├── hexagon.png │ │ ├── point.png │ │ ├── square.png │ │ ├── xcross.png │ │ ├── triangle_d.png │ │ ├── triangle_l.png │ │ ├── triangle_r.png │ │ └── triangle_u.png │ │ ├── trimage_lock.png │ │ ├── copytoclipboard.png │ │ ├── csection_line.png │ │ ├── curvetypes │ │ ├── xfy.png │ │ └── yfx.png │ │ ├── expander_down.png │ │ ├── expander_right.png │ │ ├── mask │ │ ├── mask_tool.png │ │ ├── mask_circle.png │ │ ├── mask_rectangle.png │ │ ├── mask_circle_outside.png │ │ └── mask_rectangle_outside.png │ │ ├── point_selection.png │ │ ├── rotationcenter.jpg │ │ ├── scales │ │ ├── lin_lin.png │ │ ├── lin_log.png │ │ ├── log_lin.png │ │ ├── log_log.png │ │ └── source.pdn │ │ ├── shapes │ │ ├── circle.png │ │ ├── contour.png │ │ ├── gtaxes.png │ │ ├── marker.png │ │ ├── polygon.png │ │ ├── polyline.png │ │ ├── segment.png │ │ ├── xrange.png │ │ ├── yrange.png │ │ ├── rectangle.png │ │ ├── ellipse_shape.png │ │ ├── point_shape.png │ │ └── oblique_rectangle.png │ │ ├── trimage_unlock.png │ │ ├── csection_oblique.png │ │ ├── curve_downsample.png │ │ ├── curvestyles │ │ ├── dots.png │ │ ├── lines.png │ │ ├── steps.png │ │ └── sticks.png │ │ ├── patterns │ │ ├── nobrush.png │ │ ├── horpattern.png │ │ ├── verpattern.png │ │ ├── bdiagpattern.png │ │ ├── crosspattern.png │ │ ├── dense1pattern.png │ │ ├── dense2pattern.png │ │ ├── dense3pattern.png │ │ ├── dense4pattern.png │ │ ├── dense5pattern.png │ │ ├── dense6pattern.png │ │ ├── dense7pattern.png │ │ ├── fdiagpattern.png │ │ ├── solidpattern.png │ │ └── diagcrosspattern.png │ │ ├── edit_point_selection.png │ │ ├── eliminate_outliers.png │ │ ├── multipoint_selection.png │ │ └── markerstyles │ │ ├── cross_marker.png │ │ ├── horiz_marker.png │ │ └── vert_marker.png ├── items │ ├── curve │ │ └── __init__.py │ ├── shape │ │ └── __init__.py │ ├── image │ │ └── __init__.py │ └── __init__.py ├── panels │ ├── __init__.py │ └── csection │ │ └── __init__.py ├── plot │ └── __init__.py ├── external │ ├── __init__.py │ └── sliders │ │ ├── _misc.py │ │ └── _sliders.py ├── interfaces │ ├── panel.py │ ├── __init__.py │ └── plotmanager.py ├── __init__.py ├── builder │ └── __init__.py └── styles │ ├── polygonmap.py │ ├── __init__.py │ └── errorbar.py ├── doc ├── features │ ├── io.rst │ ├── events.rst │ ├── fit.rst │ ├── fliprotate.rst │ ├── imagefile.rst │ ├── mathutils │ │ ├── scaler.rst │ │ ├── colormaps.rst │ │ ├── geometry.rst │ │ └── index.rst │ ├── rotatecrop.rst │ ├── colormapmanager.rst │ ├── resizedialog.rst │ ├── selectdialog.rst │ ├── pyplot.rst │ ├── panels │ │ ├── index.rst │ │ ├── reference.rst │ │ └── overview.rst │ ├── tools │ │ ├── index.rst │ │ ├── examples.rst │ │ └── overview.rst │ ├── items │ │ ├── index.rst │ │ └── builder.rst │ ├── styles │ │ ├── index.rst │ │ ├── overview.rst │ │ └── reference.rst │ ├── index.rst │ ├── plot │ │ ├── reference.rst │ │ ├── examples.rst │ │ ├── overview.rst │ │ └── index.rst │ └── signals.rst ├── _static │ └── favicon.ico ├── images │ ├── panorama.png │ ├── plot_widgets.png │ ├── plotpy-banner.png │ ├── my_plot_manager.png │ ├── panorama-vertical.png │ ├── plotpy-vertical.png │ └── screenshots │ │ ├── fit.png │ │ ├── plot.png │ │ ├── hist2d.png │ │ ├── imagexy.png │ │ ├── manager.png │ │ ├── pcolor.png │ │ ├── __init__.png │ │ ├── contrast.png │ │ ├── get_point.png │ │ ├── mandelbrot.png │ │ ├── transform.png │ │ ├── computations.png │ │ ├── dotarraydemo.png │ │ ├── filtertest1.png │ │ ├── filtertest2.png │ │ ├── imagefilter.png │ │ ├── imagesuperp.png │ │ ├── cross_section.png │ │ ├── cross_section2.png │ │ ├── simple_dialog.png │ │ ├── simple_window.png │ │ └── image_plot_tools.png ├── dev │ ├── index.rst │ ├── v1_to_guidata_v3.csv │ └── build.rst ├── intro │ ├── index.rst │ └── installation.rst ├── release_notes │ ├── index.rst │ └── release_2.02.md └── index.rst ├── babel.cfg ├── MANIFEST.in ├── colormaps └── README.md ├── .pre-commit-config.yaml ├── media ├── X │ └── 2023_10_31-V2.txt └── LinkedIn │ └── 2023_10_31-V2.md ├── plotpy-tests.desktop ├── requirements.txt ├── .coveragerc ├── qtdesigner └── plotplugin.py ├── .readthedocs.yaml ├── .pylintrc ├── .github └── workflows │ ├── build.yml │ └── build_deploy.yml ├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── src ├── debug.hpp └── mandelbrot.pyx ├── scripts ├── build_inplace.bat ├── build_wheels.bat ├── build-wheels.sh └── run_with_env.py └── LICENSE /.env.template: -------------------------------------------------------------------------------- 1 | PYTHONPATH=. -------------------------------------------------------------------------------- /plotpy/tests/features/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plotpy/tests/items/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plotpy/tests/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plotpy/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plotpy/tests/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/features/io.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.io -------------------------------------------------------------------------------- /doc/features/events.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.events 2 | -------------------------------------------------------------------------------- /plotpy/mathutils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /plotpy/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /doc/features/fit.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.fit 2 | -------------------------------------------------------------------------------- /doc/features/fliprotate.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.fliprotate 2 | -------------------------------------------------------------------------------- /doc/features/imagefile.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.imagefile 2 | -------------------------------------------------------------------------------- /doc/features/mathutils/scaler.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.mathutils.scaler -------------------------------------------------------------------------------- /doc/features/rotatecrop.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.rotatecrop 2 | -------------------------------------------------------------------------------- /doc/features/colormapmanager.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.colormap 2 | -------------------------------------------------------------------------------- /doc/features/mathutils/colormaps.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.mathutils.colormap -------------------------------------------------------------------------------- /doc/features/mathutils/geometry.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.mathutils.geometry -------------------------------------------------------------------------------- /doc/features/resizedialog.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.resizedialog 2 | -------------------------------------------------------------------------------- /doc/features/selectdialog.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: plotpy.widgets.selectdialog 2 | -------------------------------------------------------------------------------- /doc/features/pyplot.rst: -------------------------------------------------------------------------------- 1 | .. module:: pyplot 2 | .. automodule:: plotpy.pyplot 3 | -------------------------------------------------------------------------------- /doc/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/_static/favicon.ico -------------------------------------------------------------------------------- /doc/images/panorama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/panorama.png -------------------------------------------------------------------------------- /doc/images/plot_widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/plot_widgets.png -------------------------------------------------------------------------------- /plotpy/data/icons/apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/apply.png -------------------------------------------------------------------------------- /plotpy/data/icons/axes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/axes.png -------------------------------------------------------------------------------- /plotpy/data/icons/busy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/busy.png -------------------------------------------------------------------------------- /plotpy/data/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/copy.png -------------------------------------------------------------------------------- /plotpy/data/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/edit.png -------------------------------------------------------------------------------- /plotpy/data/icons/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/exit.png -------------------------------------------------------------------------------- /plotpy/data/icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/file.png -------------------------------------------------------------------------------- /plotpy/data/icons/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/font.png -------------------------------------------------------------------------------- /plotpy/data/icons/funct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/funct.png -------------------------------------------------------------------------------- /plotpy/data/icons/hflip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/hflip.png -------------------------------------------------------------------------------- /plotpy/data/icons/max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/max.png -------------------------------------------------------------------------------- /plotpy/data/icons/min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/min.png -------------------------------------------------------------------------------- /plotpy/data/icons/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/move.png -------------------------------------------------------------------------------- /plotpy/data/icons/none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/none.png -------------------------------------------------------------------------------- /plotpy/data/icons/print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/print.png -------------------------------------------------------------------------------- /plotpy/data/icons/shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shape.png -------------------------------------------------------------------------------- /plotpy/data/icons/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/trash.png -------------------------------------------------------------------------------- /plotpy/data/icons/vflip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/vflip.png -------------------------------------------------------------------------------- /plotpy/data/icons/xmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/xmax.png -------------------------------------------------------------------------------- /plotpy/data/icons/xmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/xmin.png -------------------------------------------------------------------------------- /plotpy/tests/data/brain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/tests/data/brain.png -------------------------------------------------------------------------------- /doc/images/plotpy-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/plotpy-banner.png -------------------------------------------------------------------------------- /plotpy/data/icons/arredit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/arredit.png -------------------------------------------------------------------------------- /plotpy/data/icons/center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/center.png -------------------------------------------------------------------------------- /plotpy/data/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/delete.png -------------------------------------------------------------------------------- /plotpy/data/icons/eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/eraser.png -------------------------------------------------------------------------------- /plotpy/data/icons/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/export.png -------------------------------------------------------------------------------- /plotpy/data/icons/filenew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filenew.png -------------------------------------------------------------------------------- /plotpy/data/icons/hcursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/hcursor.png -------------------------------------------------------------------------------- /plotpy/data/icons/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/python.png -------------------------------------------------------------------------------- /plotpy/data/icons/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/refresh.png -------------------------------------------------------------------------------- /plotpy/data/icons/vcursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/vcursor.png -------------------------------------------------------------------------------- /plotpy/data/icons/xcursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/xcursor.png -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | # This file is used to configure Babel for the project. 2 | 3 | [python: **.py] 4 | encoding = utf-8 5 | -------------------------------------------------------------------------------- /doc/images/my_plot_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/my_plot_manager.png -------------------------------------------------------------------------------- /doc/images/panorama-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/panorama-vertical.png -------------------------------------------------------------------------------- /doc/images/plotpy-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/plotpy-vertical.png -------------------------------------------------------------------------------- /doc/images/screenshots/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/fit.png -------------------------------------------------------------------------------- /doc/images/screenshots/plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/plot.png -------------------------------------------------------------------------------- /plotpy/data/icons/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/arrow_down.png -------------------------------------------------------------------------------- /plotpy/data/icons/arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/arrow_up.png -------------------------------------------------------------------------------- /plotpy/data/icons/autoscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/autoscale.png -------------------------------------------------------------------------------- /plotpy/data/icons/cell_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/cell_edit.png -------------------------------------------------------------------------------- /plotpy/data/icons/cmap_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/cmap_edit.png -------------------------------------------------------------------------------- /plotpy/data/icons/contrast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/contrast.png -------------------------------------------------------------------------------- /plotpy/data/icons/csapplylut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csapplylut.png -------------------------------------------------------------------------------- /plotpy/data/icons/csection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csection.png -------------------------------------------------------------------------------- /plotpy/data/icons/csection_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csection_a.png -------------------------------------------------------------------------------- /plotpy/data/icons/csperimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csperimage.png -------------------------------------------------------------------------------- /plotpy/data/icons/fileclose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/fileclose.png -------------------------------------------------------------------------------- /plotpy/data/icons/fileimport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/fileimport.png -------------------------------------------------------------------------------- /plotpy/data/icons/fileopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/fileopen.png -------------------------------------------------------------------------------- /plotpy/data/icons/filesave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filesave.png -------------------------------------------------------------------------------- /plotpy/data/icons/filesaveas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filesaveas.png -------------------------------------------------------------------------------- /plotpy/data/icons/full_range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/full_range.png -------------------------------------------------------------------------------- /plotpy/data/icons/imagestats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/imagestats.png -------------------------------------------------------------------------------- /plotpy/data/icons/item_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/item_list.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/grid.png -------------------------------------------------------------------------------- /plotpy/data/icons/magnifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/magnifier.png -------------------------------------------------------------------------------- /plotpy/data/icons/not_found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/not_found.png -------------------------------------------------------------------------------- /plotpy/data/icons/on_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/on_curve.png -------------------------------------------------------------------------------- /plotpy/data/icons/quickview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/quickview.png -------------------------------------------------------------------------------- /plotpy/data/icons/save_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/save_all.png -------------------------------------------------------------------------------- /plotpy/data/icons/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/settings.png -------------------------------------------------------------------------------- /plotpy/data/icons/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/snapshot.png -------------------------------------------------------------------------------- /plotpy/data/icons/styles/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/styles/dot.png -------------------------------------------------------------------------------- /plotpy/tests/data/mr-brain.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/tests/data/mr-brain.dcm -------------------------------------------------------------------------------- /doc/images/screenshots/hist2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/hist2d.png -------------------------------------------------------------------------------- /doc/images/screenshots/imagexy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/imagexy.png -------------------------------------------------------------------------------- /doc/images/screenshots/manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/manager.png -------------------------------------------------------------------------------- /doc/images/screenshots/pcolor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/pcolor.png -------------------------------------------------------------------------------- /plotpy/data/icons/autorefresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/autorefresh.png -------------------------------------------------------------------------------- /plotpy/data/icons/csautoscale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csautoscale.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/edit.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/plot.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/ps.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/curve.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/image.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/label.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/legend.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/star.png -------------------------------------------------------------------------------- /plotpy/data/icons/styles/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/styles/dash.png -------------------------------------------------------------------------------- /plotpy/data/icons/styles/solid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/styles/solid.png -------------------------------------------------------------------------------- /plotpy/data/icons/trimage_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/trimage_lock.png -------------------------------------------------------------------------------- /doc/images/screenshots/__init__.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/__init__.png -------------------------------------------------------------------------------- /doc/images/screenshots/contrast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/contrast.png -------------------------------------------------------------------------------- /doc/images/screenshots/get_point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/get_point.png -------------------------------------------------------------------------------- /doc/images/screenshots/mandelbrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/mandelbrot.png -------------------------------------------------------------------------------- /doc/images/screenshots/transform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/transform.png -------------------------------------------------------------------------------- /plotpy/data/icons/copytoclipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/copytoclipboard.png -------------------------------------------------------------------------------- /plotpy/data/icons/csection_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csection_line.png -------------------------------------------------------------------------------- /plotpy/data/icons/curvetypes/xfy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curvetypes/xfy.png -------------------------------------------------------------------------------- /plotpy/data/icons/curvetypes/yfx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curvetypes/yfx.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/imshow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/imshow.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/insert.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/rename.png -------------------------------------------------------------------------------- /plotpy/data/icons/expander_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/expander_down.png -------------------------------------------------------------------------------- /plotpy/data/icons/expander_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/expander_right.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/doc.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/gif.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/html.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/jpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/jpg.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/pdf.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/png.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/pps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/pps.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/tar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/tar.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/tgz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/tgz.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/tif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/tif.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/txt.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/xls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/xls.png -------------------------------------------------------------------------------- /plotpy/data/icons/filetypes/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/filetypes/zip.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/errorbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/errorbar.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/histogram.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/cross.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/diamond.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/ellipse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/ellipse.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/hexagon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/hexagon.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/point.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/square.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/xcross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/xcross.png -------------------------------------------------------------------------------- /plotpy/data/icons/mask/mask_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/mask/mask_tool.png -------------------------------------------------------------------------------- /plotpy/data/icons/point_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/point_selection.png -------------------------------------------------------------------------------- /plotpy/data/icons/rotationcenter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/rotationcenter.jpg -------------------------------------------------------------------------------- /plotpy/data/icons/scales/lin_lin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/scales/lin_lin.png -------------------------------------------------------------------------------- /plotpy/data/icons/scales/lin_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/scales/lin_log.png -------------------------------------------------------------------------------- /plotpy/data/icons/scales/log_lin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/scales/log_lin.png -------------------------------------------------------------------------------- /plotpy/data/icons/scales/log_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/scales/log_log.png -------------------------------------------------------------------------------- /plotpy/data/icons/scales/source.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/scales/source.pdn -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/circle.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/contour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/contour.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/gtaxes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/gtaxes.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/marker.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/polygon.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/polyline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/polyline.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/segment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/segment.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/xrange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/xrange.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/yrange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/yrange.png -------------------------------------------------------------------------------- /plotpy/data/icons/styles/dashdot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/styles/dashdot.png -------------------------------------------------------------------------------- /plotpy/data/icons/trimage_unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/trimage_unlock.png -------------------------------------------------------------------------------- /plotpy/tests/data/brain_cylinder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/tests/data/brain_cylinder.png -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft doc 2 | graft qtdesigner 3 | graft src 4 | graft colormaps 5 | include *.desktop 6 | include requirements.txt -------------------------------------------------------------------------------- /doc/images/screenshots/computations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/computations.png -------------------------------------------------------------------------------- /doc/images/screenshots/dotarraydemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/dotarraydemo.png -------------------------------------------------------------------------------- /doc/images/screenshots/filtertest1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/filtertest1.png -------------------------------------------------------------------------------- /doc/images/screenshots/filtertest2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/filtertest2.png -------------------------------------------------------------------------------- /doc/images/screenshots/imagefilter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/imagefilter.png -------------------------------------------------------------------------------- /doc/images/screenshots/imagesuperp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/imagesuperp.png -------------------------------------------------------------------------------- /plotpy/data/icons/csection_oblique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/csection_oblique.png -------------------------------------------------------------------------------- /plotpy/data/icons/curve_downsample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curve_downsample.png -------------------------------------------------------------------------------- /plotpy/data/icons/curvestyles/dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curvestyles/dots.png -------------------------------------------------------------------------------- /plotpy/data/icons/curvestyles/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curvestyles/lines.png -------------------------------------------------------------------------------- /plotpy/data/icons/curvestyles/steps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curvestyles/steps.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/edit_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/edit_add.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/editcopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/editcopy.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/editpaste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/editpaste.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/filesave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/filesave.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/annotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/annotation.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/histogram2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/histogram2d.png -------------------------------------------------------------------------------- /plotpy/data/icons/items/polygonmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/items/polygonmap.png -------------------------------------------------------------------------------- /plotpy/data/icons/mask/mask_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/mask/mask_circle.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/nobrush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/nobrush.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/rectangle.png -------------------------------------------------------------------------------- /plotpy/data/icons/styles/dashdotdot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/styles/dashdotdot.png -------------------------------------------------------------------------------- /doc/images/screenshots/cross_section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/cross_section.png -------------------------------------------------------------------------------- /doc/images/screenshots/cross_section2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/cross_section2.png -------------------------------------------------------------------------------- /doc/images/screenshots/simple_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/simple_dialog.png -------------------------------------------------------------------------------- /doc/images/screenshots/simple_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/simple_window.png -------------------------------------------------------------------------------- /plotpy/data/icons/curvestyles/sticks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/curvestyles/sticks.png -------------------------------------------------------------------------------- /plotpy/data/icons/edit_point_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/edit_point_selection.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/editdelete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/editdelete.png -------------------------------------------------------------------------------- /plotpy/data/icons/editors/fileimport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/editors/fileimport.png -------------------------------------------------------------------------------- /plotpy/data/icons/eliminate_outliers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/eliminate_outliers.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/triangle_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/triangle_d.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/triangle_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/triangle_l.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/triangle_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/triangle_r.png -------------------------------------------------------------------------------- /plotpy/data/icons/markers/triangle_u.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markers/triangle_u.png -------------------------------------------------------------------------------- /plotpy/data/icons/mask/mask_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/mask/mask_rectangle.png -------------------------------------------------------------------------------- /plotpy/data/icons/multipoint_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/multipoint_selection.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/horpattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/horpattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/verpattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/verpattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/ellipse_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/ellipse_shape.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/point_shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/point_shape.png -------------------------------------------------------------------------------- /doc/images/screenshots/image_plot_tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/doc/images/screenshots/image_plot_tools.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/bdiagpattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/bdiagpattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/crosspattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/crosspattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense1pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense1pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense2pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense2pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense3pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense3pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense4pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense4pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense5pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense5pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense6pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense6pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/dense7pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/dense7pattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/fdiagpattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/fdiagpattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/solidpattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/solidpattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/markerstyles/cross_marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markerstyles/cross_marker.png -------------------------------------------------------------------------------- /plotpy/data/icons/markerstyles/horiz_marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markerstyles/horiz_marker.png -------------------------------------------------------------------------------- /plotpy/data/icons/markerstyles/vert_marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/markerstyles/vert_marker.png -------------------------------------------------------------------------------- /plotpy/data/icons/mask/mask_circle_outside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/mask/mask_circle_outside.png -------------------------------------------------------------------------------- /plotpy/data/icons/patterns/diagcrosspattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/patterns/diagcrosspattern.png -------------------------------------------------------------------------------- /plotpy/data/icons/shapes/oblique_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/shapes/oblique_rectangle.png -------------------------------------------------------------------------------- /plotpy/data/icons/mask/mask_rectangle_outside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlotPyStack/PlotPy/HEAD/plotpy/data/icons/mask/mask_rectangle_outside.png -------------------------------------------------------------------------------- /colormaps/README.md: -------------------------------------------------------------------------------- 1 | This directory contains functions and data useful to generate default colormaps used 2 | in Plotpy. Launch `colormap.py` to create/overwrite the colormaps_default.json file. -------------------------------------------------------------------------------- /doc/features/mathutils/index.rst: -------------------------------------------------------------------------------- 1 | Utilities 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | geometry 9 | scaler 10 | colormaps 11 | -------------------------------------------------------------------------------- /doc/features/panels/index.rst: -------------------------------------------------------------------------------- 1 | .. _panels: 2 | 3 | Plot panels 4 | =========== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | overview 11 | reference 12 | -------------------------------------------------------------------------------- /doc/features/tools/index.rst: -------------------------------------------------------------------------------- 1 | .. _tools: 2 | 3 | Plot tools 4 | ========== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | overview 11 | examples 12 | reference 13 | -------------------------------------------------------------------------------- /doc/dev/index.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | contribute 9 | build 10 | guiqwt_to_plotpy 11 | v1_to_v2 12 | platforms 13 | -------------------------------------------------------------------------------- /plotpy/items/curve/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=W0611 4 | # flake8: noqa 5 | 6 | from plotpy.items.curve.base import CurveItem 7 | from plotpy.items.curve.errorbar import ErrorBarCurveItem 8 | -------------------------------------------------------------------------------- /doc/features/items/index.rst: -------------------------------------------------------------------------------- 1 | .. _items: 2 | 3 | Plot items 4 | ========== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | overview 11 | examples 12 | builder 13 | reference 14 | -------------------------------------------------------------------------------- /doc/features/styles/index.rst: -------------------------------------------------------------------------------- 1 | .. _styles: 2 | 3 | Styles for items and tools 4 | ========================== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | overview 11 | reference 12 | -------------------------------------------------------------------------------- /doc/intro/index.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | overview 9 | motivation 10 | installation 11 | examples 12 | licenses 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.12.2 5 | hooks: 6 | # Run the linter. 7 | - id: ruff-check 8 | args: [ --fix ] 9 | # Run the formatter. 10 | - id: ruff-format -------------------------------------------------------------------------------- /doc/features/panels/reference.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | Reference 4 | --------- 5 | 6 | .. automodule:: plotpy.panels.base 7 | 8 | .. automodule:: plotpy.panels.itemlist 9 | 10 | .. automodule:: plotpy.panels.contrastadjustment 11 | 12 | .. automodule:: plotpy.panels.csection 13 | -------------------------------------------------------------------------------- /media/X/2023_10_31-V2.txt: -------------------------------------------------------------------------------- 1 | PlotPy V2 is now live! 🎉Introducing an optimized 2D curve/image plotting library📊. 2 | It's the successor to guiqwt. 3 | PlotPy is part of PlotPyStack, representing 15 years of expertise 4 | in Python-Qt scientific software development. 5 | #Python #Plotting 6 | -------------------------------------------------------------------------------- /doc/release_notes/index.rst: -------------------------------------------------------------------------------- 1 | Release notes 2 | ============= 3 | 4 | This section contains the release notes for all versions of :mod:`plotpy`, documenting 5 | new features, improvements, bug fixes, and breaking changes. 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | :glob: 10 | :reversed: 11 | 12 | release_* 13 | -------------------------------------------------------------------------------- /plotpy-tests.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=PlotPy 5 | GenericName=PlotPy Test launcher 6 | Comment=The PlotPy Python library provides powerful curve and image plotting tools 7 | TryExec=plotpy-tests 8 | Exec=plotpy-tests 9 | Icon=plotpy.svg 10 | Categories=Education;Science;Physics; 11 | -------------------------------------------------------------------------------- /doc/features/tools/examples.rst: -------------------------------------------------------------------------------- 1 | Example 2 | ------- 3 | 4 | The following example add all the existing image tools to a :class:`.PlotWidget` 5 | object for testing purpose: 6 | 7 | .. literalinclude:: ../../../plotpy/tests/tools/test_image_plot_tools.py 8 | :start-after: guitest: 9 | 10 | .. image:: /images/screenshots/image_plot_tools.png 11 | -------------------------------------------------------------------------------- /plotpy/panels/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=unused-import 4 | # flake8: noqa 5 | 6 | from .base import PanelWidget 7 | from .contrastadjustment import ContrastAdjustment 8 | from .csection import ( 9 | ObliqueCrossSection, 10 | XCrossSection, 11 | YCrossSection, 12 | LineCrossSection, 13 | ) 14 | from .itemlist import PlotItemList 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Coverage 2 | Cython>=3.0 3 | Pillow 4 | PyQt5>5.15.5 5 | PythonQwt >= 0.15 6 | SciPy >= 1.7.3 7 | babel 8 | build 9 | guidata >= 3.13 10 | myst_parser 11 | numpy >= 1.22 12 | pre-commit 13 | pylint 14 | pytest 15 | pytest-xvfb 16 | python-docs-theme 17 | ruff 18 | scikit-image >= 0.19 19 | sphinx 20 | sphinx-copybutton 21 | sphinx_qt_documentation 22 | tifffile 23 | -------------------------------------------------------------------------------- /plotpy/plot/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=unused-import 4 | # flake8: noqa 5 | 6 | from .base import BasePlot, BasePlotOptions 7 | from .manager import PlotManager 8 | from .plotwidget import ( 9 | PlotDialog, 10 | PlotOptions, 11 | PlotWidget, 12 | PlotWindow, 13 | SubplotWidget, 14 | SyncPlotWindow, 15 | SyncPlotDialog, 16 | set_widget_title_icon, 17 | ) 18 | -------------------------------------------------------------------------------- /doc/features/index.rst: -------------------------------------------------------------------------------- 1 | Features 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | plot/index 9 | items/index 10 | tools/index 11 | styles/index 12 | panels/index 13 | colormapmanager 14 | fit 15 | pyplot 16 | resizedialog 17 | rotatecrop 18 | fliprotate 19 | selectdialog 20 | imagefile 21 | io 22 | signals 23 | mathutils/index 24 | events 25 | -------------------------------------------------------------------------------- /plotpy/tests/benchmarks/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | plotpy benchmarks 8 | ================= 9 | """ 10 | 11 | from .test_benchmarks import test_benchmarks 12 | 13 | 14 | def run() -> None: 15 | """Run plotpy benchmarks""" 16 | test_benchmarks() 17 | 18 | 19 | if __name__ == "__main__": 20 | run() 21 | -------------------------------------------------------------------------------- /doc/dev/v1_to_guidata_v3.csv: -------------------------------------------------------------------------------- 1 | plotpy v1,guidata v3 2 | 3 | ``.gui.dataitems.*``,``.dataset.*`` 4 | ``.gui.datatypes.*``,``.dataset.*`` 5 | ``.gui.dataset.qtitemwidgets``,``.dataset.qtitemwidgets`` 6 | ``.gui.dataset.qtwidgets``,``.dataset.qtwidgets`` 7 | ``.core.io.hdf5io``,``.dataset.io.h5fmt`` 8 | ``.core.config.userconfigio``,``.dataset.io.inifmt`` 9 | ``.core.config.userconfig``,``.userconfig`` 10 | ``.core.utils.disthelpers``,*removed* 11 | -------------------------------------------------------------------------------- /plotpy/widgets/colormap/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Colormaps 5 | ========= 6 | 7 | The `colormap` module contains definition of common colormaps and tools 8 | to manipulate and create them. 9 | 10 | .. automodule:: plotpy.widgets.colormap.widget 11 | :members: 12 | .. automodule:: plotpy.widgets.colormap.editor 13 | :members: 14 | .. automodule:: plotpy.widgets.colormap.manager 15 | :members: 16 | """ 17 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel = True 3 | concurrency = multiprocessing,thread 4 | omit = 5 | */plotpy/tests/* 6 | */plotpy/external/* 7 | */plotpy/plot/interactive.py 8 | *.vscode/extensions* 9 | */qwt/* 10 | */guidata/* 11 | 12 | [report] 13 | exclude_also = 14 | def __repr__ 15 | raise AssertionError 16 | raise NotImplementedError 17 | if __name__ == .__main__.: 18 | if TYPE_CHECKING: 19 | -------------------------------------------------------------------------------- /qtdesigner/plotplugin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | plotplugin 8 | ========== 9 | 10 | A plotpy plot widget plugin for Qt Designer 11 | """ 12 | 13 | from plotpy.widgets.qtdesigner import create_qtdesigner_plugin 14 | 15 | Plugin = create_qtdesigner_plugin( 16 | "plotpy", "plotpy.plot", "PlotWidget", icon="curve.png" 17 | ) 18 | -------------------------------------------------------------------------------- /plotpy/external/__init__.py: -------------------------------------------------------------------------------- 1 | from .sliders import ( 2 | QDoubleRangeSlider, 3 | QDoubleSlider, 4 | QLabeledDoubleRangeSlider, 5 | QLabeledDoubleSlider, 6 | QLabeledRangeSlider, 7 | QLabeledSlider, 8 | QRangeSlider, 9 | ) 10 | 11 | __all__ = [ 12 | "QDoubleRangeSlider", 13 | "QDoubleSlider", 14 | "QLabeledDoubleRangeSlider", 15 | "QLabeledDoubleSlider", 16 | "QLabeledRangeSlider", 17 | "QLabeledSlider", 18 | "QRangeSlider", 19 | ] 20 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_fontparam.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """FontParam test""" 7 | 8 | from guidata.qthelpers import qt_app_context 9 | 10 | from plotpy.styles.base import FontParam 11 | 12 | 13 | def test_fontparam(): 14 | with qt_app_context(exec_loop=True): 15 | fp = FontParam() 16 | fp.edit() 17 | fp.edit() 18 | 19 | 20 | if __name__ == "__main__": 21 | test_fontparam() 22 | -------------------------------------------------------------------------------- /plotpy/interfaces/panel.py: -------------------------------------------------------------------------------- 1 | class IPanel: 2 | """Interface for panels controlled by PlotManager""" 3 | 4 | @staticmethod 5 | def __inherits__(): 6 | # Avoid circular import 7 | # pylint: disable=import-outside-toplevel 8 | from plotpy.panels import PanelWidget 9 | 10 | return PanelWidget 11 | 12 | def register_panel(self, manager): 13 | """Register panel to plot manager""" 14 | pass 15 | 16 | def configure_panel(self): 17 | """Configure panel""" 18 | pass 19 | -------------------------------------------------------------------------------- /plotpy/items/shape/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=W0611 4 | # flake8: noqa 5 | 6 | from .axis import Axes 7 | from .base import AbstractShape 8 | from .ellipse import EllipseShape 9 | from .marker import Marker 10 | from .point import PointShape 11 | from .polygon import PolygonShape 12 | from .range import XRangeSelection, YRangeSelection 13 | from .rectangle import ObliqueRectangleShape, RectangleShape 14 | from .segment import SegmentShape 15 | from .svg import CircleSVGShape, RectangleSVGShape, SquareSVGShape 16 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | build: 6 | os: ubuntu-22.04 7 | tools: 8 | python: "3.11" 9 | jobs: 10 | post_create_environment: 11 | - pip install PyQt5 setuptools numpy Cython 12 | - python setup.py build_ext --inplace 13 | sphinx: 14 | configuration: doc/conf.py 15 | formats: 16 | - pdf 17 | python: 18 | install: 19 | - method: pip 20 | path: . 21 | extra_requirements: 22 | - doc 23 | -------------------------------------------------------------------------------- /plotpy/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=unused-import 4 | # flake8: noqa 5 | 6 | from .items import ( 7 | IBaseImageItem, 8 | IBasePlotItem, 9 | IColormapImageItemType, 10 | ICSImageItemType, 11 | ICurveItemType, 12 | IDecoratorItemType, 13 | IExportROIImageItemType, 14 | IHistDataSource, 15 | IImageItemType, 16 | IItemType, 17 | ISerializableType, 18 | IShapeItemType, 19 | ITrackableItemType, 20 | IVoiImageItemType, 21 | ) 22 | from .panel import IPanel 23 | from .plotmanager import IPlotManager 24 | -------------------------------------------------------------------------------- /doc/features/styles/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | The `styles` module (see :ref:`styles`) provides the classes for configuring 5 | the plot items and the plot tools. Those classes are data sets 6 | (:py:class:`guidata.dataset.DataSet`) 7 | containing parameters that affect the visual appearance of the plot items and 8 | the plot tools behavior. 9 | 10 | .. seealso:: 11 | 12 | :ref:`plot` 13 | Ready-to-use curve and image plotting widgets and dialog boxes 14 | 15 | :ref:`items` 16 | Plot items: curve, image, shapes, etc. 17 | 18 | :ref:`tools` 19 | Plot tools: zoom, pan, etc. 20 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | # Essential to be able to compare code side-by-side (`black` default setting) 3 | # and best compromise to minimize file size 4 | max-line-length=88 5 | 6 | [TYPECHECK] 7 | ignored-modules=qtpy.QtWidgets,qtpy.QtCore,qtpy.QtGui,qtpy.QtSvg,qtpy.QtPrintSupport,qtpy.QtDesigner,plotpy._scaler,plotpy.mandelbrot,plotpy.histogram2d,PyQt5.QtWidgets 8 | 9 | [MESSAGES CONTROL] 10 | disable=wrong-import-order 11 | 12 | [DESIGN] 13 | max-args=8 # default: 5 14 | max-attributes=12 # default: 7 15 | max-branches=17 # default: 12 16 | max-locals=20 # default: 15 17 | min-public-methods=0 # default: 2 18 | max-public-methods=25 # default: 20 -------------------------------------------------------------------------------- /plotpy/panels/csection/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Cross-sections 5 | ^^^^^^^^^^^^^^ 6 | 7 | The cross-section panels are used to display cross-sections of 2D data, either 8 | along the X or Y axis, or along an oblique rectangle (average of the data along 9 | an oblique rectangle). 10 | 11 | .. autoclass:: XCrossSection 12 | .. autoclass:: YCrossSection 13 | .. autoclass:: ObliqueCrossSection 14 | .. autoclass:: LineCrossSection 15 | """ 16 | 17 | # pylint: disable=unused-import 18 | # flake8: noqa 19 | 20 | from .cswidget import ( 21 | ObliqueCrossSection, 22 | XCrossSection, 23 | YCrossSection, 24 | LineCrossSection, 25 | ) 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_wheels: 8 | name: Build wheels on ${{ matrix.os }} 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | # macos-13 is an intel runner, macos-14 is apple silicon 13 | os: [ubuntu-latest, windows-latest, macos-13, macos-14] 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Build wheels 19 | run: pipx run cibuildwheel==2.21.2 20 | 21 | - uses: actions/upload-artifact@v4 22 | with: 23 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 24 | path: ./wheelhouse/*.whl 25 | -------------------------------------------------------------------------------- /plotpy/items/image/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=unused-import 4 | # flake8: noqa 5 | 6 | from .base import BaseImageItem, RawImageItem 7 | from .filter import ImageFilterItem, XYImageFilterItem 8 | from .standard import ImageItem, RGBImageItem, XYImageItem 9 | from .masked import MaskedArea, MaskedImageItem, MaskedXYImageItem 10 | from .misc import ( 11 | Histogram2DItem, 12 | QuadGridItem, 13 | assemble_imageitems, 14 | compute_trimageitems_original_size, 15 | get_image_from_plot, 16 | get_image_from_qrect, 17 | get_image_in_shape, 18 | get_items_in_rectangle, 19 | get_plot_qrect, 20 | ) 21 | from .transform import TrImageItem 22 | -------------------------------------------------------------------------------- /doc/features/plot/reference.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | Reference 4 | --------- 5 | 6 | High-level features 7 | ^^^^^^^^^^^^^^^^^^^ 8 | 9 | .. autoclass:: plotpy.plot.manager.PlotManager 10 | :members: 11 | .. autoclass:: plotpy.plot.PlotOptions 12 | :members: 13 | .. autoclass:: plotpy.plot.PlotWidget 14 | :members: 15 | .. autoclass:: plotpy.plot.PlotDialog 16 | :members: 17 | .. autoclass:: plotpy.plot.PlotWindow 18 | :members: 19 | .. autoclass:: plotpy.plot.SyncPlotWindow 20 | :members: 21 | 22 | Low-level features 23 | ^^^^^^^^^^^^^^^^^^ 24 | 25 | .. autoclass:: plotpy.constants.PlotType 26 | :members: 27 | .. autoclass:: plotpy.plot.BasePlot 28 | :members: 29 | .. autoclass:: plotpy.plot.BasePlotOptions 30 | :members: 31 | -------------------------------------------------------------------------------- /doc/intro/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Dependencies 5 | ------------ 6 | 7 | .. note:: 8 | 9 | See :ref:`platforms` section for more information about supported platforms, 10 | Python versions and Qt bindings. 11 | 12 | .. include:: ../requirements.rst 13 | 14 | Installation using pip 15 | ---------------------- 16 | 17 | The easiest way to install plotpy is using `pip `_:: 18 | 19 | pip install plotpy 20 | 21 | Installation from source 22 | ------------------------ 23 | 24 | To install from source, clone the repository or download the source package 25 | from `PyPI `_. 26 | 27 | Then run the following command (using `build `_):: 28 | 29 | python -m build 30 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_resize.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Resize test: using the scaler C++ engine to resize images""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy import io 13 | from plotpy.mathutils import scaler 14 | from plotpy.tests import get_path 15 | from plotpy.tests.widgets.test_rotatecrop import imshow 16 | 17 | 18 | def test_resize(): 19 | """Test""" 20 | with qt_app_context(exec_loop=False): 21 | filename = get_path("brain.png") 22 | data = io.imread(filename) 23 | dst_image = scaler.resize(data, (2000, 3000)) 24 | imshow(dst_image) 25 | 26 | 27 | if __name__ == "__main__": 28 | test_resize() 29 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Test showing an image""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy.builder import make 13 | from plotpy.tests import data as ptd 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_image(): 18 | """Testing ImageItem object""" 19 | for index, func in enumerate((ptd.gen_image1, ptd.gen_image2, ptd.gen_image3)): 20 | title = test_image.__doc__ + f" #{index + 1}" 21 | data = func() 22 | with qt_app_context(exec_loop=True): 23 | _win = ptv.show_items([make.image(data)], wintitle=title) 24 | 25 | 26 | if __name__ == "__main__": 27 | test_image() 28 | -------------------------------------------------------------------------------- /plotpy/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | plotpy test package 8 | =================== 9 | """ 10 | 11 | import os.path as osp 12 | 13 | from guidata.configtools import get_module_data_path 14 | 15 | import plotpy # noqa: F401 16 | 17 | TESTDATAPATH = get_module_data_path("plotpy", osp.join("tests", "data")) 18 | 19 | 20 | def get_path(filename: str) -> str: 21 | """Return absolute path of test file""" 22 | return osp.join(TESTDATAPATH, filename) 23 | 24 | 25 | def run() -> None: 26 | """Run plotpy test launcher""" 27 | from guidata.guitest import run_testlauncher 28 | 29 | import plotpy.config # load icons 30 | 31 | run_testlauncher(plotpy) 32 | 33 | 34 | if __name__ == "__main__": 35 | run() 36 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_histogram.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Histogram test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | from numpy.random import normal 12 | 13 | from plotpy.builder import make 14 | 15 | 16 | def test_histogram(): 17 | """Test""" 18 | with qt_app_context(exec_loop=True): 19 | data = normal(0, 1, (2000,)) 20 | win = make.dialog( 21 | edit=False, 22 | toolbar=True, 23 | wintitle="Histogram test", 24 | type="curve", 25 | ) 26 | plot = win.manager.get_plot() 27 | plot.add_item(make.histogram(data)) 28 | win.show() 29 | 30 | 31 | if __name__ == "__main__": 32 | test_histogram() 33 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_no_auto_tools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Testing `auto_tools` plot option""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | from numpy import linspace, sin 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_no_auto_tools(): 18 | """Test no auto tools""" 19 | x = linspace(-10, 10, 200) 20 | y = sin(sin(sin(x))) 21 | with qt_app_context(exec_loop=True): 22 | _win = ptv.show_items( 23 | [make.curve(x, y, color="b")], 24 | auto_tools=False, 25 | wintitle=test_no_auto_tools.__doc__, 26 | ) 27 | 28 | 29 | if __name__ == "__main__": 30 | test_no_auto_tools() 31 | -------------------------------------------------------------------------------- /doc/features/items/builder.rst: -------------------------------------------------------------------------------- 1 | .. _builder: 2 | 3 | Item builder 4 | ------------ 5 | 6 | The `builder` module provides a builder singleton class that can be 7 | used to simplify the creation of plot items. 8 | 9 | .. autodata:: plotpy.builder.make 10 | 11 | .. autoclass:: plotpy.constants.LUTAlpha 12 | :members: 13 | 14 | .. autoclass:: plotpy.builder.PlotBuilder 15 | :members: widget,dialog,window,gridparam,grid,mcurve,pcurve,curve,merror,perror,error,histogram,phistogram,range,vcursor,hcursor,xcursor,marker,image,maskedimage,maskedxyimage,rgbimage,quadgrid,pcolor,trimage,xyimage,imagefilter,contours,histogram2D,rectangle,ellipse,polygon,circle,segment,svg,annotated_point,annotated_rectangle,annotated_ellipse,annotated_circle,annotated_segment,annotated_polygon,label,legend,info_label,range_info_label,computation,computations,computation2d,computations2d 16 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image_rgb.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """RGB Image test, creating the RGBImageItem object via make.rgbimage""" 7 | 8 | # guitest: show 9 | 10 | from guidata.configtools import get_image_file_path 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_image_rgb(): 18 | """Testing RGB image item""" 19 | with qt_app_context(exec_loop=True): 20 | item = make.rgbimage( 21 | filename=get_image_file_path("image.png"), xdata=[-1, 1], ydata=[-1, 1] 22 | ) 23 | _wn = ptv.show_items([item], plot_type="image", wintitle=test_image_rgb.__doc__) 24 | 25 | 26 | if __name__ == "__main__": 27 | test_image_rgb() 28 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image_xy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Image with custom X/Y axes linear scales""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy.builder import make 13 | from plotpy.tests import data as ptd 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_imagexy(): 18 | """Testing XYImageItem""" 19 | with qt_app_context(exec_loop=True): 20 | x, y, data = ptd.gen_xyimage() 21 | _win = ptv.show_items( 22 | [make.xyimage(x, y, data)], 23 | plot_type="image", 24 | xlabel="x (a.u.)", 25 | ylabel="y (a.u.)", 26 | wintitle=test_imagexy.__doc__, 27 | ) 28 | 29 | 30 | if __name__ == "__main__": 31 | test_imagexy() 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Visual Studio Code 7 | .env 8 | .venv 9 | 10 | # C extensions 11 | *.so 12 | *.pyd 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | eggs/ 20 | .eggs/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # Tests 27 | .tox/ 28 | .hypothesis/ 29 | .coverage 30 | .coverage.* 31 | .cache 32 | unittests.xml 33 | coverage.xml 34 | coverage/ 35 | .pytest_cache/ 36 | pylint_report.txt 37 | /*.h5 38 | /*.png 39 | /*.tif 40 | /*.pickle 41 | 42 | # Sphinx documentation 43 | docs/build/ 44 | 45 | # Generated cython c file 46 | src/histogram2d.c 47 | src/mandelbrot.c 48 | 49 | # qt.conf files 50 | doc/source/deployment_example/qt.conf 51 | 52 | # Coverage 53 | htmlcov 54 | 55 | # Other 56 | *.bak 57 | *_ui.py 58 | *.mo 59 | *.pot 60 | -------------------------------------------------------------------------------- /plotpy/external/sliders/_misc.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from typing import TYPE_CHECKING, Iterator 3 | 4 | if TYPE_CHECKING: 5 | from qtpy.QtCore import QObject 6 | 7 | 8 | @contextmanager 9 | def signals_blocked(obj: "QObject") -> Iterator[None]: 10 | """Context manager to temporarily block signals emitted by QObject: `obj`. 11 | 12 | Parameters 13 | ---------- 14 | obj : QObject 15 | The QObject whose signals should be blocked. 16 | 17 | Examples 18 | -------- 19 | ```python 20 | from qtpy.QtWidgets import QSpinBox 21 | from superqt import signals_blocked 22 | 23 | spinbox = QSpinBox() 24 | with signals_blocked(spinbox): 25 | spinbox.setValue(10) 26 | ``` 27 | """ 28 | previous = obj.blockSignals(True) 29 | try: 30 | yield 31 | finally: 32 | obj.blockSignals(previous) 33 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image_contour.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Contour test""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | from plotpy.tests.data import gen_xyz_data 16 | 17 | 18 | def test_contour(): 19 | """Contour plotting test""" 20 | with qt_app_context(exec_loop=True): 21 | _x, _y, z = gen_xyz_data() 22 | _win = ptv.show_items( 23 | [make.image(z)] + make.contours(z, np.arange(-2, 2, 0.5)), 24 | wintitle=test_contour.__doc__, 25 | curve_antialiasing=False, 26 | lock_aspect_ratio=True, 27 | ) 28 | 29 | 30 | if __name__ == "__main__": 31 | test_contour() 32 | -------------------------------------------------------------------------------- /plotpy/tests/widgets/test_qtdesigner.ui: -------------------------------------------------------------------------------- 1 | 2 | Dialog 3 | 4 | 5 | 6 | 0 7 | 0 8 | 500 9 | 800 10 | 11 | 12 | 13 | Dialog 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PlotWidget 27 | QwtPlot 28 |
plotpy.plot.plotwidget
29 |
30 |
31 | 32 | 33 |
34 | -------------------------------------------------------------------------------- /doc/features/tools/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | A `plot tool` is an object providing various features to a plotting widget 5 | (:py:class:`.BasePlot`): 6 | 7 | * Buttons, 8 | * Menus, 9 | * Selection tools, 10 | * Image I/O tools, 11 | * Etc. 12 | 13 | Before being used, a tool has to be registered to a plotting widget's manager, 14 | i.e. an instance of the :py:class:`.PlotManager` class (see :ref:`plot` 15 | for more details). 16 | 17 | The :py:class:`.BasePlot` widget do not provide any :py:class:`.PlotManager`: 18 | the manager has to be created separately. On the contrary, the ready-to-use widget 19 | :py:class:`.PlotWidget` are higher-level plotting widgets with 20 | integrated manager, tools and panels. 21 | 22 | .. seealso:: 23 | 24 | :ref:`plot` 25 | Ready-to-use curve and image plotting widgets and dialog boxes 26 | 27 | :ref:`items` 28 | Plot items: curves, images, markers, etc. 29 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_fit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Curve fitting tools""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | 12 | from plotpy.widgets.fit import FitParam, guifit 13 | 14 | 15 | def test_fit(): 16 | """Test the curve fitting tool""" 17 | x = np.linspace(-10, 10, 1000) 18 | y = np.cos(1.5 * x) + np.random.rand(x.shape[0]) * 0.2 19 | 20 | def fit(x, params): 21 | a, b = params 22 | return np.cos(b * x) + a 23 | 24 | a = FitParam("Offset", 0.7, -1.0, 1.0) 25 | b = FitParam("Frequency", 1.2, 0.3, 3.0, logscale=True) 26 | params = [a, b] 27 | values = guifit(x, y, fit, params, xlabel="Time (s)", ylabel="Power (a.u.)") 28 | 29 | print(values) 30 | print([param.value for param in params]) 31 | 32 | 33 | if __name__ == "__main__": 34 | test_fit() 35 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_line_cross_section_tool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import numpy as np 4 | from guidata.qthelpers import exec_dialog, qt_app_context 5 | 6 | from plotpy.interfaces.items import IImageItemType 7 | from plotpy.panels.csection.cswidget import LineCrossSection 8 | from plotpy.tests.unit.utils import create_window, drag_mouse 9 | from plotpy.tools import LineCrossSectionTool 10 | 11 | 12 | def test_line_cross_section(): 13 | """Test the line cross section tool.""" 14 | with qt_app_context(exec_loop=False): 15 | win, _tool = create_window( 16 | LineCrossSectionTool, 17 | active_item_type=IImageItemType, 18 | panels=[LineCrossSection], 19 | ) 20 | n = 100 21 | drag_mouse(win, np.linspace(0.25, 0.75, n), np.linspace(0.25, 0.75, n)) 22 | exec_dialog(win) 23 | 24 | 25 | if __name__ == "__main__": 26 | test_line_cross_section() 27 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_seg_dist.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | import qtpy.QtCore as QC 4 | 5 | from plotpy.items.curve.base import seg_dist, seg_dist_v 6 | 7 | param_list = [ 8 | ((200, 100), (150, 196), (250, 180), 86), 9 | ((200, 100), (190, 196), (210, 180), 80), 10 | ((201, 105), (201, 196), (201, 180), 75), 11 | ] 12 | 13 | 14 | @pytest.mark.parametrize("point_1,point_2,point_3,output", param_list) 15 | def test_seg_dist(point_1, point_2, point_3, output): 16 | """ """ 17 | ret = seg_dist(QC.QPointF(*point_1), QC.QPointF(*point_2), QC.QPointF(*point_3)) 18 | assert int(ret) == output 19 | 20 | 21 | def test_seg_dist_v(): 22 | """Test de seg_dist_v""" 23 | a = (np.arange(10.0) ** 2).reshape(5, 2) 24 | ix, dist = seg_dist_v((2.1, 3.3), a[:-1, 0], a[:-1, 1], a[1:, 0], a[1:, 1]) 25 | assert ix == 0 26 | assert round(dist, 2) == 0.85 27 | 28 | 29 | if __name__ == "__main__": 30 | test_seg_dist_v() 31 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_oblique_cross_section_tool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import numpy as np 4 | from guidata.qthelpers import exec_dialog, qt_app_context 5 | 6 | from plotpy.interfaces.items import IImageItemType 7 | from plotpy.panels.csection.cswidget import ObliqueCrossSection 8 | from plotpy.tests.unit.utils import create_window, drag_mouse 9 | from plotpy.tools import ObliqueCrossSectionTool 10 | 11 | 12 | def test_oblique_cross_section(): 13 | """Test the oblique cross section tool.""" 14 | with qt_app_context(exec_loop=False): 15 | win, _tool = create_window( 16 | ObliqueCrossSectionTool, 17 | active_item_type=IImageItemType, 18 | panels=[ObliqueCrossSection], 19 | ) 20 | n = 100 21 | drag_mouse(win, np.linspace(0.25, 0.75, n), np.linspace(0.25, 0.75, n)) 22 | 23 | exec_dialog(win) 24 | 25 | 26 | if __name__ == "__main__": 27 | test_oblique_cross_section() 28 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_plot_yreverse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Reverse y-axis test for curve plotting""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_plot_yreverse(): 18 | """Testing reverse x/y axes""" 19 | with qt_app_context(exec_loop=True): 20 | x = np.linspace(-10, 10, 200) 21 | win = ptv.show_items( 22 | [make.curve(x, x * np.exp(-x), color="b")], 23 | plot_type="curve", 24 | wintitle=test_plot_yreverse.__doc__, 25 | ) 26 | plot = win.manager.get_plot() 27 | plot.set_axis_direction("left", True) 28 | plot.set_axis_direction("bottom", True) 29 | 30 | 31 | if __name__ == "__main__": 32 | test_plot_yreverse() 33 | -------------------------------------------------------------------------------- /doc/features/panels/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | A `plot panel` is a graphical user interface element that interacts with a 5 | `plot` object, `plot items` and `plot tools`. 6 | 7 | Panels are objects inheriting from the :py:class:`.PanelWidget` class. 8 | 9 | .. seealso:: 10 | 11 | :ref:`plot` 12 | Ready-to-use curve and image plotting widgets and dialog boxes 13 | 14 | :ref:`items` 15 | Plot items: curves, images, markers, etc. 16 | 17 | :ref:`tools` 18 | Plot tools: zoom, pan, etc. 19 | 20 | 21 | The built-in panels are: 22 | 23 | * :py:data:`plotpy.constants.ID_ITEMLIST`: `item list` panel 24 | * :py:data:`plotpy.constants.ID_CONTRAST`: `contrast adjustment` panel 25 | * :py:data:`plotpy.constants.ID_XCS`: `X-axis cross section` panel 26 | * :py:data:`plotpy.constants.ID_YCS`: `Y-axis cross section` panel 27 | * :py:data:`plotpy.constants.ID_OCS`: `oblique cross section` panel 28 | * :py:data:`plotpy.constants.ID_LCS`: `line cross section` panel 29 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_svgshapes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Test showing SVG shapes""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy.builder import make 13 | from plotpy.tests import get_path 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_svg(): 18 | """Test showing SVG shapes""" 19 | with qt_app_context(exec_loop=True): 20 | path1, path2 = get_path("svg_tool.svg"), get_path("svg_target.svg") 21 | csvg = make.svg("circle", path2, 0, 0, 100, 100, "Circle") 22 | rsvg = make.svg("rectangle", path1, 150, 0, 200, 100, "Rect") 23 | ssvg = make.svg("square", path1, 0, 150, 100, 250, "Square") 24 | items = [csvg, rsvg, ssvg] 25 | _win = ptv.show_items(items, wintitle=test_svg.__doc__, lock_aspect_ratio=True) 26 | 27 | 28 | if __name__ == "__main__": 29 | test_svg() 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[bat]": { 3 | "files.encoding": "cp850" 4 | }, 5 | "[python]": { 6 | "editor.defaultFormatter": "charliermarsh.ruff" 7 | }, 8 | "[restructuredtext]": { 9 | "editor.wordWrap": "on" 10 | }, 11 | "editor.codeActionsOnSave": { 12 | "source.organizeImports.ruff": "explicit" 13 | }, 14 | "editor.formatOnSave": true, 15 | "editor.rulers": [ 16 | 88 17 | ], 18 | "files.exclude": { 19 | "**/__pycache__": true, 20 | "**/*.pyc": true, 21 | "**/*.pyo": true, 22 | "**/*.pyd": true 23 | }, 24 | "files.trimFinalNewlines": true, 25 | "files.trimTrailingWhitespace": true, 26 | "python.analysis.autoFormatStrings": true, 27 | "python.testing.pytestArgs": [], 28 | "python.testing.pytestEnabled": true, 29 | "python.testing.pytestPath": "pytest", 30 | "python.testing.unittestEnabled": false, 31 | "terminal.integrated.tabs.description": "${workspaceFolder}", 32 | } -------------------------------------------------------------------------------- /plotpy/tests/tools/test_cyclic_import.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Test cyclic import issue""" 7 | 8 | # pylint: disable=import-outside-toplevel 9 | # pylint: disable=unused-import 10 | 11 | 12 | def test_tools_cyclic_import(): 13 | """Test cyclic import issue""" 14 | # Importing one tool from each module to check if there is a cyclic import issue 15 | from plotpy.tools import ( 16 | AnnotatedPointTool, # noqa: F401 17 | AverageCrossSectionTool, # noqa: F401 18 | CurveStatsTool, # noqa: F401 19 | DoAutoscaleTool, # noqa: F401 20 | ItemCenterTool, # noqa: F401 21 | OpenImageTool, # noqa: F401 22 | PointTool, # noqa: F401 23 | PrintTool, # noqa: F401 24 | RectangularActionTool, # noqa: F401 25 | SelectTool, # noqa: F401 26 | YRangeCursorTool, # noqa: F401 27 | ) 28 | 29 | 30 | if __name__ == "__main__": 31 | test_tools_cyclic_import() 32 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_auto_curve_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Testing 'auto' plot type""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | from numpy import linspace, sin 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import data as ptd 15 | from plotpy.tests import vistools as ptv 16 | 17 | 18 | def make_curve_image_legend(): 19 | """Make curve, image and legend""" 20 | x = linspace(0, 2000, 20000) 21 | y = (sin(sin(sin(x / 50))) - 1) * -1000 22 | z = ptd.gen_image1() 23 | return [make.image(z), make.curve(x, y, color="w"), make.legend("TR")] 24 | 25 | 26 | def test_auto_curve_image(): 27 | """Test auto curve image""" 28 | with qt_app_context(exec_loop=True): 29 | items = make_curve_image_legend() 30 | _win = ptv.show_items(items, wintitle="Testing 'auto' plot type") 31 | 32 | 33 | if __name__ == "__main__": 34 | test_auto_curve_image() 35 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_plot_log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Logarithmic scale test for curve plotting""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_plot_log(): 18 | """Test plot log""" 19 | with qt_app_context(exec_loop=True): 20 | x = np.linspace(1, 10, 200) 21 | y = np.exp(-x) 22 | y[0] = 0 23 | items = [make.curve(x, y, color="b"), make.error(x, y, None, y * 0.23)] 24 | win = ptv.show_items(items, plot_type="curve", wintitle=test_plot_log.__doc__) 25 | plot = win.manager.get_plot() 26 | plot.set_axis_scale("left", "log") 27 | plot.set_axis_scale("bottom", "log") 28 | # plot.set_axis_limits("left", 4.53999297625e-05, 22026.4657948) 29 | 30 | 31 | if __name__ == "__main__": 32 | test_plot_log() 33 | -------------------------------------------------------------------------------- /doc/features/plot/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | -------- 3 | 4 | Using :class:`.PlotWidget` 5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | The following example shows how to use the :class:`.PlotWidget` class to create 8 | a simple plot with a curve and a filtering tool. In this example, the plot 9 | manager (see :class:`.PlotManager`) is not used, at least not directly: 10 | the plot manager is integrated in the :class:`.PlotWidget` class. 11 | 12 | .. literalinclude:: ../../../plotpy/tests/widgets/test_filtertest1.py 13 | :start-after: guitest: 14 | 15 | .. image:: ../../images/screenshots/filtertest1.png 16 | 17 | Using a plot manager 18 | ^^^^^^^^^^^^^^^^^^^^ 19 | 20 | Even if this simple example does not justify the use of the :class:`.PlotManager` 21 | (this is an unnecessary complication here), it shows how to use it. In more complex 22 | applications, using the :class:`.PlotManager` allows to design highly versatile 23 | graphical user interfaces. 24 | 25 | .. literalinclude:: ../../../plotpy/tests/widgets/test_filtertest2.py 26 | :start-after: guitest: 27 | 28 | .. image:: ../../images/screenshots/filtertest2.png 29 | -------------------------------------------------------------------------------- /plotpy/tests/benchmarks/test_bigimages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Test showing 10 big images""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | import pytest 12 | from guidata.qthelpers import qt_app_context 13 | 14 | from plotpy.builder import make 15 | 16 | 17 | def imshow(): 18 | win = make.dialog( 19 | toolbar=True, title="Displaying 10 big images test", size=(800, 600) 20 | ) 21 | plot = win.manager.get_plot() 22 | for i in range(10): 23 | plot.add_item(make.image(compute_image(i))) 24 | win.show() 25 | return win 26 | 27 | 28 | def compute_image(i, N=7500, M=1750): 29 | if i % 2 == 0: 30 | N, M = M, N 31 | return (np.random.rand(N, M) * 65536).astype(np.int16) 32 | 33 | 34 | @pytest.mark.skip(reason="Not relevant in automated test suite") 35 | def test_bigimages(): 36 | """Test Bigimages""" 37 | with qt_app_context(exec_loop=True): 38 | _persist_obj = imshow() 39 | 40 | 41 | if __name__ == "__main__": 42 | test_bigimages() 43 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_tools_export.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Export tools unit tests""" 7 | 8 | from unittest.mock import patch 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | from qtpy import QtWidgets as QW 13 | 14 | from plotpy.builder import make 15 | from plotpy.tools.curve import export_curve_data 16 | 17 | 18 | def test_export_curve(tmpdir): 19 | """Test export of a curve""" 20 | x = np.linspace(-10, 10, 200) 21 | y = x + 1 22 | 23 | with qt_app_context(exec_loop=False): 24 | curve = make.curve(x, y, color="g") 25 | 26 | dest = tmpdir / "output.txt" 27 | with patch.object(QW.QFileDialog, "getSaveFileName") as gsf_mock: 28 | gsf_mock.return_value = (str(dest), "") 29 | export_curve_data(curve) 30 | 31 | assert dest.exists() 32 | data = dest.readlines() 33 | assert len(data) == 200 34 | for line in data: 35 | x, y = line.split(",") 36 | assert float(y) == float(x) + 1 37 | -------------------------------------------------------------------------------- /doc/dev/build.rst: -------------------------------------------------------------------------------- 1 | How to build, test and deploy 2 | ----------------------------- 3 | 4 | Build instructions 5 | ^^^^^^^^^^^^^^^^^^ 6 | 7 | To build the wheel, you need: 8 | 9 | * Python 10 | * numpy 11 | * Cython 12 | * A C++ compiler like gcc or `Build Tools for Visual Studio 2017 `_ 13 | 14 | Then run the following command:: 15 | 16 | python setup.py bdist_wheel 17 | 18 | It should generate a ``.whl`` file in the `dist` directory. 19 | 20 | 21 | Running unittests 22 | ^^^^^^^^^^^^^^^^^ 23 | 24 | To run the unittests, you need: 25 | 26 | * Python 27 | * pytest 28 | * coverage (optional) 29 | 30 | Then run the following command:: 31 | 32 | pytest plotpy 33 | 34 | To run test with coverage support, use the following command:: 35 | 36 | pytest -v --cov --cov-report=html plotpy 37 | 38 | 39 | Code formatting 40 | ^^^^^^^^^^^^^^^ 41 | 42 | The code is formatted with `ruff `_. 43 | 44 | If you are using `Visual Studio Code `_, 45 | the formatting is done automatically when you save a file, thanks to the 46 | project settings in the `.vscode` directory. 47 | -------------------------------------------------------------------------------- /plotpy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | plotpy 4 | ====== 5 | 6 | Based on `PythonQwt` (plotting widgets for Qt graphical user interfaces) and 7 | on the scientific modules NumPy and SciPy, :mod:`plotpy` is a Python library 8 | providing efficient 2D data-plotting features (curve/image visualization 9 | and related tools) for interactive computing and signal/image processing 10 | application development. 11 | 12 | .. image:: images/panorama.png 13 | 14 | 15 | External resources: 16 | * Python Package Index: `PyPI`_ 17 | * Bug reports and feature requests: `GitHub`_ 18 | 19 | .. _PyPI: https://pypi.python.org/pypi/plotpy 20 | .. _GitHub: https://github.com/PierreRaybaut/plotpy 21 | """ 22 | 23 | __version__ = "2.8.3" 24 | __VERSION__ = tuple([int(number) for number in __version__.split(".")]) 25 | 26 | # --- Important note: DATAPATH and LOCALEPATH are used by guidata.configtools 27 | # --- to retrieve data and translation files paths 28 | # 29 | # Dear (Debian, RPM, ...) package makers, please feel free to customize the 30 | # following path to module's data (e.g. icons) and translations: 31 | DATAPATH = LOCALEPATH = "" 32 | -------------------------------------------------------------------------------- /plotpy/interfaces/plotmanager.py: -------------------------------------------------------------------------------- 1 | class IPlotManager: 2 | """A 'controller' that organizes relations between 3 | plots (BasePlot), panels, tools (GuiTool) and toolbar 4 | """ 5 | 6 | def add_plot(self, plot, plot_id="default"): 7 | """ 8 | 9 | :param plot: 10 | :param plot_id: 11 | """ 12 | # Avoid circular import 13 | # pylint: disable=import-outside-toplevel 14 | from plotpy.plot.base import BasePlot 15 | 16 | assert id not in self.plots # pylint: disable=no-member 17 | assert isinstance(plot, BasePlot) 18 | 19 | def add_panel(self, panel): 20 | """ 21 | 22 | :param panel: 23 | """ 24 | assert id not in self.panels # pylint: disable=no-member 25 | 26 | def add_toolbar(self, toolbar, toolbar_id="default"): 27 | """ 28 | 29 | :param toolbar: 30 | :param toolbar_id: 31 | """ 32 | assert id not in self.toolbars # pylint: disable=no-member 33 | 34 | def get_active_plot(self): 35 | """The active plot is the plot whose canvas has the focus 36 | otherwise it's the "default" plot 37 | """ 38 | pass 39 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_cursors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Horizontal/vertical cursors test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | from numpy import linspace, sin 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_cursor(): 18 | """Test cursor""" 19 | x = linspace(-10, 10, 1000) + 1 20 | y = sin(sin(sin(x))) + 3 21 | with qt_app_context(exec_loop=True): 22 | curve = make.curve(x, y, "ab", "b") 23 | hcursor = make.hcursor(3.2, label="y = %.2f") 24 | vcursor = make.vcursor(7, label="x = %.2f") 25 | vcursor2 = make.vcursor(-1, label="NOT MOVABLE = %.2f", movable=False) 26 | xcursor = make.xcursor(-4, 2.5, label="x = %.2f
y = %.2f") 27 | legend = make.legend("TR") 28 | _win = ptv.show_items( 29 | wintitle="Plot cursors", 30 | items=[curve, hcursor, vcursor, vcursor2, xcursor, legend], 31 | plot_type="curve", 32 | ) 33 | 34 | 35 | if __name__ == "__main__": 36 | test_cursor() 37 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image_aspect_ratio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Image and shapes with unlock aspect ratio test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy.builder import make 13 | from plotpy.tests import data as ptd 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_image_aspect_ratio(): 18 | """Testing image and shapes with unlock aspect ratio""" 19 | with qt_app_context(exec_loop=True): 20 | data = ptd.gen_image4(500, 700) 21 | image = make.image(data) 22 | image.hide() 23 | circle = make.circle(200, 200, 500, 300) 24 | circle.select() 25 | txt = "This test is considered passed if
the ellipse is drawn properly." 26 | win = ptv.show_items( 27 | [image, circle, make.label(txt, "C", (0, 0), "C")], 28 | plot_type="image", 29 | wintitle=test_image_aspect_ratio.__doc__, 30 | lock_aspect_ratio=False, 31 | ) 32 | win.plot_widget.plot.set_aspect_ratio(0.25) 33 | 34 | 35 | if __name__ == "__main__": 36 | test_image_aspect_ratio() 37 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_cross_section_oblique.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Oblique averaged cross section test""" 7 | 8 | # guitest: show 9 | 10 | from __future__ import annotations 11 | 12 | from plotpy.panels.csection.csitem import ObliqueCrossSectionItem 13 | from plotpy.panels.csection.cswidget import ObliqueCrossSection 14 | from plotpy.tests.tools import test_cross_section_line 15 | from plotpy.tools import ImageMaskTool, ObliqueCrossSectionTool, OCSPanelTool 16 | 17 | # debug mode shows the ROI in the top-left corner of the image plot: 18 | ObliqueCrossSectionItem.DEBUG = False 19 | 20 | 21 | class OCSImageDialog(test_cross_section_line.BaseCSImageDialog): 22 | """Oblique averaged cross section test""" 23 | 24 | TOOLCLASSES = (ObliqueCrossSectionTool, OCSPanelTool, ImageMaskTool) 25 | PANELCLASS = ObliqueCrossSection 26 | 27 | 28 | def test_cross_section_oblique(): 29 | """Test cross section oblique""" 30 | test_cross_section_line.generic_cross_section_dialog( 31 | "Oblique averaged cross section test", OCSImageDialog 32 | ) 33 | 34 | 35 | if __name__ == "__main__": 36 | test_cross_section_oblique() 37 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_annotated_range.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Testing `AnnotatedXRange` and `AnnotatedYRange` items""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_annotated_range_selection(): 18 | """Test AnnotatedXRange and AnnotatedYRange items""" 19 | x = np.linspace(-5, 15, 200) 20 | y = np.sin(np.sin(np.sin(x))) + 0.5 21 | with qt_app_context(exec_loop=True): 22 | items = [ 23 | make.curve(x, y, color="b"), 24 | make.annotated_xrange(x0=-5, x1=0.0, title="X Range Selection"), 25 | make.annotated_yrange(y0=-0.2, y1=0.1, title="Y Range Selection"), 26 | ] 27 | _win = ptv.show_items( 28 | items, 29 | wintitle=test_annotated_range_selection.__doc__, 30 | title="Annotated Range Selection", 31 | plot_type="curve", 32 | disable_readonly_for_items=False, 33 | ) 34 | 35 | 36 | if __name__ == "__main__": 37 | test_annotated_range_selection() 38 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_get_rectangle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # For licensing and distribution details, please read carefully xgrid/__init__.py 4 | 5 | """ 6 | Get rectangular selection from image 7 | """ 8 | 9 | # guitest: show 10 | 11 | import numpy as np 12 | from guidata.env import execenv 13 | from guidata.qthelpers import qt_app_context 14 | 15 | from plotpy.builder import make 16 | from plotpy.tests.tools.test_get_segment import SEG_AXES_COORDS, PatchedSelectDialog 17 | from plotpy.tools import RectangleTool 18 | from plotpy.widgets.selectdialog import select_with_shape_tool 19 | 20 | 21 | def test_get_rectangle(): 22 | """Test get_rectangle""" 23 | with qt_app_context(): 24 | image = make.image(data=np.random.rand(200, 200), colormap="gray") 25 | shape = select_with_shape_tool( 26 | None, RectangleTool, image, "Test", tooldialogclass=PatchedSelectDialog 27 | ) 28 | if shape is not None: 29 | rect = shape.get_rect() 30 | if execenv.unattended: 31 | assert [round(i) for i in list(rect)] == SEG_AXES_COORDS 32 | elif rect is not None: 33 | print("Area:", rect) 34 | 35 | 36 | if __name__ == "__main__": 37 | test_get_rectangle() 38 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_aspect_ratio_tool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from guidata.qthelpers import exec_dialog, qt_app_context 4 | 5 | from plotpy.interfaces.items import IImageItemType 6 | from plotpy.tests.unit.utils import create_window 7 | from plotpy.tools import AspectRatioTool 8 | 9 | 10 | def test_aspect_ratio_tool(): 11 | """Test the aspect ratio tool.""" 12 | with qt_app_context(exec_loop=False): 13 | win, tool = create_window(AspectRatioTool, active_item_type=IImageItemType) 14 | plot = win.manager.get_plot() 15 | 16 | initial_aspect_ratio: float = plot.get_aspect_ratio() 17 | 18 | new_ratio = 0.5 19 | 20 | plot.set_aspect_ratio(new_ratio) 21 | tool.edit_aspect_ratio() 22 | assert plot.get_aspect_ratio() == new_ratio 23 | 24 | plot.set_aspect_ratio(initial_aspect_ratio) 25 | tool.edit_aspect_ratio() 26 | assert plot.get_aspect_ratio() == initial_aspect_ratio 27 | 28 | tool.lock_aspect_ratio(True) 29 | assert plot.lock_aspect_ratio is True 30 | tool.lock_aspect_ratio(False) 31 | assert plot.lock_aspect_ratio is False 32 | 33 | exec_dialog(win) 34 | 35 | 36 | if __name__ == "__main__": 37 | test_aspect_ratio_tool() 38 | -------------------------------------------------------------------------------- /plotpy/builder/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | # pylint: disable=C0103 7 | 8 | """ 9 | Item builder 10 | ------------ 11 | 12 | The `builder` module provides a builder singleton class that can be 13 | used to simplify the creation of plot items. 14 | """ 15 | 16 | from __future__ import annotations 17 | 18 | from .annotation import AnnotationBuilder 19 | from .curvemarker import CurveMarkerCursorBuilder 20 | from .image import ImageBuilder 21 | from .label import LabelBuilder 22 | from .plot import WidgetBuilder 23 | from .shape import ShapeBuilder 24 | 25 | 26 | class PlotBuilder( 27 | WidgetBuilder, 28 | CurveMarkerCursorBuilder, 29 | ImageBuilder, 30 | LabelBuilder, 31 | ShapeBuilder, 32 | AnnotationBuilder, 33 | ): 34 | """Class regrouping a set of factory functions to simplify the creation 35 | of plot widgets and plot items. 36 | 37 | It is a singleton class, so you should not create instances of this class 38 | but use the :py:data:`plotpy.builder.make` instance instead. 39 | """ 40 | 41 | def __init__(self): 42 | super().__init__() 43 | 44 | 45 | #: Instance of :py:class:`.PlotBuilder` 46 | make = PlotBuilder() 47 | -------------------------------------------------------------------------------- /src/debug.hpp: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8;mode:c++;c-file-style:"stroustrup" -*- */ 2 | /* 3 | Licensed under the terms of the BSD 3-Clause 4 | (see plotpy/__init__.py for details) 5 | */ 6 | #ifndef __DEBUG_HPP__ 7 | #define __DEBUG_HPP__ 8 | 9 | #define DEBUG 0 10 | 11 | #if DEBUG 12 | 13 | #define check(msg, x, n, r) \ 14 | if (x < 0 || x >= n) \ 15 | { \ 16 | printf(msg "%d out of bound (%d)\n", x, n); \ 17 | return r; \ 18 | } 19 | #define check_img_ptr(msg, p, r, img) \ 20 | if (p < img.base || p > (img.base + (img.ni - 1) * img.si + (img.nj - 1) * img.sj)) \ 21 | { \ 22 | printf(msg "%p out of bound (%p,%dx%d, %dx%d\n", p, img.base, img.ni, img.si, img.nj, img.sj); \ 23 | return r; \ 24 | } 25 | 26 | #else 27 | 28 | #define check(msg, x, n, r) ; 29 | #define check_img_ptr(msg, p, r, img) ; 30 | 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_loadsaveitems_hdf5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Load/save items from/to HDF5 file""" 7 | 8 | # guitest: show 9 | 10 | # WARNING: 11 | # This script requires read/write permissions on current directory 12 | 13 | from __future__ import annotations 14 | 15 | from guidata.io import HDF5Reader, HDF5Writer 16 | 17 | from plotpy.tests.features.test_loadsaveitems_pickle import IOTest 18 | 19 | 20 | class HDF5Test(IOTest): 21 | """Test load/save items from/to HDF5 file""" 22 | 23 | FNAME = "loadsavecanvas.h5" 24 | 25 | def restore_items(self) -> None: 26 | """Restore items from HDF5 file""" 27 | reader = HDF5Reader(self.FNAME) 28 | self.plot.deserialize(reader) 29 | reader.close() 30 | 31 | def save_items(self) -> None: 32 | """Save items to HDF5 file""" 33 | writer = HDF5Writer(self.FNAME) 34 | self.plot.serialize(writer) 35 | writer.close() 36 | 37 | 38 | def test_loadsaveitems_hdf5(): 39 | """Test load/save items from/to HDF5 file""" 40 | test = HDF5Test("Load/save items from/to HDF5 file") 41 | test.run() 42 | 43 | 44 | if __name__ == "__main__": 45 | test_loadsaveitems_hdf5() 46 | -------------------------------------------------------------------------------- /doc/features/plot/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | Features 5 | ^^^^^^^^ 6 | 7 | The `plot` module provides the following features: 8 | 9 | * :py:class:`.PlotManager`: the `plot manager` is an object to 10 | link `plots`, `panels` and `tools` together for designing highly 11 | versatile graphical user interfaces 12 | 13 | * :py:class:`.PlotWidget`: a ready-to-use widget for curve/image 14 | displaying with an integrated and preconfigured `plot manager` providing 15 | the `item list panel` and curve/image-related `tools` 16 | 17 | * :py:class:`.PlotDialog`: a ready-to-use dialog box for 18 | curve/image displaying with an integrated and preconfigured `plot manager` 19 | providing the `item list panel` and curve/image-related `tools` 20 | 21 | * :py:class:`.SyncPlotWindow`: a ready-to-use window for curve/image 22 | displaying with plot axes synchronization 23 | 24 | .. seealso:: 25 | 26 | :ref:`items` 27 | Plot items: curves, images, markers, text, etc. 28 | 29 | :ref:`tools` 30 | Plot tools: zoom, pan, etc. 31 | 32 | 33 | Class diagrams 34 | ^^^^^^^^^^^^^^ 35 | 36 | Plot widgets with integrated plot manager: 37 | 38 | .. image:: ../../images/plot_widgets.png 39 | 40 | Building your own plot manager: 41 | 42 | .. image:: ../../images/my_plot_manager.png 43 | -------------------------------------------------------------------------------- /plotpy/external/sliders/_sliders.py: -------------------------------------------------------------------------------- 1 | from qtpy.QtCore import Signal 2 | 3 | from ._generic_range_slider import _GenericRangeSlider 4 | from ._generic_slider import _GenericSlider 5 | 6 | 7 | class _IntMixin: 8 | def __init__(self, *args, **kwargs): 9 | super().__init__(*args, **kwargs) 10 | self._singleStep = 1 11 | 12 | def _type_cast(self, value) -> int: 13 | return int(round(value)) 14 | 15 | 16 | class _FloatMixin: 17 | _fvalueChanged = Signal(float) 18 | _fsliderMoved = Signal(float) 19 | _frangeChanged = Signal(float, float) 20 | 21 | def __init__(self, *args, **kwargs): 22 | super().__init__(*args, **kwargs) 23 | self._singleStep = 0.01 24 | self._pageStep = 0.1 25 | 26 | def _type_cast(self, value) -> float: 27 | return float(value) 28 | 29 | 30 | class QDoubleSlider(_FloatMixin, _GenericSlider): 31 | pass 32 | 33 | 34 | class QIntSlider(_IntMixin, _GenericSlider): 35 | # mostly just an example... use QSlider instead. 36 | valueChanged = Signal(int) 37 | 38 | 39 | class QRangeSlider(_IntMixin, _GenericRangeSlider): 40 | pass 41 | 42 | 43 | class QDoubleRangeSlider(_FloatMixin, QRangeSlider): 44 | pass 45 | 46 | 47 | # QRangeSlider.__doc__ += "\n" + textwrap.indent(QSlider.__doc__, " ") 48 | -------------------------------------------------------------------------------- /src/mandelbrot.pyx: -------------------------------------------------------------------------------- 1 | # cython: language_level=3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Licensed under the terms of the BSD 3-Clause 5 | # (see plotpy/__init__.py for details) 6 | 7 | """Mandelbrot algorithm""" 8 | 9 | cimport cython 10 | cimport numpy as np 11 | 12 | @cython.profile(False) 13 | cdef inline int mandel(double real, double imag, int nmax): 14 | cdef double z_real=0., z_imag=0. 15 | cdef int i 16 | for i in range(nmax): 17 | z_real, z_imag = (z_real*z_real - z_imag*z_imag + real, 18 | 2*z_real*z_imag + imag) 19 | if z_real*z_real + z_imag*z_imag > 4: 20 | return i 21 | return -1 22 | 23 | @cython.boundscheck(False) 24 | def mandelbrot(double x1, double y1, double x2, double y2, 25 | data, unsigned int nmax): 26 | """Compute Mandelbrot fractal""" 27 | cdef double dx, dy, real, imag 28 | cdef unsigned int row, col 29 | cdef unsigned int rows = data.shape[0] 30 | cdef unsigned int cols = data.shape[1] 31 | 32 | dx = (x2-x1)/(cols-1) 33 | dy = (y2-y1)/(rows-1) 34 | 35 | for col in range(cols): 36 | real = x1 + col*dx 37 | for row in range(rows): 38 | imag = y1 + row*dy 39 | data[row, col] = mandel(real, imag, nmax) 40 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_cross_section.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Renders a cross section chosen by a cross marker""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import get_path 15 | 16 | 17 | def create_window(): 18 | win = make.dialog( 19 | edit=False, 20 | toolbar=True, 21 | wintitle="Cross sections test", 22 | show_xsection=True, 23 | show_ysection=True, 24 | type="image", 25 | size=(640, 600), 26 | ) 27 | return win 28 | 29 | 30 | def test_cross_section(): 31 | """Test cross section""" 32 | with qt_app_context(exec_loop=True): 33 | filename = get_path("brain.png") 34 | win = create_window() 35 | win.show() 36 | image = make.image(filename=filename, colormap="bone") 37 | data2 = np.array(image.data.T[200:], copy=True) 38 | image2 = make.image(data2, title="Modified", alpha_function="linear") 39 | plot = win.manager.get_plot() 40 | plot.add_item(image) 41 | plot.add_item(image2, z=1) 42 | 43 | 44 | if __name__ == "__main__": 45 | test_cross_section() 46 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_dicom_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """DICOM image test 7 | 8 | Requires pydicom (>=0.9.3)""" 9 | 10 | # guitest: show 11 | 12 | import pytest 13 | from guidata.qthelpers import qt_app_context 14 | 15 | from plotpy.builder import make 16 | from plotpy.tests import get_path 17 | 18 | try: 19 | import pydicom # type:ignore 20 | except ImportError: 21 | pydicom = None 22 | 23 | 24 | @pytest.mark.skipif(pydicom is None, reason="pydicom not installed") 25 | def test_dicom_image(): 26 | with qt_app_context(exec_loop=True): 27 | win = make.dialog( 28 | edit=False, 29 | toolbar=True, 30 | wintitle="DICOM I/O test", 31 | show_contrast=True, 32 | type="image", 33 | size=(600, 700), 34 | ) 35 | filename = get_path("mr-brain.dcm") 36 | image = make.image(filename=filename, title="DICOM img", colormap="gray") 37 | plot = win.manager.get_plot() 38 | plot.add_item(image) 39 | plot.select_item(image) 40 | contrast = win.manager.get_contrast_panel() 41 | contrast.histogram.eliminate_outliers(54.0) 42 | 43 | 44 | if __name__ == "__main__": 45 | win = test_dicom_image() 46 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_plot_types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """PlotTypes test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | from numpy import linspace, sin 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import data as ptd 15 | from plotpy.tests import vistools as ptv 16 | 17 | 18 | def test_plot_types(): 19 | """Test plot types""" 20 | _persist = [] 21 | with qt_app_context(exec_loop=True): 22 | x = linspace(-10, 10, 200) 23 | y = sin(sin(sin(x))) 24 | for plot_type in ("curve", "image", "auto", "manual"): 25 | for reverse in (True, False): 26 | revtxt = f"{'image' if reverse else 'curve'} added first" 27 | items = [ 28 | make.curve(x, y, color="b"), 29 | make.image(ptd.gen_image1()), 30 | ] 31 | _persist.append( 32 | ptv.show_items( 33 | reversed(items) if reverse else items, 34 | plot_type=plot_type, 35 | wintitle=f"Plot type: '{plot_type}' ({revtxt})", 36 | ) 37 | ) 38 | 39 | 40 | if __name__ == "__main__": 41 | test_plot_types() 42 | -------------------------------------------------------------------------------- /plotpy/tests/widgets/test_qtdesigner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | # -*- coding: utf-8 -*- 7 | """ 8 | Testing plotpy QtDesigner plugins 9 | 10 | These plugins provide PlotWidget objects 11 | embedding in GUI layouts directly from QtDesigner. 12 | """ 13 | 14 | # guitest: show 15 | 16 | import os 17 | 18 | import pytest 19 | from guidata.qthelpers import qt_app_context 20 | 21 | from plotpy.builder import make 22 | from plotpy.tests import data as ptd 23 | 24 | try: 25 | from plotpy.widgets.qtdesigner import loadui 26 | except ImportError: 27 | # PySide6 known to fail 28 | pytest.skip( 29 | "PySide6 does not support QPyDesignerCustomWidgetPlugin", 30 | allow_module_level=True, 31 | ) 32 | 33 | FormClass = loadui(os.path.splitext(__file__)[0] + ".ui") 34 | 35 | 36 | class WindowTest(FormClass): 37 | def __init__(self, image_data): 38 | super().__init__() 39 | plot = self.imagewidget.plot 40 | plot.add_item(make.image(image_data)) 41 | self.setWindowTitle("QtDesigner plugins example") 42 | 43 | 44 | def test_qtdesigner(): 45 | with qt_app_context(exec_loop=True): 46 | form = WindowTest(ptd.gen_image4(200, 200)) 47 | form.show() 48 | 49 | 50 | if __name__ == "__main__": 51 | test_qtdesigner() 52 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_annotations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Annotation test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | from numpy import linspace, sin 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import data as ptd 15 | from plotpy.tests import vistools as ptv 16 | 17 | 18 | def plot(*items, plot_type="auto"): 19 | title = "All annotation tools" 20 | if plot_type in ("curve", "image"): 21 | title = f"{plot_type.capitalize()} specialized plot annotation tools" 22 | win = ptv.show_items(items, plot_type=plot_type, wintitle=title, title=title) 23 | win.register_annotation_tools() 24 | return win 25 | 26 | 27 | def test_annotation(): 28 | """Test annotation""" 29 | x = linspace(-10, 10, 200) 30 | y = sin(sin(sin(x))) 31 | persist = [] 32 | with qt_app_context(exec_loop=True): 33 | persist.append(plot(make.curve(x, y, color="b"), plot_type="curve")) 34 | item = make.image(ptd.gen_image1()) 35 | item.set_readonly(True) 36 | item.set_selectable(False) 37 | persist.append(plot(item, plot_type="image")) 38 | persist.append(plot(make.curve(x, y, color="b"), make.image(ptd.gen_image1()))) 39 | 40 | 41 | if __name__ == "__main__": 42 | test_annotation() 43 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image_masked.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | Masked Image test, creating the MaskedImageItem object via make.maskedimage 8 | 9 | Masked image items are constructed using a masked array item. Masked data is 10 | ignored in computations, like the average cross sections. 11 | """ 12 | 13 | # guitest: show 14 | 15 | from __future__ import annotations 16 | 17 | import os.path as osp 18 | 19 | from plotpy.builder import make 20 | from plotpy.tests import get_path 21 | from plotpy.tests.features.test_loadsaveitems_pickle import PickleTest 22 | 23 | 24 | class MaskedImageTest(PickleTest): 25 | """Test class for MaskedImageItem tests""" 26 | 27 | FNAME = f"{osp.splitext(osp.basename(__file__))[0]}.pickle" 28 | IMAGE_FN = get_path("brain.png") 29 | 30 | def build_items(self) -> list: 31 | """Build items""" 32 | image = make.maskedimage( 33 | filename=self.IMAGE_FN, 34 | colormap="gray", 35 | show_mask=True, 36 | xdata=[0, 20], 37 | ydata=[0, 25], 38 | ) 39 | return [image] 40 | 41 | 42 | def test_image_masked(): 43 | """Test MaskedImageItem""" 44 | test = MaskedImageTest("MaskedImageItem test") 45 | test.run() 46 | 47 | 48 | if __name__ == "__main__": 49 | test_image_masked() 50 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_image_masked_xy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | Masked Image test, creating the MaskedXYImageItem object via make.maskedxyimage 8 | 9 | Masked image XY items are constructed using a masked array item. Masked data is 10 | ignored in computations, like the average cross sections. 11 | """ 12 | 13 | # guitest: show 14 | 15 | from __future__ import annotations 16 | 17 | from plotpy import io 18 | from plotpy.builder import make 19 | from plotpy.tests.items.test_image_masked import MaskedImageTest 20 | 21 | 22 | class XYMaskedImageTest(MaskedImageTest): 23 | """XYMaskedImageItem test class""" 24 | 25 | def build_items(self) -> list: 26 | """Build items""" 27 | data = io.imread(self.IMAGE_FN, to_grayscale=True) 28 | x = [0] 29 | delta = 1 30 | for x_val in range(data.shape[0] - 1): 31 | delta = delta + 0.1 32 | x.append(x[-1] + delta) 33 | y = [0] 34 | for y_val in range(data.shape[1] - 1): 35 | delta = delta + 0.1 36 | y.append(y[-1] + delta) 37 | image = make.maskedimage(data=data, colormap="gray", show_mask=True, x=x, y=y) 38 | return [image] 39 | 40 | 41 | def test_image_masked_xy(): 42 | """Test MaskedImageItem""" 43 | test = XYMaskedImageTest("MaskedImageItem test") 44 | test.run() 45 | 46 | 47 | if __name__ == "__main__": 48 | test_image_masked_xy() 49 | -------------------------------------------------------------------------------- /scripts/build_inplace.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was copied from PythonQwt project 3 | REM ====================================================== 4 | REM Package build script 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal enabledelayedexpansion 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | 15 | if exist MANIFEST ( del /q MANIFEST ) 16 | :: Iterate over all directories in the grandparent directory 17 | :: (WinPython base directories) 18 | call %FUNC% GetPythonExeGrandParentDir DIR0 19 | for /D %%d in ("%DIR0%*") do ( 20 | :: Get the directory name without the path 21 | for %%n in (%%d) do set "DIRNAME=%%~nxn" 22 | 23 | :: Check if the directory ends with "-PyQt6" or "-PySide6" 24 | if not "!DIRNAME:~-6!"=="-PyQt6" ( 25 | if not "!DIRNAME:~-8!"=="-PySide6" ( 26 | set WINPYDIRBASE=%%d 27 | call !WINPYDIRBASE!\scripts\env.bat 28 | echo ****************************************************************************** 29 | echo Building %MODNAME% from "%%d" 30 | echo ****************************************************************************** 31 | python setup.py build_ext --inplace 32 | echo ---- 33 | ) 34 | ) 35 | ) 36 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /plotpy/tests/features/test_computations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Plot computations test""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | import scipy.integrate as spt 12 | from guidata.qthelpers import qt_app_context 13 | 14 | from plotpy.builder import make 15 | from plotpy.tests import vistools as ptv 16 | 17 | 18 | def test_computations(): 19 | """Test computations""" 20 | x = np.linspace(-10, 10, 1000) 21 | y = np.sin(np.sin(np.sin(x))) 22 | with qt_app_context(exec_loop=True): 23 | curve = make.curve(x, y, "ab", "b") 24 | range = make.xrange(-2, 2) 25 | disp0 = make.range_info_label( 26 | range, "BR", "x = %.1f ± %.1f cm", title="Range info" 27 | ) 28 | 29 | disp1 = make.computation( 30 | range, "BL", "trapz=%g", curve, lambda x, y: spt.trapezoid(y, x) 31 | ) 32 | 33 | disp2 = make.computations( 34 | range, 35 | "TL", 36 | [ 37 | (curve, "min=%.5f", lambda x, y: y.min()), 38 | (curve, "max=%.5f", lambda x, y: y.max()), 39 | (curve, "avg=%.5f", lambda x, y: y.mean()), 40 | ], 41 | ) 42 | legend = make.legend("TR") 43 | _win = ptv.show_items( 44 | wintitle="Plot computations", 45 | items=[curve, range, disp0, disp1, disp2, legend], 46 | plot_type="curve", 47 | ) 48 | 49 | 50 | if __name__ == "__main__": 51 | test_computations() 52 | -------------------------------------------------------------------------------- /scripts/build_wheels.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM This script was copied from PythonQwt project 3 | REM ====================================================== 4 | REM Package build script 5 | REM ====================================================== 6 | REM Licensed under the terms of the MIT License 7 | REM Copyright (c) 2020 Pierre Raybaut 8 | REM (see PythonQwt LICENSE file for more details) 9 | REM ====================================================== 10 | setlocal enabledelayedexpansion 11 | call %~dp0utils GetScriptPath SCRIPTPATH 12 | call %FUNC% GetModName MODNAME 13 | call %FUNC% SetPythonPath 14 | 15 | if exist MANIFEST ( del /q MANIFEST ) 16 | :: Iterate over all directories in the grandparent directory 17 | :: (WinPython base directories) 18 | call %FUNC% GetPythonExeGrandParentDir DIR0 19 | for /D %%d in ("%DIR0%*") do ( 20 | :: Get the directory name without the path 21 | for %%n in (%%d) do set "DIRNAME=%%~nxn" 22 | 23 | :: Check if the directory ends with "-PyQt6" or "-PySide6" 24 | if not "!DIRNAME:~-6!"=="-PyQt6" ( 25 | if not "!DIRNAME:~-8!"=="-PySide6" ( 26 | set WINPYDIRBASE=%%d 27 | set OLD_PATH=!PATH! 28 | call !WINPYDIRBASE!\scripts\env.bat 29 | echo ****************************************************************************** 30 | echo Building %MODNAME% from "%%d" 31 | echo ****************************************************************************** 32 | python -m build 33 | echo ---- 34 | set PATH=!OLD_PATH! 35 | ) 36 | ) 37 | ) 38 | call %FUNC% EndOfScript -------------------------------------------------------------------------------- /media/LinkedIn/2023_10_31-V2.md: -------------------------------------------------------------------------------- 1 | # Announcement: Release of PlotPy V2 📊 2 | 3 | PlotPy V2 distinguishes itself in the realm of plotting libraries. Designed for Python/Qt applications, this library offers a blend of superior performance and enhanced interactive features. Its image display features, driven by a C++ transform engine, include real-time high-quality interpolation, LUT, and geometric transformations, elevating data interaction. 4 | 5 | PlotPy is part of the PlotPyStack project (), dedicated to delivering a comprehensive toolkit for crafting scientific and technical data visualization applications. 6 | 7 | The development efforts for PlotPy V2 were financed by the CEA (). 8 | 9 | What's New in PlotPy V2 🌟: 10 | 11 | 🔍 Major Updates: 12 | 13 | Refined and unified API for curve and image plotting features (widget, dialog, window). Introduction of a new window for synchronized multi-plot displays 🔄. 14 | 15 | 🎨 Enhancements: 16 | 17 | Expanded Image Lookup Table functionalities. 18 | Integration of SVG-based shapes. 19 | 20 | 📖 Documentation: 21 | Comprehensive Sphinx-based documentation enriched with API links, examples, and tutorials. 22 | 23 | ⚙️ Development Features: 24 | Black code formatting, robust `pytest`-based automated test suite, and a 70% test coverage milestone ✅. 25 | 26 | We invite the community to explore PlotPy V2's capabilities. 27 | 28 | Explore PlotPy V2 on PyPI: . 29 | View the project on GitHub: . 30 | Review the documentation: . 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, CEA-Codra, Pierre Raybaut. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /scripts/build-wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -u -x 3 | 4 | ARCH=$(uname -m) 5 | export PLAT=manylinux_2_24_$ARCH 6 | 7 | # Accurately check if Python version is 3.9 or greater 8 | function is_python_version_ge_39 { 9 | local pydir="$1" 10 | if [[ $pydir =~ cp([0-9]+)([0-9]+)-cp ]]; then 11 | local version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" 12 | # Use sort with version sort flag to compare version against 3.9 13 | if [[ $(echo -e "3.9\n$version" | sort -V | head -n1) == "3.9" ]]; then 14 | return 0 # True, version is >= 3.9 15 | fi 16 | fi 17 | return 1 # False, version is < 3.9 18 | } 19 | 20 | # Compile wheels, only for CPython 3.9+ 21 | for PYDIR in /opt/python/*; do 22 | if is_python_version_ge_39 "$PYDIR"; then 23 | PYBIN="$PYDIR/bin" 24 | "${PYBIN}/pip" install -r /io/requirements.txt 25 | "${PYBIN}/pip" wheel /io/ --no-deps -w wheelhouse/ 26 | fi 27 | done 28 | 29 | # Bundle external shared libraries into the wheels 30 | for wheel in wheelhouse/*.whl; do 31 | if auditwheel show "$wheel"; then 32 | auditwheel repair "$wheel" --plat "$PLAT" -w /io/wheelhouse/ 33 | fi 34 | done 35 | 36 | # Install packages and test, only for CPython 3.9+ 37 | for PYDIR in /opt/python/*; do 38 | if is_python_version_ge_39 "$PYDIR"; then 39 | PYBIN="$PYDIR/bin" 40 | "${PYBIN}/pip" install plotpy --no-index -f /io/wheelhouse 41 | (cd "$HOME"; INSTDIR=$("${PYBIN}/python" -c "import plotpy, os.path as osp; print(osp.dirname(plotpy.__file__))"); export QT_QPA_PLATFORM=offscreen; "${PYBIN}/pytest" "$INSTDIR") 42 | fi 43 | done 44 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_cursor_tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import numpy as np 6 | import pytest 7 | from guidata.qthelpers import exec_dialog, qt_app_context 8 | 9 | from plotpy.tests.unit.utils import ( 10 | create_window, 11 | drag_mouse, 12 | ) 13 | from plotpy.tools import ( 14 | HCursorTool, 15 | HRangeTool, 16 | VCursorTool, 17 | XCursorTool, 18 | ) 19 | 20 | if TYPE_CHECKING: 21 | from plotpy.tools.cursor import BaseCursorTool 22 | 23 | 24 | @pytest.mark.parametrize( 25 | "cursor_tool", [HCursorTool, VCursorTool, XCursorTool, HRangeTool] 26 | ) 27 | def test_cursor_tool(cursor_tool: type[BaseCursorTool]): 28 | """Test the cursor tools. by simulating a mouse drag and checking if the tool 29 | shape is created. 30 | 31 | Args: 32 | cursor_tool: Cursor tool class to test. 33 | """ 34 | with qt_app_context(): 35 | win, _tool = create_window(cursor_tool) 36 | win.show() 37 | plot = win.manager.get_plot() 38 | 39 | active_tool = win.manager.get_active_tool() 40 | assert isinstance(active_tool, cursor_tool) 41 | tool_shape_type = type(active_tool.create_shape()) 42 | assert tool_shape_type not in (type(item) for item in plot.get_items()) 43 | 44 | drag_mouse(win, np.array([0.5, 0.6, 0.7]), np.array([0.5, 0.6, 0.7])) 45 | assert tool_shape_type in (type(item) for item in plot.get_items()) 46 | 47 | exec_dialog(win) 48 | 49 | 50 | if __name__ == "__main__": 51 | for tool in (HCursorTool, VCursorTool, XCursorTool, HRangeTool): 52 | test_cursor_tool(tool) 53 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_actiontool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ActionTool test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import create_action, qt_app_context 11 | from qtpy import QtWidgets as QW 12 | 13 | from plotpy.builder import make 14 | from plotpy.items import ImageItem 15 | from plotpy.plot import PlotDialog, PlotOptions 16 | from plotpy.tests.data import gen_image4 17 | from plotpy.tools import ActionTool 18 | 19 | 20 | class MyPlotDialog(PlotDialog): 21 | def __init__(self): 22 | """Reimplement PlotDialog method""" 23 | super().__init__( 24 | title="ActionTool test", toolbar=True, options=PlotOptions(type="image") 25 | ) 26 | self.info_action = create_action(self, "Show info", triggered=self.show_info) 27 | self.manager.add_tool(ActionTool, self.info_action, item_types=(ImageItem,)) 28 | 29 | def show_info(self): 30 | """Show info on selected item(s)""" 31 | # This is just a demo of what can be done with ActionTool 32 | plot = self.get_plot() 33 | for item in plot.get_selected_items(): 34 | QW.QMessageBox.information(self, "Item info", str(item)) 35 | 36 | 37 | def test_image_plot_tools(): 38 | """Test""" 39 | with qt_app_context(exec_loop=True): 40 | win = MyPlotDialog() 41 | win.show() 42 | image = make.image(gen_image4(500, 500), colormap="Spectral") 43 | plot = win.manager.get_plot() 44 | plot.add_item(image) 45 | 46 | 47 | if __name__ == "__main__": 48 | test_image_plot_tools() 49 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_loadsaveitems_json.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is part of CodraFT Project 4 | # https://codra-ingenierie-informatique.github.io/CodraFT/ 5 | # 6 | # Licensed under the terms of the BSD 3-Clause or the CeCILL-B License 7 | # (see codraft/__init__.py for details) 8 | 9 | """ 10 | Unit test for plot items <--> JSON serialization/deserialization 11 | 12 | How to save/restore items to/from a JSON string? 13 | 14 | # Plot items --> JSON: 15 | writer = JSONWriter(None) 16 | save_items(writer, items) 17 | text = writer.get_json() 18 | 19 | # JSON --> Plot items: 20 | items = load_items(JSONReader(text)) 21 | 22 | """ 23 | 24 | # guitest: show 25 | 26 | # WARNING: 27 | # This script requires read/write permissions on current directory 28 | 29 | from __future__ import annotations 30 | 31 | from guidata.io import JSONReader, JSONWriter 32 | 33 | from plotpy.tests.features.test_loadsaveitems_pickle import IOTest 34 | 35 | 36 | class JSONTest(IOTest): 37 | """Class for JSON I/O testing""" 38 | 39 | FNAME = "loadsavecanvas.json" 40 | 41 | def restore_items(self) -> None: 42 | """Restore plot items""" 43 | self.plot.deserialize(JSONReader(self.FNAME)) 44 | 45 | def save_items(self) -> None: 46 | """Save plot items""" 47 | writer = JSONWriter(self.FNAME) 48 | self.plot.serialize(writer) 49 | writer.save() 50 | 51 | 52 | def test_loadsaveitems_json(): 53 | """Test load/save items from/to JSON file""" 54 | test = JSONTest("Load/save items from/to JSON file") 55 | test.run() 56 | 57 | 58 | if __name__ == "__main__": 59 | test_loadsaveitems_json() 60 | -------------------------------------------------------------------------------- /doc/features/plot/index.rst: -------------------------------------------------------------------------------- 1 | .. _plot: 2 | 3 | Plot widgets 4 | ============ 5 | 6 | A :mod:`plotpy`-based plotting widget may be constructed using one of the following 7 | methods: 8 | 9 | * *Interactive mode*: when manipulating and visualizing data in an interactive 10 | Python or IPython interpreter, the :py:mod`.pyplot` module provide 11 | the easiest way to plot curves, show images and more. Syntax is similar 12 | to MATLAB's, thus very easy to learn and to use interactively. 13 | 14 | * *Script mode*: when manipulating and visualizing data using a script, the 15 | :py:mod`.pyplot` module is still a good choice as long as you don't 16 | need to customize the figure graphical user interface (GUI) layout. 17 | However, if you want to add other widgets to the GUI, like menus, buttons 18 | and so on, you should rather use plotting widget classes instead of 19 | the `pyplot` helper functions. 20 | 21 | There are two kinds of plotting widgets defined in :mod:`plotpy`: 22 | 23 | * low-level plotting widget: :py:class:`.plot.base.BasePlot` 24 | 25 | * high-level plotting widgets (ready-to-use widgets with integrated tools 26 | and panels): :py:class:`.plot.PlotWidget` and corresponding dialog box 27 | :py:class:`.plot.PlotDialog` and window 28 | :py:class:`.plot.PlotWindow` 29 | 30 | Plot widgets with integrated plot manager: 31 | 32 | .. image:: ../../images/plot_widgets.png 33 | 34 | .. seealso:: 35 | 36 | :ref:`items` 37 | Plot items: curves, images, markers, etc. 38 | 39 | :ref:`plot` 40 | Ready-to-use curve and image plotting widgets and dialog boxes 41 | 42 | .. toctree:: 43 | :maxdepth: 2 44 | :caption: Contents: 45 | 46 | overview 47 | examples 48 | reference 49 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_image_superp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Image superposition test""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import get_path 15 | from plotpy.tools import EllipseTool, PlaceAxesTool, PolygonTool, RectangleTool 16 | 17 | 18 | def create_window(): 19 | gridparam = make.gridparam( 20 | background="black", minor_enabled=(False, False), major_style=(":", "gray", 1) 21 | ) 22 | win = make.dialog( 23 | toolbar=True, 24 | wintitle="Image superposition test", 25 | gridparam=gridparam, 26 | type="image", 27 | size=(800, 600), 28 | ) 29 | for toolklass in (RectangleTool, EllipseTool, PolygonTool, PlaceAxesTool): 30 | win.manager.add_tool(toolklass) 31 | return win 32 | 33 | 34 | def test_imagesuperp(): 35 | """Test image superposition""" 36 | filename = get_path("brain.png") 37 | with qt_app_context(exec_loop=True): 38 | win = create_window() 39 | image1 = make.image(filename=filename, title="Original", colormap="gray") 40 | data2 = np.array(image1.data[:, :150], copy=True) 41 | data2[:100] = data2[-100:] = 0 42 | image2 = make.image(data2, title="Modified", alpha_function="step") 43 | plot = win.manager.get_plot() 44 | plot.add_item(image1, z=0) 45 | plot.add_item(image2, z=1) 46 | plot.set_items_readonly(False) 47 | image2.set_readonly(True) 48 | win.manager.get_itemlist_panel().show() 49 | win.show() 50 | 51 | 52 | if __name__ == "__main__": 53 | test_imagesuperp() 54 | -------------------------------------------------------------------------------- /plotpy/tests/vistools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Visualisation tools for plotpy tests""" 7 | 8 | from __future__ import annotations 9 | 10 | from typing import TYPE_CHECKING 11 | 12 | from plotpy.builder import make 13 | 14 | if TYPE_CHECKING: 15 | from plotpy.items import BaseImageItem, CurveItem 16 | from plotpy.plot import PlotDialog 17 | 18 | 19 | def show_items( 20 | items: list[CurveItem | BaseImageItem], 21 | plot_type: str = "auto", 22 | wintitle: str = "Plot items", 23 | title: str = "Title", 24 | xlabel: str = "X", 25 | ylabel: str = "Y", 26 | auto_tools: bool = True, 27 | lock_aspect_ratio: bool | None = None, 28 | curve_antialiasing: bool | None = None, 29 | show_itemlist: bool = True, 30 | show_contrast: bool = False, 31 | winsize: tuple[int, int] | None = None, 32 | disable_readonly_for_items: bool = True, 33 | ) -> PlotDialog: 34 | """Show plot items in a dialog box""" 35 | winsize = (640, 480) if winsize is None else winsize 36 | win = make.dialog( 37 | edit=False, 38 | toolbar=True, 39 | wintitle=wintitle, 40 | title=title, 41 | xlabel=xlabel, 42 | ylabel=ylabel, 43 | type=plot_type, 44 | auto_tools=auto_tools, 45 | lock_aspect_ratio=lock_aspect_ratio, 46 | curve_antialiasing=curve_antialiasing, 47 | show_itemlist=show_itemlist, 48 | show_contrast=show_contrast, 49 | size=winsize, 50 | ) 51 | plot = win.manager.get_plot() 52 | for item in items: 53 | plot.add_item(item) 54 | if disable_readonly_for_items: 55 | plot.set_items_readonly(False) 56 | win.show() 57 | return win 58 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_fit_locked_params.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Test script for locked fit parameters feature""" 4 | 5 | import numpy as np 6 | 7 | from plotpy.widgets.fit import FitParam, guifit 8 | 9 | 10 | def test_locked_fit(): 11 | """Test the curve fitting tool with locked parameters""" 12 | # Generate test data: y = cos(1.5*x) + 0.2 13 | x = np.linspace(-10, 10, 1000) 14 | true_offset = 0.2 15 | true_freq = 1.5 16 | y = np.cos(true_freq * x) + true_offset + np.random.rand(x.shape[0]) * 0.1 17 | 18 | def fit(x, params): 19 | """Fit function: a + cos(b*x)""" 20 | a, b = params 21 | return np.cos(b * x) + a 22 | 23 | # Create fit parameters 24 | # Lock the frequency parameter at the true value 25 | a = FitParam("Offset", 0.0, -1.0, 1.0) 26 | b = FitParam("Frequency", true_freq, 0.3, 3.0, logscale=True, locked=True) 27 | 28 | params = [a, b] 29 | 30 | print("Initial values:") 31 | print(f" Offset (unlocked): {a.value}") 32 | print(f" Frequency (locked): {b.value}") 33 | 34 | values = guifit( 35 | x, 36 | y, 37 | fit, 38 | params, 39 | xlabel="Time (s)", 40 | ylabel="Amplitude (a.u.)", 41 | wintitle="Locked Parameter Test", 42 | auto_fit=True, 43 | ) 44 | 45 | if values: 46 | print("\nFinal values after fit:") 47 | print(f" Offset: {values[0]:.4f} (should be close to {true_offset})") 48 | print(f" Frequency: {values[1]:.4f} (should remain {true_freq})") 49 | print("\nNote: The frequency parameter was locked and should not have changed.") 50 | print("Only the offset parameter should have been optimized.") 51 | 52 | 53 | if __name__ == "__main__": 54 | test_locked_fit() 55 | -------------------------------------------------------------------------------- /plotpy/styles/polygonmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from guidata.dataset import DataSet, GetAttrProp, StringItem 8 | 9 | from plotpy.config import _ 10 | from plotpy.styles.base import ItemParameters 11 | 12 | if TYPE_CHECKING: 13 | from plotpy.items import PolygonMapItem 14 | 15 | 16 | class PolygonMapParam(DataSet): 17 | """Dataset defining the parameters of a PolygonMapItem instance""" 18 | 19 | _multiselection = False 20 | label = StringItem(_("Title"), default="").set_prop( 21 | "display", hide=GetAttrProp("_multiselection") 22 | ) 23 | 24 | def update_param(self, item: PolygonMapItem) -> None: 25 | """Updates the parameters using values from a given PolygonMapItem 26 | 27 | Args: 28 | item: reference PolygonMapItem instance 29 | """ 30 | self.label = str(item.title().text()) 31 | 32 | def update_item(self, item: PolygonMapItem) -> None: 33 | """Updates a given PolygonMapItem using the current parameters 34 | 35 | Args: 36 | item: instance of PolygonMapItem to update 37 | """ 38 | plot = item.plot() 39 | if plot is not None: 40 | plot.blockSignals(True) # Avoid unwanted calls of update_param 41 | # triggered by the setter methods below 42 | if not self._multiselection: 43 | # Non common parameters 44 | item.setTitle(self.label) 45 | if plot is not None: 46 | plot.blockSignals(False) 47 | 48 | 49 | class PolygonParam_MS(PolygonMapParam): 50 | """Same as PolygonParam but for multiselection""" 51 | 52 | _multiselection = True 53 | 54 | 55 | ItemParameters.register_multiselection(PolygonMapParam, PolygonParam_MS) 56 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to :mod:`plotpy`'s documentation! 2 | ========================================= 3 | 4 | .. image:: images/plotpy-banner.png 5 | :align: center 6 | 7 | :mod:`plotpy` is is a Python library providing efficient 2D data-plotting 8 | features for interactive computing and signal/image processing application 9 | development. 10 | 11 | :mod:`plotpy` is part of the `PlotPyStack`_ project, which aims at providing 12 | a full set of Python libraries for data plotting and data analysis. 13 | 14 | :mod:`plotpy` is based on: 15 | 16 | * `Python`_ language and `Qt`_ GUI toolkit (via `PySide`_ or `PyQt`_) 17 | * `guidata`_ automatic GUI generation library (`PlotPyStack`_ project) 18 | * `PythonQwt`_ plotting widgets library (`PlotPyStack`_ project) 19 | * `NumPy`_ and `SciPy`_ scientific computing libraries 20 | 21 | .. figure:: images/panorama.png 22 | 23 | A panorama of :mod:`plotpy`'s features. 24 | 25 | External resources: 26 | 27 | * Python Package Index: `PyPI`_ 28 | * Bug reports and feature requests: `GitHub`_ 29 | 30 | .. _Python: http://www.python.org 31 | .. _Qt: https://doc.qt.io/ 32 | .. _PySide: https://doc.qt.io/qtforpython-6/ 33 | .. _PyQt: http://www.riverbankcomputing.co.uk/software/pyqt/intro 34 | .. _PyPI: https://pypi.python.org/pypi/plotpy 35 | .. _GitHub: https://github.com/PlotPyStack/plotpy 36 | .. _guidata: https://pypi.python.org/pypi/guidata 37 | .. _PythonQwt: https://pypi.python.org/pypi/PythonQwt 38 | .. _NumPy: https://pypi.python.org/pypi/NumPy 39 | .. _SciPy: https://pypi.python.org/pypi/SciPy 40 | .. _PlotPyStack: https://github.com/PlotPyStack 41 | 42 | .. module:: plotpy 43 | 44 | Table of contents 45 | ----------------- 46 | 47 | .. toctree:: 48 | :maxdepth: 2 49 | 50 | intro/index 51 | features/index 52 | dev/index 53 | release_notes/index 54 | 55 | * :ref:`genindex` 56 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_builder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Builder tests""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import data as ptd 15 | from plotpy.tests import vistools as ptv 16 | 17 | 18 | def test_builder(): 19 | """Testing plot builder""" 20 | data = ptd.gen_image1() 21 | show = ptv.show_items 22 | with qt_app_context(exec_loop=True): 23 | _win0 = show([make.image(data, colormap="pink")], wintitle="Pink colormap test") 24 | _win1 = show([make.image(data)], wintitle="Default LUT range") 25 | img1 = make.image(data) 26 | img1.set_lut_range([0, 1]) 27 | _win2 = show([img1], wintitle="0->1 LUT range through item") 28 | img2 = make.image(data, lut_range=[0, 1]) 29 | _win3 = show([img2], wintitle="0->1 LUT range through builder") 30 | x = np.linspace(1, 10, 200) 31 | y = np.sin(x) 32 | 33 | crv1 = make.curve(x, y, dx=x * 0.1, dy=y * 0.23) 34 | crv2 = make.curve(x, np.cos(x), color="#FF0000") 35 | _win4 = show([crv1, crv2], wintitle="Error bars with make.curve()") 36 | 37 | dat22 = np.zeros((2, 2), np.float32) 38 | dat22[0, 0] = 1 39 | dat22[0, 1] = 2 40 | dat22[1, 0] = 3 41 | dat22[1, 1] = 4 42 | img3 = make.image(dat22, xdata=[-1, 3], ydata=[-1, 3], interpolation="nearest") 43 | _win5 = show([img3], wintitle="2x2 image") 44 | 45 | img4 = make.image(dat22, x=[0, 2], y=[0, 2], interpolation="nearest") 46 | _win6 = show([img4], wintitle="Equivalent 2x2 XY image") 47 | 48 | 49 | if __name__ == "__main__": 50 | test_builder() 51 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_curves_highdpi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Curve plotting test with high DPI""" 7 | 8 | # guitest: show,skip 9 | 10 | import os 11 | 12 | import numpy as np 13 | import pytest 14 | from guidata.qthelpers import qt_app_context 15 | 16 | from plotpy.builder import make 17 | from plotpy.tests import vistools as ptv 18 | 19 | 20 | @pytest.mark.skip(reason="This test is not relevant for the automated test suite") 21 | def test_plot_highdpi(): 22 | """Curve plotting test with high DPI""" 23 | # When setting the QT_SCALE_FACTOR to "2", performance is degraded, due to the 24 | # increased number of points to be drawn. As a workaround, we use the downsampling 25 | # feature to reduce the number of points to be drawn. 26 | # We also enable the antialiasing feature to improve the rendering of the curves, 27 | # which is also affected by the high DPI setting. 28 | # See https://github.com/PlotPyStack/PlotPy/issues/10 29 | os.environ["QT_SCALE_FACTOR"] = "2" 30 | 31 | npoints = 5000000 # 5M points are needed to see the difference 32 | dsamp_factor = npoints // 50000 # Max 50000 points to be drawn 33 | x = np.linspace(-10, 10, npoints) 34 | y = np.sin(np.sin(np.sin(x))) 35 | with qt_app_context(exec_loop=True): 36 | _win = ptv.show_items( 37 | [ 38 | make.curve(x, y, color="b", dsamp_factor=dsamp_factor, use_dsamp=True), 39 | make.legend("TR"), 40 | ], 41 | curve_antialiasing=True, 42 | wintitle=test_plot_highdpi.__doc__, 43 | title="Curves with high DPI", 44 | plot_type="curve", 45 | ) 46 | 47 | 48 | if __name__ == "__main__": 49 | test_plot_highdpi() 50 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_curves.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Curve plotting test""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | 13 | from plotpy.builder import make 14 | from plotpy.tests import vistools as ptv 15 | 16 | 17 | def test_plot(): 18 | """Curve plotting test""" 19 | x = np.linspace(-10, 10, 200) 20 | dy = x / 100.0 21 | y = np.sin(np.sin(np.sin(x))) 22 | x2 = np.linspace(-10, 10, 20) 23 | y2 = np.sin(np.sin(np.sin(x2))) 24 | with qt_app_context(exec_loop=True): 25 | items = [ 26 | make.curve(x, y, color="b"), 27 | curve1 := make.curve(x2, y2, color="g", title="Readonly"), 28 | curve2 := make.curve(x, np.sin(2 * y), color="r", title="Private"), 29 | make.merror(x, y / 2, dy), 30 | make.label( 31 | "Relative position outside", (x[0], y[0]), (-10, -10), "BR" 32 | ), 33 | make.label("Relative position inside", (x[0], y[0]), (10, 10), "TL"), 34 | make.label("Absolute position", "R", (0, 0), "R"), 35 | make.legend("TR"), 36 | make.marker( 37 | position=(5.0, 0.8), 38 | label_cb=lambda x, y: "A = %.2f" % x, 39 | markerstyle="|", 40 | movable=False, 41 | ), 42 | ] 43 | curve1.set_readonly(True) 44 | curve2.set_private(True) 45 | _win = ptv.show_items( 46 | items, 47 | wintitle=test_plot.__doc__, 48 | title="Curves", 49 | plot_type="curve", 50 | disable_readonly_for_items=False, 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | test_plot() 56 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_zaxislog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Z-axis log scale tool test""" 7 | 8 | # guitest: show 9 | 10 | from __future__ import annotations 11 | 12 | import numpy as np 13 | from guidata.qthelpers import qt_app_context, qt_wait 14 | 15 | from plotpy.builder import make 16 | from plotpy.plot import PlotDialog, PlotOptions 17 | from plotpy.tests.data import gen_2d_gaussian 18 | from plotpy.tools import ZAxisLogTool 19 | 20 | 21 | class MyPlotDialog(PlotDialog): 22 | def __init__(self) -> None: 23 | """Reimplement PlotDialog method""" 24 | super().__init__( 25 | title="Z-axis log scale tool test", 26 | toolbar=True, 27 | options=PlotOptions(type="image"), 28 | ) 29 | # No need to add the tools to the manager, they are automatically added 30 | # when the `register_curve_tools` or `register_image_tools` method is called 31 | self.setup_items() 32 | 33 | def setup_items(self) -> None: 34 | """Setup items""" 35 | plot = self.get_plot() 36 | data = gen_2d_gaussian(512, np.uint16) 37 | item = make.image(data, title="Image", colormap="plasma") 38 | plot.add_item(item) 39 | plot.set_active_item(item) 40 | item.unselect() 41 | 42 | 43 | def test_zaxislogtool() -> None: 44 | """Test the Z-axis log scale tool""" 45 | with qt_app_context(exec_loop=True): 46 | win = MyPlotDialog() 47 | win.show() 48 | tool = win.manager.get_tool(ZAxisLogTool) 49 | qt_wait(1, except_unattended=True) 50 | for _index in range(2): 51 | tool.activate() 52 | qt_wait(1, except_unattended=True) 53 | 54 | 55 | if __name__ == "__main__": 56 | test_zaxislogtool() 57 | -------------------------------------------------------------------------------- /doc/features/signals.rst: -------------------------------------------------------------------------------- 1 | Qt signals 2 | ---------- 3 | 4 | In `guiqwt` version 2, the `signals` module used to contain constants defining 5 | the custom Qt ``SIGNAL`` objects used by `guiqwt`: the signals definition were 6 | gathered there to avoid misspelling signals at connect and emit sites (with 7 | old-style signals, any misspelled signal string would have lead to a silent 8 | failure of signal emission or connection). 9 | 10 | Since version 3, to ensure PyQt5 compatibility, `guiqwt` and thus plotpy are using 11 | only new-style signals and slots. 12 | 13 | All signals are summarized below, in order to facilitate migration 14 | from `guiqwt` v2 to v3, then to plotpy. 15 | 16 | Signals emitted by :py:class:`.BasePlot` objects: 17 | 18 | - :py:attr:`.BasePlot.SIG_ITEM_MOVED` 19 | - :py:attr:`.BasePlot.SIG_ITEM_HANDLE_MOVED` 20 | - :py:attr:`.BasePlot.SIG_ITEM_RESIZED` 21 | - :py:attr:`.BasePlot.SIG_ITEM_ROTATED` 22 | - :py:attr:`.BasePlot.SIG_MARKER_CHANGED` 23 | - :py:attr:`.BasePlot.SIG_AXES_CHANGED` 24 | - :py:attr:`.BasePlot.SIG_ANNOTATION_CHANGED` 25 | - :py:attr:`.BasePlot.SIG_RANGE_CHANGED` 26 | - :py:attr:`.BasePlot.SIG_ITEMS_CHANGED` 27 | - :py:attr:`.BasePlot.SIG_ITEM_PARAMETERS_CHANGED` 28 | - :py:attr:`.BasePlot.SIG_ACTIVE_ITEM_CHANGED` 29 | - :py:attr:`.BasePlot.SIG_ITEM_REMOVED` 30 | - :py:attr:`.BasePlot.SIG_ITEM_SELECTION_CHANGED` 31 | - :py:attr:`.BasePlot.SIG_PLOT_LABELS_CHANGED` 32 | - :py:attr:`.BasePlot.SIG_AXIS_DIRECTION_CHANGED` 33 | - :py:attr:`.BasePlot.SIG_LUT_CHANGED` 34 | - :py:attr:`.BasePlot.SIG_MASK_CHANGED` 35 | - :py:attr:`.BasePlot.SIG_CS_CURVE_CHANGED` 36 | 37 | Signals emitted by other objects: 38 | 39 | - :py:attr:`.PanelWidget.SIG_VISIBILITY_CHANGED` 40 | - :py:attr:`.InteractiveTool.SIG_VALIDATE_TOOL` 41 | - :py:attr:`.InteractiveTool.SIG_TOOL_JOB_FINISHED` 42 | - :py:attr:`.OpenFileTool.SIG_OPEN_FILE` 43 | - :py:attr:`.ImageMaskTool.SIG_APPLIED_MASK_TOOL` 44 | -------------------------------------------------------------------------------- /plotpy/tests/widgets/test_fliprotate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Flip/rotate test""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import exec_dialog, qt_app_context 11 | from qtpy import QtWidgets as QW 12 | 13 | from plotpy.tests.widgets.test_rotatecrop import create_test_data, imshow 14 | from plotpy.tools import RotationCenterTool 15 | from plotpy.widgets.fliprotate import FlipRotateDialog, FlipRotateWidget 16 | 17 | 18 | def widget_test(fname): 19 | """Test the flip/rotate widget""" 20 | array0, item = create_test_data(fname) 21 | widget = FlipRotateWidget(None, toolbar=True) 22 | widget.transform.set_item(item) 23 | widget.set_parameters(-90, True, False) 24 | widget.show() 25 | return widget 26 | 27 | 28 | def dialog_test(fname): 29 | """Test the flip/rotate dialog with rotation point changeable""" 30 | array0, item = create_test_data(fname) 31 | dlg = FlipRotateDialog(None, toolbar=True) 32 | dlg.manager.add_tool( 33 | RotationCenterTool, 34 | rotation_center=False, 35 | rotation_point_move_with_shape=True, 36 | on_all_items=True, 37 | ) 38 | dlg.transform.set_item(item) 39 | if exec_dialog(dlg) == QW.QDialog.Accepted: 40 | array1 = dlg.transform.get_result() 41 | dlg1 = imshow(array0, title="array0") 42 | dlg2 = imshow(array1, title="array1") 43 | return dlg, dlg1, dlg2 44 | 45 | 46 | def test_flip_rotate(): 47 | """Test the flip/rotate widget and dialog""" 48 | persist_list = [] 49 | with qt_app_context(exec_loop=True): 50 | persist_list.append(widget_test("brain.png")) 51 | with qt_app_context(exec_loop=False): 52 | persist_list.append(dialog_test("brain.png")) 53 | 54 | 55 | if __name__ == "__main__": 56 | test_flip_rotate() 57 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Run Test Launcher", 9 | "type": "debugpy", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/plotpy/tests/__init__.py", 12 | "console": "integratedTerminal", 13 | "envFile": "${workspaceFolder}/.env", 14 | "justMyCode": true, 15 | "env": { 16 | "QT_COLOR_MODE": "light", 17 | } 18 | }, 19 | { 20 | "name": "Run current file", 21 | "type": "debugpy", 22 | "request": "launch", 23 | "program": "${file}", 24 | "console": "integratedTerminal", 25 | "envFile": "${workspaceFolder}/.env", 26 | "justMyCode": false, 27 | "pythonArgs": [ 28 | "-W error::DeprecationWarning", 29 | "-W error::RuntimeWarning", 30 | ], 31 | "env": {} 32 | }, 33 | { 34 | "name": "Run current file (unattended)", 35 | "type": "debugpy", 36 | "request": "launch", 37 | "program": "${file}", 38 | "console": "integratedTerminal", 39 | "envFile": "${workspaceFolder}/.env", 40 | "pythonArgs": [ 41 | "-W error::DeprecationWarning", 42 | ], 43 | "justMyCode": false, 44 | "args": [ 45 | "--unattended" 46 | ], 47 | "env": { 48 | "GUIDATA_PARSE_ARGS": "1", 49 | } 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /plotpy/items/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=unused-import 4 | # flake8: noqa 5 | 6 | from .annotation import ( 7 | AnnotatedCircle, 8 | AnnotatedEllipse, 9 | AnnotatedObliqueRectangle, 10 | AnnotatedPoint, 11 | AnnotatedRectangle, 12 | AnnotatedPolygon, 13 | AnnotatedSegment, 14 | AnnotatedXRange, 15 | AnnotatedYRange, 16 | AnnotatedShape, 17 | ) 18 | from .contour import ContourItem, create_contour_items 19 | from .curve import CurveItem, ErrorBarCurveItem 20 | from .grid import GridItem 21 | from .histogram import HistogramItem 22 | from .image import ( 23 | BaseImageItem, 24 | Histogram2DItem, 25 | ImageFilterItem, 26 | ImageItem, 27 | MaskedImageItem, 28 | MaskedXYImageItem, 29 | QuadGridItem, 30 | RawImageItem, 31 | RGBImageItem, 32 | TrImageItem, 33 | XYImageFilterItem, 34 | XYImageItem, 35 | assemble_imageitems, 36 | compute_trimageitems_original_size, 37 | get_image_from_plot, 38 | get_image_from_qrect, 39 | get_image_in_shape, 40 | get_items_in_rectangle, 41 | get_plot_qrect, 42 | ) 43 | from .image.masked import MaskedArea, MaskedImageItem, MaskedXYImageItem 44 | from .label import ( 45 | AbstractLabelItem, 46 | DataInfoLabel, 47 | LabelItem, 48 | LegendBoxItem, 49 | ObjectInfo, 50 | RangeComputation, 51 | XRangeComputation, 52 | YRangeComputation, 53 | RangeComputation2d, 54 | RangeInfo, 55 | SelectedLegendBoxItem, 56 | ) 57 | from .polygonmap import PolygonMapItem 58 | from .shape import ( 59 | AbstractShape, 60 | Axes, 61 | CircleSVGShape, 62 | EllipseShape, 63 | Marker, 64 | ObliqueRectangleShape, 65 | PointShape, 66 | PolygonShape, 67 | RectangleShape, 68 | RectangleSVGShape, 69 | SegmentShape, 70 | SquareSVGShape, 71 | XRangeSelection, 72 | YRangeSelection, 73 | ) 74 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_image_filter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Image filter demo""" 7 | 8 | # guitest: show 9 | 10 | import numpy as np 11 | from guidata.qthelpers import qt_app_context 12 | from scipy.ndimage import gaussian_filter 13 | 14 | from plotpy import io 15 | from plotpy.builder import make 16 | from plotpy.tests import data as ptd 17 | from plotpy.tests import get_path 18 | 19 | 20 | def imshow(x, y, data, filter_area, yreverse=True): 21 | with qt_app_context(exec_loop=True): 22 | win = make.dialog( 23 | edit=False, 24 | toolbar=True, 25 | wintitle="Image filter demo", 26 | xlabel="x (cm)", 27 | ylabel="y (cm)", 28 | yreverse=yreverse, 29 | type="image", 30 | size=(800, 600), 31 | ) 32 | image = make.xyimage(x, y, data) 33 | plot = win.manager.get_plot() 34 | plot.add_item(image) 35 | xmin, xmax, ymin, ymax = filter_area 36 | 37 | def ifilter(x, y, data): 38 | """Image filter function""" 39 | return gaussian_filter(data, 5) 40 | 41 | flt = make.imagefilter(xmin, xmax, ymin, ymax, image, filter=ifilter) 42 | plot.add_item(flt, z=1) 43 | plot.replot() 44 | win.show() 45 | 46 | 47 | def test_imagefilter(): 48 | """Test image filter""" 49 | x, y, data = ptd.gen_xyimage() 50 | imshow(x, y, data, filter_area=(-3.0, -1.0, 0.0, 2.0), yreverse=False) 51 | 52 | filename = get_path("brain.png") 53 | data = io.imread(filename, to_grayscale=True) 54 | x = np.linspace(0, 30.0, data.shape[1]) 55 | y = np.linspace(0, 30.0, data.shape[0]) 56 | imshow(x, y, data, filter_area=(10.0, 20.0, 5.0, 15.0)) 57 | 58 | 59 | if __name__ == "__main__": 60 | test_imagefilter() 61 | -------------------------------------------------------------------------------- /scripts/run_with_env.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. 2 | 3 | """Run a command with environment variables loaded from a .env file.""" 4 | 5 | from __future__ import annotations 6 | 7 | import os 8 | import subprocess 9 | import sys 10 | from pathlib import Path 11 | 12 | 13 | def load_env_file(env_path: str | None = None) -> None: 14 | """Load environment variables from a .env file.""" 15 | if env_path is None: 16 | # Get ".eenv" file from the current directory 17 | env_path = Path.cwd() / ".env" 18 | if not Path(env_path).is_file(): 19 | raise FileNotFoundError(f"Environment file not found: {env_path}") 20 | print(f"Loading environment variables from: {env_path}") 21 | with open(env_path, encoding="utf-8") as f: 22 | for line in f: 23 | line = line.strip() 24 | if not line or line.startswith("#") or "=" not in line: 25 | continue 26 | key, value = line.split("=", 1) 27 | os.environ[key.strip()] = value.strip() 28 | print(f" Loaded variable: {key.strip()}={value.strip()}") 29 | 30 | 31 | def execute_command(command: list[str]) -> int: 32 | """Execute a command with the loaded environment variables.""" 33 | print("Executing command:") 34 | print(" ".join(command)) 35 | print("") 36 | result = subprocess.call(command) 37 | print(f"Process exited with code {result}") 38 | return result 39 | 40 | 41 | def main() -> None: 42 | """Main function to load environment variables and execute a command.""" 43 | if len(sys.argv) < 2: 44 | print("Usage: python run_with_env.py [args ...]") 45 | sys.exit(1) 46 | print("🏃 Running with environment variables") 47 | load_env_file() 48 | return execute_command(sys.argv[1:]) 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_line.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Internal test related to pcolor feature""" 7 | 8 | import numpy as np 9 | import pytest 10 | 11 | from plotpy._scaler import _line_test as line 12 | 13 | N = 10 14 | TEST_LINE_DATA = [(0, 0, 9, 9), (9, 9, 0, 0), (0, 5, 0, 9), (2, 5, 7, 5), (0, 1, 2, 9)] 15 | TEST_TRI_DATA = [(0, 1, 2, 9, 8, 7)] 16 | 17 | 18 | def print_tri(imin, imax): 19 | """Print triangle""" 20 | for i in range(N): 21 | for j in range(N): 22 | if j < imin[i] or j > imax[i]: 23 | print(".", end=" ") 24 | else: 25 | print("*", end=" ") 26 | print() 27 | 28 | 29 | @pytest.mark.parametrize("x0,y0,x1,y1", TEST_LINE_DATA) 30 | def test_line(x0, y0, x1, y1): 31 | """Test line""" 32 | print(x0, ",", y0, "->", x1, ",", y1) 33 | imin = np.full((N,), N, dtype=np.int32) 34 | imax = np.full((N,), 0, dtype=np.int32) 35 | print("imin:", repr(imin), imin.dtype) 36 | print("imax:", repr(imax), imax.dtype) 37 | line(x0, y0, x1, y1, N, imin, imax) 38 | print_tri(imin, imax) 39 | 40 | 41 | @pytest.mark.parametrize("x0,y0,x1,y1,x2,y2", TEST_TRI_DATA) 42 | def test_tri(x0, y0, x1, y1, x2, y2): 43 | """Test triangle""" 44 | print(x0, ",", y0, "->", x1, ",", y1) 45 | imin = np.full((N,), N, dtype=np.int32) 46 | imax = np.full((N,), 0, dtype=np.int32) 47 | imin[:] = N + 1 48 | imax[:] = -1 49 | line(x0, y0, x1, y1, N, imin, imax) 50 | line(x0, y0, x2, y2, N, imin, imax) 51 | line(x1, y1, x2, y2, N, imin, imax) 52 | print_tri(imin, imax) 53 | 54 | 55 | if __name__ == "__main__": 56 | test_tri(0, 1, 2, 9, 8, 7) 57 | test_line(0, 0, 9, 9) 58 | test_line(9, 9, 0, 0) 59 | test_line(0, 5, 0, 9) 60 | test_line(2, 5, 7, 5) 61 | test_line(0, 1, 2, 9) 62 | -------------------------------------------------------------------------------- /plotpy/tests/data/svg_tool.svg: -------------------------------------------------------------------------------- 1 | 2 | 56 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_downsample_curve.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | DownSampleCurveTool test 8 | 9 | This plotpy tool provides a toggle to downsample the current curve with a given factor. 10 | """ 11 | 12 | # guitest: show 13 | 14 | from __future__ import annotations 15 | 16 | from guidata.qthelpers import exec_dialog, qt_app_context 17 | from numpy import linspace, sin 18 | 19 | from plotpy.builder import make 20 | from plotpy.config import _ 21 | 22 | 23 | def edit_downsampled_curve( 24 | cdata: tuple[tuple[float, float], ...], dsamp_factor: int 25 | ) -> None: 26 | """ 27 | Plot curves and return selected point(s) coordinates 28 | 29 | Args: 30 | cdata: tuple of curves to plot 31 | dsamp_factor: downsampling factor 32 | """ 33 | win = make.dialog( 34 | wintitle=_("Right-click on the curve to enable/disable downsampling"), 35 | edit=True, 36 | type="curve", 37 | size=(800, 600), 38 | ) 39 | plot = win.manager.get_plot() 40 | for cx, cy in cdata[:-1]: 41 | item = make.mcurve(cx, cy) 42 | plot.add_item(item) 43 | item = make.mcurve(*cdata[-1], "r-+", dsamp_factor=dsamp_factor, use_dsamp=True) 44 | plot.add_item(item) 45 | plot.set_active_item(item) 46 | plot.unselect_item(item) 47 | exec_dialog(win) 48 | 49 | 50 | def test_edit_curve(): 51 | """Test""" 52 | with qt_app_context(exec_loop=False): 53 | nlines = 1000 54 | x = linspace(-10, 10, num=nlines) 55 | y = 0.25 * sin(sin(sin(x * 0.5))) 56 | x2 = linspace(-10, 10, num=nlines) 57 | y2 = sin(sin(sin(x2))) 58 | cdata = ((x, y), (x2, y2), (x, sin(2 * y))) 59 | for dsamp_factor in (10, 50): 60 | edit_downsampled_curve(cdata, dsamp_factor) 61 | 62 | 63 | if __name__ == "__main__": 64 | test_edit_curve() 65 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_get_rectangle_with_svg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # For licensing and distribution details, please read carefully xgrid/__init__.py 4 | 5 | """ 6 | Get rectangular selection from image with SVG shape 7 | """ 8 | 9 | # guitest: show 10 | 11 | from guidata.env import execenv 12 | from guidata.qthelpers import qt_app_context 13 | 14 | from plotpy.builder import make 15 | from plotpy.tests import get_path 16 | from plotpy.tests.data import gen_image4 17 | from plotpy.tests.tools.test_get_segment import SEG_AXES_COORDS, PatchedSelectDialog 18 | from plotpy.tools import RectangularShapeTool 19 | from plotpy.widgets.selectdialog import select_with_shape_tool 20 | 21 | 22 | class SVGToolExample(RectangularShapeTool): 23 | """Tool to select a rectangular area and create a pattern from it""" 24 | 25 | TITLE = "Pattern selection tool" 26 | ICON = "your_icon.svg" 27 | AVOID_NULL_SHAPE = True 28 | SVG_FNAME = get_path("svg_tool.svg") 29 | 30 | def create_shape(self): 31 | """Create shape to be drawn""" 32 | with open(self.SVG_FNAME, "rb") as svg_file: 33 | svg_data = svg_file.read() 34 | shape = make.svg("rectangle", svg_data, 0, 0, 1, 1, "SVG") 35 | self.set_shape_style(shape) 36 | return shape, 0, 2 37 | 38 | 39 | def test_get_rectangle_with_svg(): 40 | """Test get_rectangle_with_svg""" 41 | with qt_app_context(): 42 | image = make.image(data=gen_image4(200, 200), colormap="gray") 43 | shape = select_with_shape_tool( 44 | None, SVGToolExample, image, "Test", tooldialogclass=PatchedSelectDialog 45 | ) 46 | if shape is not None: 47 | rect = shape.get_rect() 48 | if execenv.unattended: 49 | assert [round(i) for i in list(rect)] == SEG_AXES_COORDS 50 | elif rect is not None: 51 | print("Area:", rect) 52 | 53 | 54 | if __name__ == "__main__": 55 | test_get_rectangle_with_svg() 56 | -------------------------------------------------------------------------------- /plotpy/mathutils/arrayfuncs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Array functions 5 | --------------- 6 | 7 | Overview 8 | ^^^^^^^^ 9 | 10 | The :py:mod:`.arrayfuncs` module provides miscellaneous array functions. 11 | 12 | The following functions are available: 13 | 14 | * :py:func:`.get_nan_min` 15 | * :py:func:`.get_nan_max` 16 | * :py:func:`.get_nan_range` 17 | 18 | Reference 19 | ^^^^^^^^^ 20 | 21 | .. autofunction:: get_nan_min 22 | .. autofunction:: get_nan_max 23 | .. autofunction:: get_nan_range 24 | """ 25 | 26 | from __future__ import annotations 27 | 28 | import numpy as np 29 | 30 | 31 | def get_nan_min(data: np.ndarray | np.ma.MaskedArray) -> float: 32 | """Return minimum value of data, ignoring NaNs 33 | 34 | Args: 35 | data: Data array (or masked array) 36 | 37 | Returns: 38 | float: Minimum value of data, ignoring NaNs 39 | """ 40 | if isinstance(data, np.ma.MaskedArray): 41 | data = data.data 42 | if data.dtype.name in ("float32", "float64", "float128"): 43 | return np.nanmin(data) 44 | else: 45 | return data.min() 46 | 47 | 48 | def get_nan_max(data: np.ndarray | np.ma.MaskedArray) -> float: 49 | """Return maximum value of data, ignoring NaNs 50 | 51 | Args: 52 | data: Data array (or masked array) 53 | 54 | Returns: 55 | float: Maximum value of data, ignoring NaNs 56 | """ 57 | if isinstance(data, np.ma.MaskedArray): 58 | data = data.data 59 | if data.dtype.name in ("float32", "float64", "float128"): 60 | return np.nanmax(data) 61 | else: 62 | return data.max() 63 | 64 | 65 | def get_nan_range(data: np.ndarray | np.ma.MaskedArray) -> tuple[float, float]: 66 | """Return range of data, i.e. (min, max), ignoring NaNs 67 | 68 | Args: 69 | data: Data array (or masked array) 70 | 71 | Returns: 72 | tuple: Minimum and maximum value of data, ignoring NaNs 73 | """ 74 | return get_nan_min(data), get_nan_max(data) 75 | -------------------------------------------------------------------------------- /plotpy/styles/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # pylint: disable=W0611 4 | # flake8: noqa 5 | 6 | from plotpy.styles.axes import ( 7 | AxesParam, 8 | AxeStyleParam, 9 | AxisItem, 10 | AxisItemWidget, 11 | AxisParam, 12 | ImageAxesParam, 13 | ) 14 | from plotpy.styles.base import ( 15 | COLORS, 16 | LINESTYLES, 17 | MARKERS, 18 | BrushStyleItem, 19 | BrushStyleItemWidget, 20 | BrushStyleParam, 21 | FontItem, 22 | FontItemWidget, 23 | FontParam, 24 | GridParam, 25 | ItemParameters, 26 | LineStyleItem, 27 | LineStyleItemWidget, 28 | LineStyleParam, 29 | SymbolItem, 30 | SymbolItemWidget, 31 | SymbolParam, 32 | TextStyleItem, 33 | TextStyleItemWidget, 34 | TextStyleParam, 35 | style_generator, 36 | update_style_attr, 37 | ) 38 | from plotpy.styles.curve import CurveParam, CurveParam_MS 39 | from plotpy.styles.errorbar import ErrorBarParam 40 | from plotpy.styles.histogram import ( 41 | Histogram2DParam, 42 | Histogram2DParam_MS, 43 | HistogramParam, 44 | ) 45 | from plotpy.styles.image import ( 46 | BaseImageParam, 47 | ImageFilterParam, 48 | ImageParam, 49 | ImageParam_MS, 50 | MaskedImageParam, 51 | MaskedImageParam_MS, 52 | MaskedImageParamMixin, 53 | MaskedXYImageParam, 54 | MaskedXYImageParam_MS, 55 | QuadGridParam, 56 | RawImageParam, 57 | RawImageParam_MS, 58 | RGBImageParam, 59 | TrImageParam, 60 | TrImageParam_MS, 61 | XYImageParam, 62 | XYImageParam_MS, 63 | ) 64 | from plotpy.styles.label import ( 65 | LabelParam, 66 | LabelParam_MS, 67 | LabelParamWithContents, 68 | LabelParamWithContents_MS, 69 | LegendParam, 70 | LegendParam_MS, 71 | ) 72 | from plotpy.styles.polygonmap import PolygonMapParam, PolygonParam_MS 73 | from plotpy.styles.shape import ( 74 | AnnotationParam, 75 | AnnotationParam_MS, 76 | AxesShapeParam, 77 | MarkerParam, 78 | RangeShapeParam, 79 | ShapeParam, 80 | ) 81 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_get_point.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | SelectPointTool test 8 | 9 | This plotpy tool provide a MATLAB-like "ginput" feature. 10 | """ 11 | 12 | # guitest: show 13 | 14 | from __future__ import annotations 15 | 16 | from guidata.qthelpers import exec_dialog, qt_app_context 17 | from numpy import linspace, sin 18 | 19 | from plotpy.builder import make 20 | from plotpy.config import _ 21 | from plotpy.tools import SelectPointTool 22 | 23 | 24 | def callback_function(tool: SelectPointTool) -> None: 25 | print("Current coordinates:", tool.get_coordinates()) 26 | 27 | 28 | def get_point(cdata: tuple[tuple[float, float], ...]) -> None: 29 | """ 30 | Plot curves and return selected point(s) coordinates 31 | 32 | Args: 33 | cdata: A tuple of curves to plot 34 | """ 35 | win = make.dialog( 36 | wintitle=_("Select one point then press OK to accept"), 37 | edit=True, 38 | type="curve", 39 | size=(800, 600), 40 | ) 41 | default = win.manager.add_tool( 42 | SelectPointTool, 43 | title="Test", 44 | on_active_item=True, 45 | mode="reuse", 46 | end_callback=callback_function, 47 | ) 48 | default.activate() 49 | plot = win.manager.get_plot() 50 | for cx, cy in cdata[:-1]: 51 | item = make.mcurve(cx, cy) 52 | plot.add_item(item) 53 | item = make.mcurve(*cdata[-1], "r-+") 54 | plot.add_item(item) 55 | plot.set_active_item(item) 56 | plot.unselect_item(item) 57 | exec_dialog(win) 58 | 59 | 60 | def test_get_point(): 61 | """Test""" 62 | with qt_app_context(exec_loop=False): 63 | x = linspace(-10, 10, 200) 64 | y = 0.25 * sin(sin(sin(x * 0.5))) 65 | x2 = linspace(-10, 10, 200) 66 | y2 = sin(sin(sin(x2))) 67 | get_point(((x, y), (x2, y2), (x, sin(2 * y)))) 68 | 69 | 70 | if __name__ == "__main__": 71 | test_get_point() 72 | -------------------------------------------------------------------------------- /plotpy/widgets/about.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | about 4 | ===== 5 | 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | import guidata 11 | import qwt 12 | from guidata.configtools import get_icon 13 | from guidata.widgets import about as guidata_about 14 | from qtpy.QtCore import Qt 15 | from qtpy.QtWidgets import QMainWindow, QMessageBox 16 | 17 | import plotpy 18 | from plotpy.config import _ 19 | 20 | 21 | def get_python_libs_info(addinfo: str = "") -> str: 22 | """Get Python and libraries information 23 | 24 | Args: 25 | addinfo: additional information to be displayed 26 | 27 | Returns: 28 | str: Python and libraries information 29 | """ 30 | if addinfo: 31 | addinfo = ", " + addinfo 32 | addinfo = f"PlotPy {plotpy.__version__}{addinfo}" 33 | return guidata_about.get_python_libs_info(addinfo) 34 | 35 | 36 | def about(html: bool = True, copyright_only: bool = False) -> str: 37 | """Return text about this package 38 | 39 | Args: 40 | html: return html text. Defaults to True. 41 | copyright_only: if True, return only copyright 42 | 43 | Returns: 44 | str: text about this package 45 | """ 46 | info = guidata_about.AboutInfo( 47 | name="PlotPy", 48 | version=plotpy.__version__, 49 | description=_("Set of tools for curve and image plotting"), 50 | author="Pierre Raybaut", 51 | year=2016, 52 | organization="PlotPyStack", 53 | ) 54 | addinfo = f"guidata {guidata.__version__}, PythonQwt {qwt.__version__}" 55 | return info.about(html=html, copyright_only=copyright_only, addinfo=addinfo) 56 | 57 | 58 | def show_about_dialog() -> None: 59 | """Show ``plotpy`` about dialog""" 60 | win = QMainWindow(None) 61 | win.setAttribute(Qt.WA_DeleteOnClose) 62 | win.hide() 63 | win.setWindowIcon(get_icon("plotpy.svg")) 64 | QMessageBox.about(win, _("About") + " PlotPy", about(html=True)) 65 | win.close() 66 | -------------------------------------------------------------------------------- /plotpy/mathutils/scaler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | # pylint: disable=C0103 7 | 8 | """ 9 | Scaling functions 10 | ----------------- 11 | 12 | Overview 13 | ^^^^^^^^ 14 | 15 | The :py:mod:`.scaler` module provides scaling functions for images, thanks to 16 | the C++ scaler engine (`_scaler` extension). 17 | 18 | The following functions are available: 19 | 20 | * :py:func:`.resize`: resize an image using the scaler engine 21 | 22 | Reference 23 | ^^^^^^^^^ 24 | 25 | .. autofunction:: resize 26 | """ 27 | 28 | # TODO: Move all _scaler imports in this module and do something to avoid 29 | # the need to import INTERP_LINEAR, INTERP_AA, ... in all modules using the 30 | # scaler (code refactoring between pyplot.imshow, 31 | # styles.BaseImageParam.update_item) 32 | 33 | # TODO: Other functions like resize could be written in the future 34 | 35 | import numpy as np 36 | 37 | from plotpy._scaler import INTERP_AA, INTERP_LINEAR, INTERP_NEAREST, _scale_rect 38 | 39 | 40 | def resize(data, shape, interpolation=None): 41 | """Resize array *data* to *shape* (tuple) 42 | interpolation: 'nearest', 'linear' (default), 'antialiasing'""" 43 | interpolate = (INTERP_NEAREST,) 44 | if interpolation is not None: 45 | interp_dict = { 46 | "nearest": INTERP_NEAREST, 47 | "linear": INTERP_LINEAR, 48 | "antialiasing": INTERP_AA, 49 | } 50 | assert interpolation in interp_dict, "invalid interpolation option" 51 | interp_mode = interp_dict[interpolation] 52 | if interp_mode in (INTERP_NEAREST, INTERP_LINEAR): 53 | interpolate = (interp_mode,) 54 | if interp_mode == INTERP_AA: 55 | aa = np.ones((5, 5), data.dtype) 56 | interpolate = (interp_mode, aa) 57 | out = np.empty(shape) 58 | src_rect = (0, 0, data.shape[1], data.shape[0]) 59 | dst_rect = (0, 0, out.shape[1], out.shape[0]) 60 | _scale_rect(data, src_rect, out, dst_rect, (1.0, 0.0, None), interpolate) 61 | return out 62 | -------------------------------------------------------------------------------- /doc/release_notes/release_2.02.md: -------------------------------------------------------------------------------- 1 | # Version 2.2 # 2 | 3 | ## PlotPy Version 2.2.0 (2024-03-04) ## 4 | 5 | In this release, test coverage is 75%. 6 | 7 | New features: 8 | 9 | * Added `SIG_ITEM_PARAMETERS_CHANGED` signal to `BasePlot` class: 10 | * This signal is emitted when the parameters of an item are changed using the parameters dialog, or a specific tool (e.g. the colormap selection tool, or the lock/unlock tool for image items) 11 | * This signal is emitted with the item as argument 12 | * It is often emitted before the `SIG_ITEMS_CHANGED` signal, which is global to all items, but not necessarily. For example, when the colormap of an image is changed, the `SIG_ITEM_PARAMETERS_CHANGED` signal is emitted for the image item, but the `SIG_ITEMS_CHANGED` signal is not emitted. 13 | * Added new colormap presets: 14 | * `viridis`, `plasma`, `inferno`, `magma`, `cividis` 15 | * `afmhot` 16 | * `coolwarm`, `bwr`, `seismic` 17 | * `gnuplot2`, `CMRmap`, `rainbow`, `turbo` 18 | * Fixed all qualitative colormaps: 19 | * All qualitative colormaps have been re-computed because they are not supposed to be interpolated, which was the case and made them unusable 20 | * The qualitative colormaps are now usable and look like the ones from Matplotlib 21 | * Colormap manager: 22 | * Added a button to remove a custom colormap 23 | * The preset colormaps *and* the currently selected colormap are read-only 24 | * Added automatic unit tests for interactive tools: 25 | * `AnnotatedCircleTool`, `AnnotatedEllipseTool`, `AnnotatedObliqueRectangleTool`, `AnnotatedPointTool`, `AnnotatedRectangleTool`, `AnnotatedSegmentTool` 26 | * `AverageCrossSectionTool`, `CrossSectionTool`, `ObliqueCrossSectionTool`, `LineCrossSectionTool` 27 | * `EditPointTool`, `SelectPointsTool`, `SelectPointTool` 28 | * `AspectRatioTool`, `ImageStatsTool`, `SnapshotTool` 29 | * `DisplayCoordsTool`, `RectZoomTool` 30 | * `CircleTool`, `EllipseTool`, `FreeFormTool`, `MultiLineTool`, `ObliqueRectangleTool`, `PointTool`, `RectangleTool`, `SegmentTool` 31 | * Internal package reorganization: moved icons to `plotpy/data/icons` folder 32 | -------------------------------------------------------------------------------- /plotpy/tests/tools/test_customize_shape_tool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Shows how to customize a shape created with a tool like RectangleTool""" 7 | 8 | # guitest: show 9 | 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy.builder import make 13 | from plotpy.constants import LUTAlpha 14 | from plotpy.styles import style_generator, update_style_attr 15 | from plotpy.tests import get_path 16 | from plotpy.tools import ( 17 | EllipseTool, 18 | MultiLineTool, 19 | PolygonTool, 20 | RectangleTool, 21 | SegmentTool, 22 | ) 23 | 24 | STYLE = style_generator() 25 | 26 | 27 | def customize_shape(shape): 28 | global STYLE 29 | param = shape.shapeparam 30 | style = next(STYLE) 31 | update_style_attr(style, param) 32 | param.update_item(shape) 33 | shape.plot().replot() 34 | 35 | 36 | def create_window(): 37 | gridparam = make.gridparam( 38 | background="black", minor_enabled=(False, False), major_style=(":", "gray", 1) 39 | ) 40 | win = make.dialog( 41 | edit=False, 42 | toolbar=True, 43 | wintitle="All image and plot tools test", 44 | gridparam=gridparam, 45 | type="image", 46 | size=(800, 600), 47 | ) 48 | for toolklass in ( 49 | RectangleTool, 50 | EllipseTool, 51 | SegmentTool, 52 | MultiLineTool, 53 | PolygonTool, 54 | ): 55 | win.manager.add_tool(toolklass, handle_final_shape_cb=customize_shape) 56 | return win 57 | 58 | 59 | def test_customize_shape_tool(): 60 | """Test""" 61 | with qt_app_context(exec_loop=True): 62 | filename = get_path("brain.png") 63 | win = create_window() 64 | image = make.image( 65 | filename=filename, colormap="bone", alpha_function=LUTAlpha.LINEAR 66 | ) 67 | plot = win.manager.get_plot() 68 | plot.add_item(image) 69 | win.show() 70 | 71 | 72 | if __name__ == "__main__": 73 | test_customize_shape_tool() 74 | -------------------------------------------------------------------------------- /plotpy/tests/data/svg_target.svg: -------------------------------------------------------------------------------- 1 | 2 | 43 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_builder_shape.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Test PlotBuilder shape factory methods""" 7 | 8 | import numpy as np 9 | import pytest 10 | from guidata.qthelpers import qt_app_context 11 | 12 | from plotpy.builder import make 13 | from plotpy.tests import get_path 14 | from plotpy.tests.unit.test_builder_curve import show_items_qtbot 15 | 16 | DEFAULT_ARGS = { 17 | make.segment: [0.0, 0.0, 1.0, 1.0], 18 | make.rectangle: [0.0, 0.0, 1.0, 1.0], 19 | make.circle: [0.0, 0.0, 1.0, 1.0], 20 | make.ellipse: [0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0], 21 | } 22 | 23 | 24 | def _make_standard_shape( 25 | method, 26 | title: str = None, 27 | ): 28 | """Make annotation""" 29 | args = DEFAULT_ARGS[method] 30 | return method( 31 | *args, 32 | title=title, 33 | ) 34 | 35 | 36 | @pytest.mark.parametrize( 37 | "method", 38 | [make.segment, make.rectangle, make.circle, make.ellipse], 39 | ) 40 | def test_builder_standard_shape(method): 41 | with qt_app_context(exec_loop=False): 42 | items = [] 43 | items.append(_make_standard_shape(method, title="title")) 44 | show_items_qtbot(items) 45 | 46 | 47 | def test_builder_polygon(): 48 | items = [] 49 | x = np.linspace(0, 1, 10) 50 | y = x**2 51 | with qt_app_context(exec_loop=False): 52 | for closed in [True, False]: 53 | items.append(make.polygon(x, y, closed=closed, title="title")) 54 | show_items_qtbot(items) 55 | 56 | 57 | def test_builder_svgshape(): 58 | items = [] 59 | svg_path = get_path("svg_target.svg") 60 | with open(svg_path, "rb") as f: 61 | svg_data = f.read() 62 | with qt_app_context(exec_loop=False): 63 | for shape_str in ("circle", "rectangle", "square"): 64 | for data_or_path in (svg_data, svg_path): 65 | items.append( 66 | make.svg(shape_str, data_or_path, 0.0, 0.0, 1.0, 1.0, title="title") 67 | ) 68 | show_items_qtbot(items) 69 | -------------------------------------------------------------------------------- /plotpy/tests/features/test_image_coords.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | Testing image coordinates issues 8 | 9 | Check that the first image pixel is centered on (0, 0) coordinates. 10 | 11 | See https://github.com/PlotPyStack/guiqwt/issues/90 12 | """ 13 | 14 | # guitest: show 15 | 16 | from __future__ import annotations 17 | 18 | from typing import Literal 19 | 20 | import numpy as np 21 | from guidata.qthelpers import qt_app_context 22 | 23 | from plotpy.builder import make 24 | from plotpy.tests import data as ptd 25 | from plotpy.tests import vistools as ptv 26 | from plotpy.tools import DisplayCoordsTool 27 | 28 | 29 | def test_pixel_coords(image_type: Literal["standard", "xy"] = "standard") -> None: 30 | """Testing image pixel coordinates""" 31 | title = test_pixel_coords.__doc__ + f" ({image_type} image)" 32 | data = ptd.gen_2d_gaussian(20, np.uint8, x0=-10, y0=-10, mu=7, sigma=10.0) 33 | with qt_app_context(exec_loop=True): 34 | if image_type == "xy": 35 | x = np.linspace(0.0, 10.0, data.shape[1], dtype=float) 36 | y = np.linspace(0.0, 10.0, data.shape[0], dtype=float) ** 2 / 10.0 37 | image = make.xyimage(x, y, data, interpolation="nearest") 38 | else: 39 | image = make.image(data, interpolation="nearest") 40 | text = "First pixel should be centered on (0, 0) coordinates" 41 | label = make.label(text, (1.0, 1.0), (0, 0), "L") 42 | rect = make.rectangle(5.0, 5.0, 10.0, 10.0, "Rectangle") 43 | cursors = [] 44 | for i_cursor in range(0, 21, 10): 45 | cursors.append(make.vcursor(i_cursor, movable=False)) 46 | cursors.append(make.hcursor(i_cursor, movable=False)) 47 | win = ptv.show_items([image, label, rect] + cursors, wintitle=title) 48 | plot = win.get_plot() 49 | plot.select_item(image) 50 | win.manager.get_tool(DisplayCoordsTool).activate_curve_pointer(True) 51 | 52 | 53 | if __name__ == "__main__": 54 | test_pixel_coords("standard") 55 | test_pixel_coords("xy") 56 | -------------------------------------------------------------------------------- /.github/workflows/build_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload to PyPI 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | build_wheels: 10 | name: Build wheels on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macos-14, macos-15] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install build dependencies (for prebuild) 20 | run: | 21 | pip install --upgrade pip 22 | pip install babel 23 | 24 | - name: Build wheels 25 | uses: pypa/cibuildwheel@v2.21.2 26 | env: 27 | CIBW_BEFORE_BUILD: pip install babel && pybabel compile -d plotpy/locale -D plotpy 28 | 29 | - uses: actions/upload-artifact@v4 30 | with: 31 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 32 | path: ./wheelhouse/*.whl 33 | 34 | build_sdist: 35 | name: Build source distribution 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Install build dependencies 41 | run: | 42 | pip install babel 43 | 44 | - name: Compile translations using Babel 45 | run: | 46 | pybabel compile -d plotpy/locale -D plotpy 47 | 48 | - name: Build sdist 49 | run: pipx run build --sdist 50 | 51 | - uses: actions/upload-artifact@v4 52 | with: 53 | name: cibw-sdist 54 | path: dist/*.tar.gz 55 | 56 | upload_pypi: 57 | needs: [build_wheels, build_sdist] 58 | runs-on: ubuntu-latest 59 | environment: pypi 60 | permissions: 61 | id-token: write 62 | if: github.event_name == 'release' && github.event.action == 'published' 63 | steps: 64 | - uses: actions/download-artifact@v4 65 | with: 66 | # unpacks all CIBW artifacts into dist/ 67 | pattern: cibw-* 68 | path: dist 69 | merge-multiple: true 70 | 71 | - uses: pypa/gh-action-pypi-publish@release/v1 72 | with: 73 | attestations: false 74 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /plotpy/tests/tools/test_get_segment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """ 7 | Test ``get_segment`` feature: select a segment on an image. 8 | 9 | This plotpy tool provide a MATLAB-like "ginput" feature. 10 | """ 11 | 12 | # guitest: show 13 | 14 | import numpy as np 15 | import qtpy.QtCore as QC 16 | from guidata.env import execenv 17 | from guidata.qthelpers import qt_app_context 18 | 19 | from plotpy.builder import make 20 | from plotpy.coords import axes_to_canvas 21 | from plotpy.tools import AnnotatedSegmentTool 22 | from plotpy.widgets.selectdialog import SelectDialog, select_with_shape_tool 23 | 24 | SEG_AXES_COORDS = [20, 20, 70, 70] 25 | 26 | 27 | class PatchedSelectDialog(SelectDialog): 28 | """Patched SelectDialog""" 29 | 30 | def set_image_and_tool(self, item, toolclass, **kwargs): 31 | """Reimplement SelectDialog method""" 32 | super().set_image_and_tool(item, toolclass, **kwargs) 33 | if execenv.unattended: 34 | self.show() 35 | self.sel_tool.add_shape_to_plot( 36 | self.manager.get_plot(), 37 | QC.QPointF(*axes_to_canvas(item, *SEG_AXES_COORDS[:2])), 38 | QC.QPointF(*axes_to_canvas(item, *SEG_AXES_COORDS[2:])), 39 | ) 40 | 41 | 42 | def test_get_segment(): 43 | """Test get_segment""" 44 | with qt_app_context(): 45 | image = make.image(data=np.random.rand(200, 200), colormap="gray") 46 | shape = select_with_shape_tool( 47 | None, 48 | AnnotatedSegmentTool, 49 | image, 50 | "Test", 51 | tooldialogclass=PatchedSelectDialog, 52 | ) 53 | if shape is not None: 54 | rect = shape.get_rect() 55 | if execenv.unattended: 56 | assert [round(i) for i in list(rect)] == SEG_AXES_COORDS 57 | elif rect is not None: 58 | distance = np.sqrt((rect[2] - rect[0]) ** 2 + (rect[3] - rect[1]) ** 2) 59 | print("Distance:", distance) 60 | 61 | 62 | if __name__ == "__main__": 63 | test_get_segment() 64 | -------------------------------------------------------------------------------- /plotpy/tests/unit/test_display_coords_tool.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import numpy as np 6 | import pytest 7 | import qtpy.QtCore as QC 8 | from guidata.qthelpers import qt_app_context 9 | 10 | from plotpy.interfaces.items import ICurveItemType, IImageItemType 11 | from plotpy.tests.unit.utils import create_window, drag_mouse 12 | from plotpy.tools import DisplayCoordsTool 13 | 14 | if TYPE_CHECKING: 15 | from plotpy.interfaces.items import IItemType 16 | 17 | 18 | @pytest.mark.parametrize("active_item", [ICurveItemType, IImageItemType, None]) 19 | def test_display_coords(active_item: type[IItemType] | None): 20 | """Test display coordinates tool on a curve and on an image.""" 21 | with qt_app_context(exec_loop=False): 22 | win, _tool = create_window(DisplayCoordsTool, active_item_type=active_item) 23 | plot = win.manager.get_plot() 24 | 25 | # The is no way to test a condition while the mouse is moving so it is 26 | # not possible to test the display of the coordinates while the mouse is moving. 27 | assert plot.curve_pointer is False and plot.canvas_pointer is False 28 | drag_mouse(win, np.array([0.5]), np.array([0.5]), click=False) 29 | assert plot.curve_pointer is False and plot.canvas_pointer is False 30 | drag_mouse(win, np.array([0.5]), np.array([0.5]), click=True) 31 | assert plot.curve_pointer is False and plot.canvas_pointer is False 32 | drag_mouse( 33 | win, 34 | np.array([0.5]), 35 | np.array([0.5]), 36 | click=False, 37 | mod=QC.Qt.KeyboardModifier.AltModifier, 38 | ) 39 | assert plot.curve_pointer is False and plot.canvas_pointer is False 40 | drag_mouse( 41 | win, 42 | np.array([0.5]), 43 | np.array([0.5]), 44 | click=False, 45 | mod=QC.Qt.KeyboardModifier.ControlModifier, 46 | ) 47 | assert plot.curve_pointer is False and plot.canvas_pointer is False 48 | 49 | 50 | if __name__ == "__main__": 51 | for item in (ICurveItemType, IImageItemType): 52 | test_display_coords(item) 53 | -------------------------------------------------------------------------------- /doc/features/styles/reference.rst: -------------------------------------------------------------------------------- 1 | :tocdepth: 3 2 | 3 | Reference 4 | --------- 5 | 6 | Base classes 7 | ^^^^^^^^^^^^ 8 | 9 | .. autoclass:: plotpy.styles.base.ItemParameters 10 | :members: 11 | 12 | Plot (grid, axes) 13 | ^^^^^^^^^^^^^^^^^ 14 | 15 | .. autoclass:: plotpy.styles.GridParam 16 | :members: 17 | .. autoclass:: plotpy.styles.AxesParam 18 | :members: 19 | .. autoclass:: plotpy.styles.ImageAxesParam 20 | :members: 21 | 22 | Curve items 23 | ^^^^^^^^^^^ 24 | 25 | .. autoclass:: plotpy.styles.CurveParam 26 | :members: 27 | .. autoclass:: plotpy.styles.ErrorBarParam 28 | :members: 29 | 30 | Image items 31 | ^^^^^^^^^^^ 32 | 33 | .. autoclass:: plotpy.styles.RawImageParam 34 | :members: 35 | .. autoclass:: plotpy.styles.ImageParam 36 | :members: 37 | .. autoclass:: plotpy.styles.TrImageParam 38 | :members: 39 | .. autoclass:: plotpy.styles.XYImageParam 40 | :members: 41 | .. autoclass:: plotpy.styles.RGBImageParam 42 | :members: 43 | .. autoclass:: plotpy.styles.MaskedImageParam 44 | :members: 45 | .. autoclass:: plotpy.styles.MaskedXYImageParam 46 | :members: 47 | .. autoclass:: plotpy.styles.ImageFilterParam 48 | :members: 49 | .. autoclass:: plotpy.styles.QuadGridParam 50 | :members: 51 | 52 | Histogram items 53 | ^^^^^^^^^^^^^^^ 54 | 55 | .. autoclass:: plotpy.styles.HistogramParam 56 | :members: 57 | .. autoclass:: plotpy.styles.Histogram2DParam 58 | :members: 59 | 60 | Shape items 61 | ^^^^^^^^^^^ 62 | 63 | .. autoclass:: plotpy.styles.LabelParam 64 | :members: 65 | .. autoclass:: plotpy.styles.LegendParam 66 | :members: 67 | .. autoclass:: plotpy.styles.ShapeParam 68 | :members: 69 | .. autoclass:: plotpy.styles.AnnotationParam 70 | :members: 71 | .. autoclass:: plotpy.styles.AxesShapeParam 72 | :members: 73 | .. autoclass:: plotpy.styles.RangeShapeParam 74 | :members: 75 | .. autoclass:: plotpy.styles.MarkerParam 76 | :members: 77 | .. autoclass:: plotpy.styles.FontParam 78 | :members: 79 | .. autoclass:: plotpy.styles.SymbolParam 80 | :members: 81 | .. autoclass:: plotpy.styles.LineStyleParam 82 | :members: 83 | .. autoclass:: plotpy.styles.BrushStyleParam 84 | :members: 85 | .. autoclass:: plotpy.styles.TextStyleParam 86 | :members: 87 | -------------------------------------------------------------------------------- /plotpy/styles/errorbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import annotations 4 | 5 | from typing import TYPE_CHECKING 6 | 7 | from guidata.dataset import BoolItem, ChoiceItem, ColorItem, DataSet, FloatItem, IntItem 8 | from qtpy import QtGui as QG 9 | 10 | from plotpy.config import _ 11 | 12 | if TYPE_CHECKING: 13 | from plotpy.items import ErrorBarCurveItem 14 | 15 | 16 | class ErrorBarParam(DataSet): 17 | """Error bar style parameters for a curve.""" 18 | 19 | mode = ChoiceItem( 20 | _("Display"), 21 | default=0, 22 | choices=[_("error bars with caps (x, y)"), _("error area (y)")], 23 | help=_( 24 | "Note: only y-axis error bars are shown in " 25 | "error area mode\n(width and cap parameters " 26 | "will also be ignored)" 27 | ), 28 | ) 29 | color = ColorItem(_("Color"), default="darkred") 30 | alpha = FloatItem( 31 | _("Alpha"), default=0.9, min=0, max=1, help=_("Error bar transparency") 32 | ) 33 | width = FloatItem(_("Width"), default=1.0, min=1.0) 34 | cap = IntItem(_("Cap"), default=4, min=0) 35 | ontop = BoolItem(_("set to foreground"), _("Visibility"), default=False) 36 | 37 | def update_param(self, item: ErrorBarCurveItem) -> None: 38 | """ 39 | Update the parameters associated with the error bar item. 40 | 41 | Args: 42 | item: The error bar item from which to update the parameters. 43 | """ 44 | color = item.errorPen.color() 45 | self.color = str(color.name()) 46 | self.alpha = color.alphaF() 47 | self.width = item.errorPen.widthF() 48 | self.cap = item.errorCap 49 | self.ontop = item.errorOnTop 50 | 51 | def update_item(self, item: ErrorBarCurveItem) -> None: 52 | """ 53 | Update the error bar item with the parameters. 54 | 55 | Args: 56 | item: The error bar item to update. 57 | """ 58 | color = QG.QColor(self.color) 59 | color.setAlphaF(self.alpha) 60 | item.errorPen = QG.QPen(color, self.width) 61 | item.errorBrush = QG.QBrush(color) 62 | item.errorCap = self.cap 63 | item.errorOnTop = self.ontop 64 | -------------------------------------------------------------------------------- /plotpy/tests/items/test_to_bins.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Licensed under the terms of the BSD 3-Clause 4 | # (see plotpy/LICENSE for details) 5 | 6 | """Test for to_bins function used in XYImageItem""" 7 | 8 | import numpy as np 9 | 10 | from plotpy.items.image.filter import to_bins 11 | 12 | 13 | def test_to_bins_single_element(): 14 | """Test to_bins with a single-element array. 15 | 16 | This is a regression test for the bug where loading an image with only 17 | 1 row (e.g., SIF files with shape (1, N)) caused an IndexError because 18 | to_bins assumed at least 2 elements to compute bin edges. 19 | """ 20 | x = np.array([5.0]) 21 | result = to_bins(x) 22 | 23 | # Should return 2 bin edges, centered around the single point 24 | assert len(result) == 2 25 | assert result[0] == 4.5 # x[0] - 0.5 26 | assert result[1] == 5.5 # x[0] + 0.5 27 | 28 | 29 | def test_to_bins_two_elements(): 30 | """Test to_bins with a two-element array.""" 31 | x = np.array([1.0, 3.0]) 32 | result = to_bins(x) 33 | 34 | # Should return 3 bin edges 35 | assert len(result) == 3 36 | np.testing.assert_allclose(result, [0.0, 2.0, 4.0]) 37 | 38 | 39 | def test_to_bins_multiple_elements(): 40 | """Test to_bins with multiple elements (uniform spacing).""" 41 | x = np.array([1.0, 2.0, 3.0, 4.0]) 42 | result = to_bins(x) 43 | 44 | # Should return 5 bin edges 45 | assert len(result) == 5 46 | np.testing.assert_allclose(result, [0.5, 1.5, 2.5, 3.5, 4.5]) 47 | 48 | 49 | def test_to_bins_non_uniform_spacing(): 50 | """Test to_bins with non-uniform spacing.""" 51 | x = np.array([0.0, 1.0, 4.0]) 52 | result = to_bins(x) 53 | 54 | # Should return 4 bin edges 55 | # First edge: 0.0 - (1.0 - 0.0) / 2 = -0.5 56 | # Middle: (0.0 + 1.0) / 2 = 0.5, (1.0 + 4.0) / 2 = 2.5 57 | # Last edge: 4.0 + (4.0 - 1.0) / 2 = 5.5 58 | assert len(result) == 4 59 | np.testing.assert_allclose(result, [-0.5, 0.5, 2.5, 5.5]) 60 | 61 | 62 | if __name__ == "__main__": 63 | test_to_bins_single_element() 64 | test_to_bins_two_elements() 65 | test_to_bins_multiple_elements() 66 | test_to_bins_non_uniform_spacing() 67 | print("All tests passed!") 68 | --------------------------------------------------------------------------------