interactive-pipe 0.9.1__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {interactive_pipe-0.9.1/src/interactive_pipe.egg-info → interactive_pipe-0.9.2}/PKG-INFO +18 -3
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/pyproject.toml +14 -1
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/readme.md +6 -2
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/__init__.py +7 -1
- interactive_pipe-0.9.2/src/interactive_pipe/cli.py +216 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/backend.py +1 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/cache.py +31 -1
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/context.py +37 -6
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/engine.py +87 -28
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/filter.py +4 -2
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/framework_state.py +8 -1
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/graph.py +8 -15
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/pipeline.py +33 -7
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/audio.py +2 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/curves.py +38 -24
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/data.py +3 -4
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/image.py +23 -6
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/parameters.py +10 -1
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/actions.py +56 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/dpg_backend.py +77 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/dpg_control.py +645 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/dpg_gui.py +658 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/dpg_image.py +367 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/dpg_panels.py +205 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/gradio_control.py +129 -3
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/gradio_gui.py +3 -4
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/gradio_layout.py +28 -36
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/gradio_outputs.py +5 -3
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/gui.py +577 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/mpl_control.py +62 -4
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/mpl_gui.py +22 -51
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/mpl_window.py +56 -30
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/nb_control.py +68 -4
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/nb_gui.py +1 -1
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/qt_audio.py +2 -2
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/qt_backend.py +63 -8
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/qt_control.py +727 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/qt_gui.py +975 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/qt_image.py +34 -9
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/qt_runner.py +78 -0
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/qt_viewer.py +334 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/qt_widgets.py +75 -1
- interactive_pipe-0.9.2/src/interactive_pipe/graphical/window.py +198 -0
- interactive_pipe-0.9.2/src/interactive_pipe/headless/animation.py +183 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/headless/control.py +145 -3
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/headless/panel.py +2 -2
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/headless/pipeline.py +45 -90
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/choose_backend.py +30 -8
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/filter_decorator.py +3 -2
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/keyword_args_analyzer.py +5 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/pipeline_decorator.py +29 -6
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2/src/interactive_pipe.egg-info}/PKG-INFO +18 -3
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe.egg-info/SOURCES.txt +31 -1
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe.egg-info/requires.txt +12 -0
- interactive_pipe-0.9.2/test/test_animation.py +274 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_cache.py +16 -0
- interactive_pipe-0.9.2/test/test_cli.py +329 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_curves.py +44 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_data_objects_exceptions.py +17 -9
- interactive_pipe-0.9.2/test/test_decorator_default_override.py +16 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_edge_cases.py +2 -6
- interactive_pipe-0.9.2/test/test_event_cache.py +79 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_graph_cache.py +53 -0
- interactive_pipe-0.9.2/test/test_gui_actions.py +237 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_headless_exceptions.py +7 -0
- interactive_pipe-0.9.2/test/test_image.py +42 -0
- interactive_pipe-0.9.2/test/test_layout_grid.py +59 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_qt_backend_cascade.py +32 -2
- interactive_pipe-0.9.2/test/test_qt_refresh.py +447 -0
- interactive_pipe-0.9.2/test/test_qt_viewer.py +274 -0
- interactive_pipe-0.9.2/test/test_range_control.py +157 -0
- interactive_pipe-0.9.2/test/test_range_control_dpg.py +94 -0
- interactive_pipe-0.9.2/test/test_range_control_gradio.py +46 -0
- interactive_pipe-0.9.2/test/test_range_control_mpl.py +52 -0
- interactive_pipe-0.9.2/test/test_range_control_nb.py +45 -0
- interactive_pipe-0.9.2/test/test_range_control_qt.py +116 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_readonly_inputs.py +41 -0
- interactive_pipe-0.9.2/test/test_reset_parameters.py +105 -0
- interactive_pipe-0.9.2/test/test_run_report.py +106 -0
- interactive_pipe-0.9.2/test/test_session.py +232 -0
- interactive_pipe-0.9.2/test/test_smoke_dpg.py +959 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_smoke_gradio.py +50 -0
- interactive_pipe-0.9.2/test/test_smoke_mpl.py +190 -0
- interactive_pipe-0.9.2/test/test_smoke_nb.py +78 -0
- interactive_pipe-0.9.2/test/test_smoke_qt.py +478 -0
- interactive_pipe-0.9.2/test/test_title_elide.py +107 -0
- interactive_pipe-0.9.1/src/interactive_pipe/graphical/gui.py +0 -297
- interactive_pipe-0.9.1/src/interactive_pipe/graphical/qt_control.py +0 -462
- interactive_pipe-0.9.1/src/interactive_pipe/graphical/qt_gui.py +0 -519
- interactive_pipe-0.9.1/src/interactive_pipe/graphical/window.py +0 -111
- interactive_pipe-0.9.1/test/test_image.py +0 -21
- interactive_pipe-0.9.1/test/test_smoke_mpl.py +0 -59
- interactive_pipe-0.9.1/test/test_smoke_qt.py +0 -266
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/LICENSE +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/setup.cfg +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/context_tracking.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/core/signature.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/data_objects/table.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/graphical/qt_panels.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/headless/keyboard.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/_private.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe/py.typed +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_context.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_control_abbreviations.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_control_registry.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_controller.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_core.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_core_exceptions.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_decorator.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_engine.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_filter.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_framework_state.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_graphical_exceptions.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_headless.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_legacy_removal.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_mutable_defaults.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_parameters.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_recorder.py +0 -0
- {interactive_pipe-0.9.1 → interactive_pipe-0.9.2}/test/test_table.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!
|
|
5
5
|
Author-email: Balthazar Neveu <balthazarneveu@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/balthazarneveu/interactive_pipe
|
|
@@ -30,6 +30,14 @@ Requires-Dist: matplotlib>=3.5.3
|
|
|
30
30
|
Requires-Dist: numpy>=1.21.6
|
|
31
31
|
Requires-Dist: Pillow>=9.0.1
|
|
32
32
|
Requires-Dist: PyYAML>=5.4.1
|
|
33
|
+
Requires-Dist: PyQt6>=6.5.2
|
|
34
|
+
Requires-Dist: PyQt6_sip>=13.5.2
|
|
35
|
+
Requires-Dist: opencv_python_headless>=4.8.1.78
|
|
36
|
+
Requires-Dist: pytest>=6.2.5
|
|
37
|
+
Requires-Dist: ipywidgets>=7.7.1
|
|
38
|
+
Requires-Dist: pandas
|
|
39
|
+
Requires-Dist: gradio
|
|
40
|
+
Requires-Dist: dearpygui>=1.11
|
|
33
41
|
Provides-Extra: qt6
|
|
34
42
|
Requires-Dist: PyQt6>=6.5.2; extra == "qt6"
|
|
35
43
|
Requires-Dist: PyQt6_sip>=13.5.2; extra == "qt6"
|
|
@@ -38,6 +46,8 @@ Requires-Dist: PyQt5>=5.15.9; extra == "qt5"
|
|
|
38
46
|
Requires-Dist: PyQt5_sip>=12.12.1; extra == "qt5"
|
|
39
47
|
Provides-Extra: notebook
|
|
40
48
|
Requires-Dist: ipywidgets>=7.7.1; extra == "notebook"
|
|
49
|
+
Provides-Extra: dpg
|
|
50
|
+
Requires-Dist: dearpygui>=1.11; extra == "dpg"
|
|
41
51
|
Provides-Extra: pytest
|
|
42
52
|
Requires-Dist: opencv_python_headless>=4.8.1.78; extra == "pytest"
|
|
43
53
|
Requires-Dist: pytest>=6.2.5; extra == "pytest"
|
|
@@ -49,6 +59,7 @@ Requires-Dist: pytest>=6.2.5; extra == "full"
|
|
|
49
59
|
Requires-Dist: ipywidgets>=7.7.1; extra == "full"
|
|
50
60
|
Requires-Dist: pandas; extra == "full"
|
|
51
61
|
Requires-Dist: gradio; extra == "full"
|
|
62
|
+
Requires-Dist: dearpygui>=1.11; extra == "full"
|
|
52
63
|
Provides-Extra: dev
|
|
53
64
|
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
54
65
|
Requires-Dist: pyright>=1.1.390; extra == "dev"
|
|
@@ -93,20 +104,24 @@ pip install interactive-pipe
|
|
|
93
104
|
from interactive_pipe import interactive, interactive_pipeline
|
|
94
105
|
import numpy as np
|
|
95
106
|
|
|
107
|
+
|
|
96
108
|
@interactive(coeff=(1.0, [0.5, 2.0], "exposure"), bias=(0.0, [-0.2, 0.2]))
|
|
97
109
|
def exposure(img, coeff=1.0, bias=0.0):
|
|
98
110
|
return img * coeff + bias
|
|
99
111
|
|
|
112
|
+
|
|
100
113
|
@interactive(blend_coeff=(0.5, [0.0, 1.0]))
|
|
101
114
|
def blend(img0, img1, blend_coeff=0.5):
|
|
102
115
|
return (1 - blend_coeff) * img0 + blend_coeff * img1
|
|
103
116
|
|
|
104
|
-
|
|
117
|
+
|
|
118
|
+
@interactive_pipeline(gui="qt") # or "dpg" (Dear PyGui), "mpl", "nb" (Jupyter/Colab), "gradio"
|
|
105
119
|
def pipe(img):
|
|
106
120
|
exposed = exposure(img)
|
|
107
121
|
blended = blend(img, exposed)
|
|
108
122
|
return exposed, blended
|
|
109
123
|
|
|
124
|
+
|
|
110
125
|
pipe(np.array([0.0, 0.5, 0.8]) * np.ones((256, 512, 3)))
|
|
111
126
|
```
|
|
112
127
|
|
|
@@ -119,7 +134,7 @@ Calling `pipe(...)` opens a window with sliders for every declared parameter.
|
|
|
119
134
|
| `gui="qt"` | `gui="mpl"` | `gui="nb"` | `gui="gradio"` |
|
|
120
135
|
|  |  |  |  |
|
|
121
136
|
|
|
122
|
-
Plus headless mode (`gui=None`) for batch processing. Full feature matrix in the [backends docs](https://balthazarneveu.github.io/interactive_pipe/getting-started/backends/).
|
|
137
|
+
Plus a GPU-rendered [Dear PyGui](https://github.com/hoffstadt/DearPyGui) window (`gui="dpg"`, `pip install "interactive-pipe[dpg]"`) and headless mode (`gui=None`) for batch processing. Full feature matrix in the [backends docs](https://balthazarneveu.github.io/interactive_pipe/getting-started/backends/).
|
|
123
138
|
|
|
124
139
|
## Learn more
|
|
125
140
|
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "interactive_pipe"
|
|
8
|
-
version = "0.9.
|
|
8
|
+
version = "0.9.2"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Balthazar Neveu", email="balthazarneveu@gmail.com" },
|
|
11
11
|
]
|
|
@@ -14,6 +14,14 @@ dependencies = [
|
|
|
14
14
|
"numpy>=1.21.6",
|
|
15
15
|
"Pillow>=9.0.1",
|
|
16
16
|
"PyYAML>=5.4.1",
|
|
17
|
+
"PyQt6>=6.5.2",
|
|
18
|
+
"PyQt6_sip>=13.5.2",
|
|
19
|
+
"opencv_python_headless>=4.8.1.78",
|
|
20
|
+
"pytest>=6.2.5",
|
|
21
|
+
"ipywidgets>=7.7.1",
|
|
22
|
+
"pandas",
|
|
23
|
+
"gradio",
|
|
24
|
+
"dearpygui>=1.11",
|
|
17
25
|
]
|
|
18
26
|
|
|
19
27
|
description = "Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!"
|
|
@@ -59,6 +67,10 @@ notebook=[
|
|
|
59
67
|
"ipywidgets>=7.7.1",
|
|
60
68
|
]
|
|
61
69
|
|
|
70
|
+
dpg=[
|
|
71
|
+
"dearpygui>=1.11",
|
|
72
|
+
]
|
|
73
|
+
|
|
62
74
|
pytest=[
|
|
63
75
|
"opencv_python_headless>=4.8.1.78",
|
|
64
76
|
"pytest>=6.2.5"
|
|
@@ -72,6 +84,7 @@ full=[
|
|
|
72
84
|
"ipywidgets>=7.7.1",
|
|
73
85
|
"pandas",
|
|
74
86
|
"gradio",
|
|
87
|
+
"dearpygui>=1.11",
|
|
75
88
|
]
|
|
76
89
|
|
|
77
90
|
dev=[
|
|
@@ -33,20 +33,24 @@ pip install interactive-pipe
|
|
|
33
33
|
from interactive_pipe import interactive, interactive_pipeline
|
|
34
34
|
import numpy as np
|
|
35
35
|
|
|
36
|
+
|
|
36
37
|
@interactive(coeff=(1.0, [0.5, 2.0], "exposure"), bias=(0.0, [-0.2, 0.2]))
|
|
37
38
|
def exposure(img, coeff=1.0, bias=0.0):
|
|
38
39
|
return img * coeff + bias
|
|
39
40
|
|
|
41
|
+
|
|
40
42
|
@interactive(blend_coeff=(0.5, [0.0, 1.0]))
|
|
41
43
|
def blend(img0, img1, blend_coeff=0.5):
|
|
42
44
|
return (1 - blend_coeff) * img0 + blend_coeff * img1
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
|
|
47
|
+
@interactive_pipeline(gui="qt") # or "dpg" (Dear PyGui), "mpl", "nb" (Jupyter/Colab), "gradio"
|
|
45
48
|
def pipe(img):
|
|
46
49
|
exposed = exposure(img)
|
|
47
50
|
blended = blend(img, exposed)
|
|
48
51
|
return exposed, blended
|
|
49
52
|
|
|
53
|
+
|
|
50
54
|
pipe(np.array([0.0, 0.5, 0.8]) * np.ones((256, 512, 3)))
|
|
51
55
|
```
|
|
52
56
|
|
|
@@ -59,7 +63,7 @@ Calling `pipe(...)` opens a window with sliders for every declared parameter.
|
|
|
59
63
|
| `gui="qt"` | `gui="mpl"` | `gui="nb"` | `gui="gradio"` |
|
|
60
64
|
|  |  |  |  |
|
|
61
65
|
|
|
62
|
-
Plus headless mode (`gui=None`) for batch processing. Full feature matrix in the [backends docs](https://balthazarneveu.github.io/interactive_pipe/getting-started/backends/).
|
|
66
|
+
Plus a GPU-rendered [Dear PyGui](https://github.com/hoffstadt/DearPyGui) window (`gui="dpg"`, `pip install "interactive-pipe[dpg]"`) and headless mode (`gui=None`) for batch processing. Full feature matrix in the [backends docs](https://balthazarneveu.github.io/interactive_pipe/getting-started/backends/).
|
|
63
67
|
|
|
64
68
|
## Learn more
|
|
65
69
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.9.
|
|
1
|
+
__version__ = "0.9.2"
|
|
2
2
|
# Backend enum for type-safe backend specification
|
|
3
3
|
from interactive_pipe.core.backend import Backend
|
|
4
4
|
|
|
@@ -23,6 +23,7 @@ from interactive_pipe.data_objects.table import Table
|
|
|
23
23
|
from interactive_pipe.headless.control import (
|
|
24
24
|
CircularControl,
|
|
25
25
|
Control,
|
|
26
|
+
RangeControl,
|
|
26
27
|
TextPrompt,
|
|
27
28
|
TimeControl,
|
|
28
29
|
)
|
|
@@ -33,6 +34,9 @@ from interactive_pipe.headless.panel import Panel
|
|
|
33
34
|
from interactive_pipe.helper.filter_decorator import interact, interactive
|
|
34
35
|
from interactive_pipe.helper.pipeline_decorator import interactive_pipeline
|
|
35
36
|
|
|
37
|
+
# Animation export (TimeControl pipelines -> GIF / PNG frames)
|
|
38
|
+
from interactive_pipe.headless.animation import export_animation
|
|
39
|
+
|
|
36
40
|
# Allowing more straightforward naming convention
|
|
37
41
|
block = interactive
|
|
38
42
|
pipeline = interactive_pipeline
|
|
@@ -57,6 +61,7 @@ __all__ = [
|
|
|
57
61
|
"Table",
|
|
58
62
|
"Control",
|
|
59
63
|
"CircularControl",
|
|
64
|
+
"RangeControl",
|
|
60
65
|
"TextPrompt",
|
|
61
66
|
"TimeControl",
|
|
62
67
|
"KeyboardControl",
|
|
@@ -66,4 +71,5 @@ __all__ = [
|
|
|
66
71
|
"interactive_pipeline",
|
|
67
72
|
"block",
|
|
68
73
|
"pipeline",
|
|
74
|
+
"export_animation",
|
|
69
75
|
]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Command-line and environment helpers to choose the GUI backend and window size.
|
|
2
|
+
|
|
3
|
+
Lets a script pick its backend / window size without editing code::
|
|
4
|
+
|
|
5
|
+
from interactive_pipe import interactive_pipeline
|
|
6
|
+
from interactive_pipe.cli import parse_gui_args
|
|
7
|
+
|
|
8
|
+
args, gui_kwargs = parse_gui_args()
|
|
9
|
+
demo = interactive_pipeline(**gui_kwargs)(my_pipeline) # gui="auto" unless --gui given
|
|
10
|
+
demo(img)
|
|
11
|
+
|
|
12
|
+
Environment variables (read when ``interactive_pipeline`` decorates a function):
|
|
13
|
+
|
|
14
|
+
- ``INTERACTIVE_PIPE_GUI``: one of auto/qt/dpg/mpl/nb/gradio/headless/none,
|
|
15
|
+
used when the decorator was called with ``gui="auto"`` (the default).
|
|
16
|
+
- ``INTERACTIVE_PIPE_SIZE``: "fullscreen", "maximized" or "WxH" (e.g. "1280x800"),
|
|
17
|
+
used when the decorator was not given a ``size``.
|
|
18
|
+
|
|
19
|
+
Precedence: CLI helper values > explicit decorator arguments (other than "auto") > environment.
|
|
20
|
+
|
|
21
|
+
This module only depends on the standard library and has no side effects on import.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import os
|
|
26
|
+
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|
27
|
+
|
|
28
|
+
GUI_ENV_VAR = "INTERACTIVE_PIPE_GUI"
|
|
29
|
+
SIZE_ENV_VAR = "INTERACTIVE_PIPE_SIZE"
|
|
30
|
+
|
|
31
|
+
GUI_CHOICES: Tuple[str, ...] = ("auto", "qt", "dpg", "mpl", "nb", "gradio", "headless")
|
|
32
|
+
# Accepted by the environment variable on top of GUI_CHOICES (aliases of "headless")
|
|
33
|
+
_HEADLESS_ALIASES = ("headless", "none")
|
|
34
|
+
|
|
35
|
+
SizeType = Union[str, Tuple[int, int]]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def parse_size(value: str) -> SizeType:
|
|
39
|
+
"""Parse a window size string.
|
|
40
|
+
|
|
41
|
+
Returns "fullscreen" for "fullscreen"/"full", "maximized" for "maximized"/"max",
|
|
42
|
+
or a ``(width, height)`` tuple for "WxH" (e.g. "1280x800").
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If the value is not a recognized size.
|
|
46
|
+
"""
|
|
47
|
+
if not isinstance(value, str):
|
|
48
|
+
raise TypeError(f"size should be a string, got {type(value).__name__}")
|
|
49
|
+
normalized = value.strip().lower()
|
|
50
|
+
if normalized in ("fullscreen", "full"):
|
|
51
|
+
return "fullscreen"
|
|
52
|
+
if normalized in ("maximized", "max"):
|
|
53
|
+
return "maximized"
|
|
54
|
+
parts = normalized.split("x")
|
|
55
|
+
if len(parts) == 2 and all(part.strip().isdigit() for part in parts):
|
|
56
|
+
width, height = (int(part) for part in parts)
|
|
57
|
+
if width > 0 and height > 0:
|
|
58
|
+
return (width, height)
|
|
59
|
+
raise ValueError(f"invalid size {value!r}: expected 'fullscreen', 'maximized' or 'WxH' (e.g. 1280x800)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _argparse_size(value: str) -> Tuple[int, int]:
|
|
63
|
+
try:
|
|
64
|
+
size = parse_size(value)
|
|
65
|
+
except ValueError as exc:
|
|
66
|
+
raise argparse.ArgumentTypeError(str(exc)) from exc
|
|
67
|
+
if not isinstance(size, tuple):
|
|
68
|
+
raise argparse.ArgumentTypeError(f"--size expects WxH (e.g. 1280x800), use --{size} instead of {value!r}")
|
|
69
|
+
return size
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def gui_from_env() -> Optional[str]:
|
|
73
|
+
"""Backend requested by ``INTERACTIVE_PIPE_GUI``, or None when unset/empty.
|
|
74
|
+
|
|
75
|
+
"none" is normalized to "headless".
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If the variable holds an unknown backend name.
|
|
79
|
+
"""
|
|
80
|
+
raw = os.environ.get(GUI_ENV_VAR, "").strip().lower()
|
|
81
|
+
if not raw:
|
|
82
|
+
return None
|
|
83
|
+
if raw in _HEADLESS_ALIASES:
|
|
84
|
+
return "headless"
|
|
85
|
+
if raw not in GUI_CHOICES:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"{GUI_ENV_VAR}={raw!r} is not a valid backend, expected one of {', '.join(GUI_CHOICES + ('none',))}"
|
|
88
|
+
)
|
|
89
|
+
return raw
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def size_from_env() -> Optional[SizeType]:
|
|
93
|
+
"""Window size requested by ``INTERACTIVE_PIPE_SIZE``, or None when unset/empty.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If the variable holds an invalid size.
|
|
97
|
+
"""
|
|
98
|
+
raw = os.environ.get(SIZE_ENV_VAR, "").strip()
|
|
99
|
+
if not raw:
|
|
100
|
+
return None
|
|
101
|
+
try:
|
|
102
|
+
return parse_size(raw)
|
|
103
|
+
except ValueError as exc:
|
|
104
|
+
raise ValueError(f"{SIZE_ENV_VAR}: {exc}") from exc
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def resolve_gui(gui: Any) -> Any:
|
|
108
|
+
"""Apply the ``INTERACTIVE_PIPE_GUI`` override to a ``gui`` decorator argument.
|
|
109
|
+
|
|
110
|
+
Only ``"auto"`` (or ``Backend.AUTO``) is overridden; explicit backends and
|
|
111
|
+
``None`` (headless) are returned unchanged. ``"headless"`` from the
|
|
112
|
+
environment is returned as None.
|
|
113
|
+
"""
|
|
114
|
+
if not (isinstance(gui, str) and gui == "auto"):
|
|
115
|
+
return gui
|
|
116
|
+
env_gui = gui_from_env()
|
|
117
|
+
if env_gui is None:
|
|
118
|
+
return gui
|
|
119
|
+
return None if env_gui == "headless" else env_gui
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def add_gui_arguments(
|
|
123
|
+
parser: argparse.ArgumentParser, *, defaults: Optional[Dict[str, Any]] = None
|
|
124
|
+
) -> argparse._ArgumentGroup:
|
|
125
|
+
"""Add ``--gui``, ``--fullscreen``, ``--maximized``, ``--size WxH`` and ``--name`` to a parser.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
parser: Parser to extend.
|
|
129
|
+
defaults: Optional default values, keyed by destination
|
|
130
|
+
(``gui``, ``fullscreen``, ``maximized``, ``size``, ``name``).
|
|
131
|
+
A default set here is always reported by ``gui_kwargs_from_args``.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
The argument group holding the GUI options.
|
|
135
|
+
"""
|
|
136
|
+
if defaults is None:
|
|
137
|
+
defaults = {}
|
|
138
|
+
unknown = set(defaults) - {"gui", "fullscreen", "maximized", "size", "name"}
|
|
139
|
+
if unknown:
|
|
140
|
+
raise ValueError(f"unknown GUI argument defaults: {sorted(unknown)}")
|
|
141
|
+
group = parser.add_argument_group("GUI", "interactive_pipe backend and window options")
|
|
142
|
+
group.add_argument(
|
|
143
|
+
"--gui",
|
|
144
|
+
choices=GUI_CHOICES,
|
|
145
|
+
default=defaults.get("gui"),
|
|
146
|
+
help="GUI backend (default: auto, or $%s)" % GUI_ENV_VAR,
|
|
147
|
+
)
|
|
148
|
+
window = group.add_mutually_exclusive_group()
|
|
149
|
+
window.add_argument(
|
|
150
|
+
"--fullscreen",
|
|
151
|
+
action="store_true",
|
|
152
|
+
default=defaults.get("fullscreen", False),
|
|
153
|
+
help="open the window in fullscreen",
|
|
154
|
+
)
|
|
155
|
+
window.add_argument(
|
|
156
|
+
"--maximized",
|
|
157
|
+
action="store_true",
|
|
158
|
+
default=defaults.get("maximized", False),
|
|
159
|
+
help="open the window maximized",
|
|
160
|
+
)
|
|
161
|
+
default_size = defaults.get("size")
|
|
162
|
+
if isinstance(default_size, str):
|
|
163
|
+
default_size = _argparse_size(default_size)
|
|
164
|
+
window.add_argument(
|
|
165
|
+
"--size",
|
|
166
|
+
type=_argparse_size,
|
|
167
|
+
default=default_size,
|
|
168
|
+
metavar="WxH",
|
|
169
|
+
help="window size in pixels, e.g. 1280x800",
|
|
170
|
+
)
|
|
171
|
+
group.add_argument("--name", default=defaults.get("name"), help="window title")
|
|
172
|
+
return group
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def gui_kwargs_from_args(args: argparse.Namespace) -> Dict[str, Any]:
|
|
176
|
+
"""Keyword arguments for ``interactive_pipeline(**kwargs)`` from parsed arguments.
|
|
177
|
+
|
|
178
|
+
Only options that were given (or have a default) are included, so an empty
|
|
179
|
+
command line yields ``{}`` and keeps the decorator defaults
|
|
180
|
+
(and the environment overrides). ``--gui headless`` maps to ``gui=None``.
|
|
181
|
+
"""
|
|
182
|
+
kwargs: Dict[str, Any] = {}
|
|
183
|
+
gui = getattr(args, "gui", None)
|
|
184
|
+
if gui is not None:
|
|
185
|
+
kwargs["gui"] = None if gui == "headless" else gui
|
|
186
|
+
if getattr(args, "fullscreen", False):
|
|
187
|
+
kwargs["size"] = "fullscreen"
|
|
188
|
+
elif getattr(args, "maximized", False):
|
|
189
|
+
kwargs["size"] = "maximized"
|
|
190
|
+
elif getattr(args, "size", None) is not None:
|
|
191
|
+
kwargs["size"] = tuple(args.size)
|
|
192
|
+
name = getattr(args, "name", None)
|
|
193
|
+
if name is not None:
|
|
194
|
+
kwargs["name"] = name
|
|
195
|
+
return kwargs
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def parse_gui_args(
|
|
199
|
+
argv: Optional[Sequence[str]] = None, parser: Optional[argparse.ArgumentParser] = None
|
|
200
|
+
) -> Tuple[argparse.Namespace, Dict[str, Any]]:
|
|
201
|
+
"""Parse the command line and return ``(args, gui_kwargs)``.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
argv: Arguments to parse (defaults to ``sys.argv[1:]``).
|
|
205
|
+
parser: Your own parser to extend with the GUI options; a new one is
|
|
206
|
+
created when omitted.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
The parsed namespace and the keyword arguments for ``interactive_pipeline``.
|
|
210
|
+
"""
|
|
211
|
+
if parser is None:
|
|
212
|
+
parser = argparse.ArgumentParser()
|
|
213
|
+
add_gui_arguments(parser)
|
|
214
|
+
argv_list: Optional[List[str]] = list(argv) if argv is not None else None
|
|
215
|
+
args = parser.parse_args(argv_list)
|
|
216
|
+
return args, gui_kwargs_from_args(args)
|
|
@@ -2,6 +2,36 @@ import logging
|
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from typing import Any, Optional
|
|
4
4
|
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _values_equal(new_value: Any, old_value: Any) -> bool:
|
|
9
|
+
"""Equality that also works for array-like parameter values.
|
|
10
|
+
|
|
11
|
+
Filter parameters are usually scalars, but any keyword argument default
|
|
12
|
+
(e.g. a numpy kernel) ends up in the compared dicts, and ``dict != dict``
|
|
13
|
+
raises on arrays ("truth value of an array is ambiguous").
|
|
14
|
+
"""
|
|
15
|
+
if new_value is old_value:
|
|
16
|
+
return True
|
|
17
|
+
if isinstance(new_value, np.ndarray) or isinstance(old_value, np.ndarray):
|
|
18
|
+
return np.array_equal(np.asarray(new_value), np.asarray(old_value))
|
|
19
|
+
if isinstance(new_value, dict) and isinstance(old_value, dict):
|
|
20
|
+
return new_value.keys() == old_value.keys() and all(
|
|
21
|
+
_values_equal(new_value[key], old_value[key]) for key in new_value
|
|
22
|
+
)
|
|
23
|
+
if isinstance(new_value, (list, tuple)) and isinstance(old_value, (list, tuple)):
|
|
24
|
+
return (
|
|
25
|
+
type(new_value) is type(old_value)
|
|
26
|
+
and len(new_value) == len(old_value)
|
|
27
|
+
and all(_values_equal(a, b) for a, b in zip(new_value, old_value))
|
|
28
|
+
)
|
|
29
|
+
try:
|
|
30
|
+
return bool(new_value == old_value)
|
|
31
|
+
except (TypeError, ValueError):
|
|
32
|
+
# incomparable objects: conservatively consider them changed
|
|
33
|
+
return False
|
|
34
|
+
|
|
5
35
|
|
|
6
36
|
class CachedResults:
|
|
7
37
|
"""
|
|
@@ -88,7 +118,7 @@ class StateChange:
|
|
|
88
118
|
:param new_params: The new parameters to check.
|
|
89
119
|
:return: True if the parameters have changed or False otherwise.
|
|
90
120
|
"""
|
|
91
|
-
if self._stored_params is None or new_params
|
|
121
|
+
if self._stored_params is None or not _values_equal(new_params, self._stored_params):
|
|
92
122
|
self._stored_params = deepcopy(new_params)
|
|
93
123
|
self._update_needed = True
|
|
94
124
|
else:
|
|
@@ -22,6 +22,10 @@ _framework_state: ContextVar[Optional[FrameworkState]] = ContextVar("_ip_framewo
|
|
|
22
22
|
|
|
23
23
|
_user_context: ContextVar[Optional[Dict[str, Any]]] = ContextVar("_ip_user_context", default=None)
|
|
24
24
|
|
|
25
|
+
# name of the filter currently executing (set by filter.run), used to attribute
|
|
26
|
+
# event reads to filters
|
|
27
|
+
_current_filter: ContextVar[Optional[str]] = ContextVar("_ip_current_filter", default=None)
|
|
28
|
+
|
|
25
29
|
|
|
26
30
|
# ============================================================================
|
|
27
31
|
# Internal Helper Functions (not exported)
|
|
@@ -54,6 +58,11 @@ def _set_user_context(ctx: Optional[Dict[str, Any]]) -> None:
|
|
|
54
58
|
_user_context.set(ctx)
|
|
55
59
|
|
|
56
60
|
|
|
61
|
+
def _set_current_filter(name: Optional[str]) -> None:
|
|
62
|
+
"""Internal: Set the name of the running filter (called by filter.run)."""
|
|
63
|
+
_current_filter.set(name)
|
|
64
|
+
|
|
65
|
+
|
|
57
66
|
# ============================================================================
|
|
58
67
|
# Layout Proxy - Display Control
|
|
59
68
|
# ============================================================================
|
|
@@ -251,13 +260,22 @@ class _EventsProxy:
|
|
|
251
260
|
key binding.
|
|
252
261
|
"""
|
|
253
262
|
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _record_read(state: FrameworkState, name: str) -> None:
|
|
265
|
+
"""Register the running filter as a reader of the event (cache invalidation)."""
|
|
266
|
+
filter_name = _current_filter.get()
|
|
267
|
+
if filter_name is not None:
|
|
268
|
+
state.event_readers.setdefault(name, set()).add(filter_name)
|
|
269
|
+
|
|
254
270
|
def __getitem__(self, name: str) -> bool:
|
|
255
271
|
"""Strict access: raises KeyError if the event was never bound.
|
|
256
272
|
|
|
257
273
|
Raises:
|
|
258
274
|
RuntimeError: If called outside of filter execution context.
|
|
259
275
|
"""
|
|
260
|
-
|
|
276
|
+
state = _get_framework_state()
|
|
277
|
+
self._record_read(state, name)
|
|
278
|
+
return state.events[name]
|
|
261
279
|
|
|
262
280
|
def get(self, name: str, default: bool = False) -> bool:
|
|
263
281
|
"""Return the event flag, or `default` when the event is not bound.
|
|
@@ -265,15 +283,22 @@ class _EventsProxy:
|
|
|
265
283
|
Raises:
|
|
266
284
|
RuntimeError: If called outside of filter execution context.
|
|
267
285
|
"""
|
|
268
|
-
|
|
286
|
+
state = _get_framework_state()
|
|
287
|
+
self._record_read(state, name)
|
|
288
|
+
return state.events.get(name, default)
|
|
269
289
|
|
|
270
290
|
def __contains__(self, name: str) -> bool:
|
|
271
291
|
"""Check whether an event name is bound."""
|
|
272
|
-
|
|
292
|
+
state = _get_framework_state()
|
|
293
|
+
self._record_read(state, name)
|
|
294
|
+
return name in state.events
|
|
273
295
|
|
|
274
296
|
def keys(self):
|
|
275
|
-
"""Names of all bound events."""
|
|
276
|
-
|
|
297
|
+
"""Names of all bound events (registers the filter as a reader of each)."""
|
|
298
|
+
state = _get_framework_state()
|
|
299
|
+
for name in state.events:
|
|
300
|
+
self._record_read(state, name)
|
|
301
|
+
return state.events.keys()
|
|
277
302
|
|
|
278
303
|
def __repr__(self) -> str:
|
|
279
304
|
try:
|
|
@@ -368,10 +393,16 @@ class _ContextProxy:
|
|
|
368
393
|
|
|
369
394
|
def __getattr__(self, name: str) -> Any:
|
|
370
395
|
"""Get item from context using attribute-style access."""
|
|
396
|
+
if name.startswith("_"):
|
|
397
|
+
# dunder/private lookups come from Python internals and tooling
|
|
398
|
+
# (copy, pickle, IPython completion...), never from user context keys
|
|
399
|
+
raise AttributeError(name)
|
|
371
400
|
try:
|
|
372
401
|
return get_context()[name]
|
|
373
402
|
except KeyError:
|
|
374
|
-
raise AttributeError(
|
|
403
|
+
raise AttributeError(
|
|
404
|
+
f"Context has no attribute '{name}'. Available keys: {list(get_context().keys())}"
|
|
405
|
+
) from None
|
|
375
406
|
|
|
376
407
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
377
408
|
"""Set item in context using attribute-style access."""
|