interactive-pipe 0.8.5__tar.gz → 0.8.7__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.8.5/src/interactive_pipe.egg-info → interactive_pipe-0.8.7}/PKG-INFO +11 -4
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/pyproject.toml +1 -1
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/readme.md +9 -2
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/__init__.py +7 -2
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/cache.py +7 -5
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/engine.py +15 -11
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/filter.py +43 -24
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/graph.py +32 -25
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/pipeline.py +30 -19
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/audio.py +8 -5
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/curves.py +86 -65
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/data.py +16 -14
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/image.py +10 -8
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/parameters.py +24 -13
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/gradio_control.py +20 -13
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/gradio_gui.py +94 -30
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/gui.py +70 -19
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/mpl_control.py +27 -11
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/mpl_gui.py +71 -34
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/mpl_window.py +14 -3
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/nb_control.py +29 -15
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/nb_gui.py +33 -9
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/qt_control.py +93 -29
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/qt_gui.py +188 -74
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/window.py +16 -9
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/control.py +106 -22
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/keyboard.py +44 -11
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/pipeline.py +81 -54
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/choose_backend.py +27 -10
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/control_abbreviation.py +84 -41
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/filter_decorator.py +34 -16
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/keyword_args_analyzer.py +28 -13
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/pipeline_decorator.py +5 -2
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/thirdparty/images_openai_api.py +30 -13
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/thirdparty/music_spotify.py +9 -11
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7/src/interactive_pipe.egg-info}/PKG-INFO +11 -4
- interactive_pipe-0.8.7/test/test_cache.py +33 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_control_abbreviations.py +27 -25
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_core.py +9 -9
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_curves.py +33 -23
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_decorator.py +3 -3
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_engine.py +31 -26
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_filter.py +13 -12
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_headless.py +68 -47
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_image.py +1 -4
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_parameters.py +7 -6
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_recorder.py +17 -11
- interactive_pipe-0.8.5/test/test_cache.py +0 -33
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/LICENSE +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/setup.cfg +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/core/signature.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/helper/_private.py +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/SOURCES.txt +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/requires.txt +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/test/test_controller.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.7
|
|
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
|
|
@@ -49,6 +49,7 @@ Requires-Dist: gradio; extra == "full"
|
|
|
49
49
|
|:--:|
|
|
50
50
|
|Quick setup `pip install interactive-pipe` |
|
|
51
51
|
| [Project website](https://balthazarneveu.github.io/interactive_pipe/) |
|
|
52
|
+
|[](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml) |
|
|
52
53
|
|
|
53
54
|
</center>
|
|
54
55
|
|
|
@@ -106,18 +107,19 @@ pip install -e ".[full]"
|
|
|
106
107
|
|
|
107
108
|
## :scroll: Features
|
|
108
109
|
|
|
109
|
-
**Version 0.8.
|
|
110
|
+
**Version 0.8.7**
|
|
110
111
|
- Modular multi-image processing filters
|
|
111
112
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
112
113
|
- Support in jupyter notebooks
|
|
113
114
|
- Tuning sliders & check buttons with a GUI
|
|
114
115
|
- Cache intermediate results in RAM for much faster processing
|
|
115
116
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
116
|
-
- Support Curve plots (2D
|
|
117
|
+
- Support Curve plots (2D signalKeybos)
|
|
117
118
|
- :new: gradio backend (+allows sharing with others).
|
|
118
119
|
- :new: Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
|
|
119
120
|
- :new: Circular sliders for Qt Backend
|
|
120
121
|
- :new: Text prompt (`free_text=("Hello world!", None),`)
|
|
122
|
+
- :new: TimeControl (possibility to play/pause time using an incrementing timer)
|
|
121
123
|
|
|
122
124
|
|
|
123
125
|
|
|
@@ -165,6 +167,11 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
165
167
|
### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
166
168
|

|
|
167
169
|
|
|
170
|
+
[Tutorial on Hugging Face space](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
[Tutorial in a Colab notebook](https://colab.research.google.com/github/livalgo/interactive-pipe-examples/blob/main/interactive_pipe_tutorial.ipynb#scrollTo=U38Oh7coVKwf)
|
|
174
|
+
|
|
168
175
|
### Learn by examples
|
|
169
176
|
#### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
|
|
170
177
|
| GUI | Pipeline |
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|:--:|
|
|
6
6
|
|Quick setup `pip install interactive-pipe` |
|
|
7
7
|
| [Project website](https://balthazarneveu.github.io/interactive_pipe/) |
|
|
8
|
+
|[](https://github.com/balthazarneveu/interactive_pipe/actions/workflows/pytest.yaml) |
|
|
8
9
|
|
|
9
10
|
</center>
|
|
10
11
|
|
|
@@ -62,18 +63,19 @@ pip install -e ".[full]"
|
|
|
62
63
|
|
|
63
64
|
## :scroll: Features
|
|
64
65
|
|
|
65
|
-
**Version 0.8.
|
|
66
|
+
**Version 0.8.7**
|
|
66
67
|
- Modular multi-image processing filters
|
|
67
68
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
68
69
|
- Support in jupyter notebooks
|
|
69
70
|
- Tuning sliders & check buttons with a GUI
|
|
70
71
|
- Cache intermediate results in RAM for much faster processing
|
|
71
72
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
72
|
-
- Support Curve plots (2D
|
|
73
|
+
- Support Curve plots (2D signalKeybos)
|
|
73
74
|
- :new: gradio backend (+allows sharing with others).
|
|
74
75
|
- :new: Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
|
|
75
76
|
- :new: Circular sliders for Qt Backend
|
|
76
77
|
- :new: Text prompt (`free_text=("Hello world!", None),`)
|
|
78
|
+
- :new: TimeControl (possibility to play/pause time using an incrementing timer)
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
|
|
@@ -121,6 +123,11 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
121
123
|
### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
122
124
|

|
|
123
125
|
|
|
126
|
+
[Tutorial on Hugging Face space](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
[Tutorial in a Colab notebook](https://colab.research.google.com/github/livalgo/interactive-pipe-examples/blob/main/interactive_pipe_tutorial.ipynb#scrollTo=U38Oh7coVKwf)
|
|
130
|
+
|
|
124
131
|
### Learn by examples
|
|
125
132
|
#### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
|
|
126
133
|
| GUI | Pipeline |
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
__version__ = "0.8.
|
|
1
|
+
__version__ = "0.8.7"
|
|
2
2
|
from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
|
|
3
|
-
from interactive_pipe.headless.control import
|
|
3
|
+
from interactive_pipe.headless.control import (
|
|
4
|
+
Control,
|
|
5
|
+
CircularControl,
|
|
6
|
+
TextPrompt,
|
|
7
|
+
TimeControl,
|
|
8
|
+
)
|
|
4
9
|
from interactive_pipe.data_objects.curves import Curve, SingleCurve
|
|
5
10
|
from interactive_pipe.data_objects.image import Image
|
|
6
11
|
from interactive_pipe.headless.keyboard import KeyboardControl
|
|
@@ -48,8 +48,7 @@ class CachedResults:
|
|
|
48
48
|
:param new_params: The new parameters to check.
|
|
49
49
|
:return: True if an update is needed or False otherwise.
|
|
50
50
|
"""
|
|
51
|
-
change_state_from_params_check = self.state_change.has_changed(
|
|
52
|
-
new_params)
|
|
51
|
+
change_state_from_params_check = self.state_change.has_changed(new_params)
|
|
53
52
|
if self.force_change:
|
|
54
53
|
self.state_change.update_needed = True
|
|
55
54
|
self.force_change = False
|
|
@@ -64,8 +63,9 @@ class CachedResults:
|
|
|
64
63
|
"""
|
|
65
64
|
if self.name is not None:
|
|
66
65
|
logging.debug(f"OVERRIDE CACHE RESULTS - {self.name}")
|
|
67
|
-
self.result =
|
|
68
|
-
new_result)
|
|
66
|
+
self.result = (
|
|
67
|
+
new_result if not self.safe_buffer_deepcopy else deepcopy(new_result)
|
|
68
|
+
)
|
|
69
69
|
|
|
70
70
|
def __repr__(self) -> str:
|
|
71
71
|
return self.name
|
|
@@ -106,4 +106,6 @@ class StateChange:
|
|
|
106
106
|
self._update_needed = value
|
|
107
107
|
|
|
108
108
|
def __repr__(self) -> str:
|
|
109
|
-
return f"{self.name}: " + (
|
|
109
|
+
return f"{self.name}: " + (
|
|
110
|
+
"needs update" if self.update_needed else "no update needed"
|
|
111
|
+
)
|
|
@@ -4,7 +4,6 @@ import time
|
|
|
4
4
|
import traceback
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
from typing import List
|
|
7
|
-
from interactive_pipe.core.cache import CachedResults
|
|
8
7
|
from interactive_pipe.core.filter import FilterCore
|
|
9
8
|
|
|
10
9
|
|
|
@@ -22,13 +21,12 @@ class PipelineEngine:
|
|
|
22
21
|
for input_index, inp in enumerate(imglst):
|
|
23
22
|
if self.safe_input_buffer_deepcopy:
|
|
24
23
|
result[input_index] = deepcopy(inp)
|
|
25
|
-
logging.debug(
|
|
26
|
-
f"<<< Deepcopy input images {input_index}")
|
|
24
|
+
logging.debug(f"<<< Deepcopy input images {input_index}")
|
|
27
25
|
else:
|
|
28
26
|
result[input_index] = inp
|
|
29
27
|
elif isinstance(imglst, dict):
|
|
30
28
|
if self.safe_input_buffer_deepcopy:
|
|
31
|
-
logging.debug(
|
|
29
|
+
logging.debug("<<< Deepcopy input images")
|
|
32
30
|
result = deepcopy(imglst)
|
|
33
31
|
else:
|
|
34
32
|
result = imglst
|
|
@@ -43,32 +41,38 @@ class PipelineEngine:
|
|
|
43
41
|
# 1 | 0 | True -> cache with no change, skip the calculation
|
|
44
42
|
# 1 | 1 | False -> cache and result changed, cannot skip so calculate
|
|
45
43
|
skip_calculation &= (prc.cache_mem is not None) and (
|
|
46
|
-
not prc.cache_mem.has_changed(prc.values)
|
|
44
|
+
not prc.cache_mem.has_changed(prc.values)
|
|
45
|
+
)
|
|
47
46
|
|
|
48
47
|
if skip_calculation and self.cache:
|
|
49
|
-
logging.debug(
|
|
50
|
-
f"--> Load cached outputs from filter {idx}: {prc.name}")
|
|
48
|
+
logging.debug(f"--> Load cached outputs from filter {idx}: {prc.name}")
|
|
51
49
|
out = prc.cache_mem.result
|
|
52
50
|
previous_calculation = False
|
|
53
51
|
else:
|
|
54
52
|
logging.debug(
|
|
55
|
-
("... " if previous_calculation else "!!! ")
|
|
53
|
+
("... " if previous_calculation else "!!! ")
|
|
54
|
+
+ f"Calculating {prc.name}"
|
|
55
|
+
)
|
|
56
56
|
try:
|
|
57
57
|
routing_in = []
|
|
58
58
|
if prc.inputs:
|
|
59
59
|
routing_in = [
|
|
60
|
-
result[idi] if idi is not None else None
|
|
60
|
+
result[idi] if idi is not None else None
|
|
61
|
+
for idi in prc.inputs
|
|
62
|
+
]
|
|
61
63
|
logging.debug(f"in types-> {[type(inp) for inp in routing_in]}")
|
|
62
64
|
out = prc.run(*routing_in)
|
|
63
65
|
if out is not None:
|
|
64
66
|
logging.debug(f"out types-> {[type(ou) for ou in out]}")
|
|
65
67
|
except Exception as e:
|
|
66
|
-
logging.error(f
|
|
68
|
+
logging.error(f"Error in {prc.name} filter:")
|
|
67
69
|
logging.error(e)
|
|
68
70
|
traceback.print_exc()
|
|
69
71
|
sys.exit(1)
|
|
70
72
|
previous_calculation = True
|
|
71
|
-
if
|
|
73
|
+
if (
|
|
74
|
+
self.cache and prc.cache_mem is not None
|
|
75
|
+
): # cache result if cache available
|
|
72
76
|
logging.debug(f"<-- Storing result from {prc.name}")
|
|
73
77
|
prc.cache_mem.update(out)
|
|
74
78
|
# put prc output at the right position within result vector
|
|
@@ -5,13 +5,28 @@ from typing import Callable, List, Optional, Union, Tuple, Any
|
|
|
5
5
|
from interactive_pipe.core.cache import CachedResults
|
|
6
6
|
from interactive_pipe.core.signature import analyze_apply_fn_signature
|
|
7
7
|
|
|
8
|
-
EQUIVALENT_STATE_KEYS = [
|
|
8
|
+
EQUIVALENT_STATE_KEYS = [
|
|
9
|
+
"global_params",
|
|
10
|
+
"global_parameters",
|
|
11
|
+
"global_state",
|
|
12
|
+
"global_context",
|
|
13
|
+
"context",
|
|
14
|
+
"state",
|
|
15
|
+
]
|
|
9
16
|
|
|
10
17
|
|
|
11
18
|
class PureFilter:
|
|
12
|
-
def __init__(
|
|
13
|
-
self
|
|
14
|
-
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
apply_fn: Optional[Callable] = None,
|
|
22
|
+
name: Optional[str] = None,
|
|
23
|
+
default_params: dict = {},
|
|
24
|
+
):
|
|
25
|
+
self.name = (
|
|
26
|
+
name
|
|
27
|
+
if name
|
|
28
|
+
else (self.__class__.__name__ if not apply_fn else apply_fn.__name__)
|
|
29
|
+
)
|
|
15
30
|
if apply_fn is not None:
|
|
16
31
|
self.apply = apply_fn
|
|
17
32
|
self._global_params = {}
|
|
@@ -34,7 +49,8 @@ class PureFilter:
|
|
|
34
49
|
def check_apply_signature(self):
|
|
35
50
|
if not hasattr(self, "__args_names") or not hasattr(self, "__kwargs_names"):
|
|
36
51
|
self.__args_names, self.__kwargs_names = analyze_apply_fn_signature(
|
|
37
|
-
self.apply
|
|
52
|
+
self.apply
|
|
53
|
+
)
|
|
38
54
|
self.signature = (self.__args_names, self.__kwargs_names)
|
|
39
55
|
else: # skip computing signature
|
|
40
56
|
pass
|
|
@@ -53,8 +69,7 @@ class PureFilter:
|
|
|
53
69
|
|
|
54
70
|
@values.setter
|
|
55
71
|
def values(self, new_values):
|
|
56
|
-
assert isinstance(
|
|
57
|
-
new_values, dict), f"{new_values} is not a dictionary"
|
|
72
|
+
assert isinstance(new_values, dict), f"{new_values} is not a dictionary"
|
|
58
73
|
self._values = {**self._values, **new_values}
|
|
59
74
|
|
|
60
75
|
def run(self, *imgs) -> Tuple[Any]:
|
|
@@ -62,14 +77,16 @@ class PureFilter:
|
|
|
62
77
|
assert isinstance(self.values, dict), f"{self.values}"
|
|
63
78
|
self.check_apply_signature()
|
|
64
79
|
for key, val in self.values.items():
|
|
65
|
-
assert
|
|
80
|
+
assert (
|
|
81
|
+
key in self.__kwargs_names.keys()
|
|
66
82
|
), f"{self.name} : {key} not in {self.__kwargs_names.keys()}"
|
|
67
83
|
global_key_found = False
|
|
68
84
|
for global_key in EQUIVALENT_STATE_KEYS:
|
|
69
85
|
if global_key in self.__kwargs_names.keys():
|
|
70
86
|
# special key to provide the context dictionary
|
|
71
87
|
out = self.apply(
|
|
72
|
-
*imgs, **{**{global_key: self.global_params}, **self.values}
|
|
88
|
+
*imgs, **{**{global_key: self.global_params}, **self.values}
|
|
89
|
+
)
|
|
73
90
|
global_key_found = True
|
|
74
91
|
break
|
|
75
92
|
if not global_key_found:
|
|
@@ -80,16 +97,16 @@ class PureFilter:
|
|
|
80
97
|
class FilterCore(PureFilter):
|
|
81
98
|
"""PureFilter with cache storage + routing nodes defined (inputs & outputs fields)"""
|
|
82
99
|
|
|
83
|
-
def __init__(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
apply_fn: Callable = None,
|
|
103
|
+
name: Optional[str] = None,
|
|
104
|
+
default_params: dict = {},
|
|
105
|
+
inputs: List[Union[int, str]] = [0],
|
|
106
|
+
outputs: List[Union[int, str]] = [0],
|
|
107
|
+
cache=True,
|
|
108
|
+
):
|
|
109
|
+
super().__init__(apply_fn=apply_fn, name=name, default_params=default_params)
|
|
93
110
|
self.inputs = inputs
|
|
94
111
|
self.outputs = outputs
|
|
95
112
|
self.cache = cache
|
|
@@ -104,7 +121,8 @@ class FilterCore(PureFilter):
|
|
|
104
121
|
def run(self, *imgs) -> Tuple[Any]:
|
|
105
122
|
if imgs:
|
|
106
123
|
assert len(imgs) == len(
|
|
107
|
-
self.inputs
|
|
124
|
+
self.inputs
|
|
125
|
+
), "number of inputs shall match what's expected"
|
|
108
126
|
if self.inputs is None:
|
|
109
127
|
if imgs is not None:
|
|
110
128
|
assert len(imgs) == 0
|
|
@@ -116,20 +134,21 @@ class FilterCore(PureFilter):
|
|
|
116
134
|
return None
|
|
117
135
|
if isinstance(out, tuple) or isinstance(out, list):
|
|
118
136
|
assert len(out) >= len(
|
|
119
|
-
self.outputs
|
|
137
|
+
self.outputs
|
|
138
|
+
), "number of outputs shall be at least greater or equal to what's expected by the filter"
|
|
120
139
|
return out
|
|
121
140
|
|
|
122
141
|
else:
|
|
123
142
|
logging.debug(
|
|
124
|
-
f"need to return a tuple when you have a single element out {type(out)}"
|
|
143
|
+
f"need to return a tuple when you have a single element out {type(out)}"
|
|
144
|
+
)
|
|
125
145
|
assert len(self.outputs) == 1, "returning a single element!"
|
|
126
146
|
return (out,)
|
|
127
147
|
|
|
128
148
|
def __repr__(self) -> str:
|
|
129
149
|
descr = f"{self.name}: "
|
|
130
150
|
descr += "(" + (", ".join([f"{it}" for it in self.inputs])) + ")"
|
|
131
|
-
descr += " -> " +
|
|
132
|
-
"(" + ", ".join([f"{it}" for it in self.outputs]) + ")"
|
|
151
|
+
descr += " -> " + "(" + ", ".join([f"{it}" for it in self.outputs]) + ")"
|
|
133
152
|
# descr += "\n"
|
|
134
153
|
return descr
|
|
135
154
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
|
-
from typing import List, Callable,
|
|
4
|
+
from typing import List, Callable, Optional, Union
|
|
5
5
|
from interactive_pipe.core.filter import FilterCore
|
|
6
6
|
from interactive_pipe.core.signature import analyze_apply_fn_signature
|
|
7
7
|
|
|
@@ -17,7 +17,9 @@ def get_name(node: ast.NodeVisitor) -> Union[str, List[str], None]:
|
|
|
17
17
|
return None
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def flatten_target_names(
|
|
20
|
+
def flatten_target_names(
|
|
21
|
+
targets: List[Union[list, str, None]], mapping_function: Optional[Callable] = None
|
|
22
|
+
) -> List[str]:
|
|
21
23
|
output_names = []
|
|
22
24
|
for target in targets:
|
|
23
25
|
target_name = mapping_function(target) if mapping_function else target
|
|
@@ -42,26 +44,27 @@ def get_call_graph(func: Callable, global_context=None) -> dict:
|
|
|
42
44
|
if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
|
|
43
45
|
function_name = get_name(node.value.func)
|
|
44
46
|
input_names = [get_name(arg) for arg in node.value.args]
|
|
45
|
-
results.append(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
results.append(
|
|
48
|
+
{
|
|
49
|
+
"function_name": function_name,
|
|
50
|
+
"function_object": global_context[function_name],
|
|
51
|
+
"args": input_names,
|
|
52
|
+
"returns": [],
|
|
53
|
+
"output_names": [],
|
|
54
|
+
}
|
|
55
|
+
)
|
|
52
56
|
logging.debug(f"Function without assignment {function_name}")
|
|
53
57
|
elif isinstance(node, ast.Assign):
|
|
54
58
|
targets = node.targets
|
|
55
59
|
value = node.value
|
|
56
60
|
if isinstance(value, ast.Call):
|
|
57
61
|
function_name = get_name(value.func)
|
|
58
|
-
logging.debug(
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
logging.debug(f"classic function with assignment {function_name}")
|
|
63
|
+
assert (
|
|
64
|
+
function_name in global_context.keys()
|
|
61
65
|
), f"Probably mispelled {function_name}"
|
|
62
66
|
input_names = [get_name(arg) for arg in value.args]
|
|
63
|
-
output_names = flatten_target_names(
|
|
64
|
-
targets, mapping_function=get_name)
|
|
67
|
+
output_names = flatten_target_names(targets, mapping_function=get_name)
|
|
65
68
|
function_object = global_context[function_name]
|
|
66
69
|
if isinstance(function_object, FilterCore):
|
|
67
70
|
# Case of a filter instance
|
|
@@ -79,17 +82,21 @@ def get_call_graph(func: Callable, global_context=None) -> dict:
|
|
|
79
82
|
function_apply = global_context[function_name]
|
|
80
83
|
else:
|
|
81
84
|
raise TypeError(
|
|
82
|
-
f"Not supported {function_name} - should be function or FilterCore"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
f"Not supported {function_name} - should be function or FilterCore"
|
|
86
|
+
)
|
|
87
|
+
results.append(
|
|
88
|
+
{
|
|
89
|
+
"function_name": function_name,
|
|
90
|
+
"function_object": function_apply,
|
|
91
|
+
"signature": {"args": sig[0], "kwargs": sig[1]},
|
|
92
|
+
"args": input_names,
|
|
93
|
+
"returns": output_names,
|
|
94
|
+
}
|
|
95
|
+
)
|
|
90
96
|
main_function = tree.body[0]
|
|
91
|
-
outputs = [
|
|
92
|
-
|
|
97
|
+
outputs = [
|
|
98
|
+
get_name(ret.value) for ret in main_function.body if isinstance(ret, ast.Return)
|
|
99
|
+
]
|
|
93
100
|
assert len(outputs) <= 1, "cannot return several times!"
|
|
94
101
|
outputs = flatten_target_names(outputs, mapping_function=None)
|
|
95
102
|
inputs, kwargs = analyze_apply_fn_signature(func)
|
|
@@ -98,7 +105,7 @@ def get_call_graph(func: Callable, global_context=None) -> dict:
|
|
|
98
105
|
"call_graph": results,
|
|
99
106
|
"args": inputs,
|
|
100
107
|
"kwargs": kwargs,
|
|
101
|
-
"returns": outputs
|
|
108
|
+
"returns": outputs,
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
return graph
|
|
@@ -24,15 +24,17 @@ class PipelineCore:
|
|
|
24
24
|
global_context: Optional[dict] = None, # alias for global_params
|
|
25
25
|
context: Optional[dict] = None, # alias for global_params
|
|
26
26
|
state: Optional[dict] = None, # alias for global_params
|
|
27
|
-
outputs:
|
|
28
|
-
safe_input_buffer_deepcopy: bool = True
|
|
27
|
+
outputs: Optional[list] = None,
|
|
28
|
+
safe_input_buffer_deepcopy: bool = True,
|
|
29
29
|
):
|
|
30
30
|
if not all(isinstance(f, FilterCore) for f in filters):
|
|
31
31
|
raise ValueError(
|
|
32
|
-
f"All elements in 'filters' must be instances of 'Filter'. {[type(f) for f in filters]}"
|
|
32
|
+
f"All elements in 'filters' must be instances of 'Filter'. {[type(f) for f in filters]}"
|
|
33
|
+
)
|
|
33
34
|
self.filters = filters
|
|
34
35
|
self.engine = PipelineEngine(
|
|
35
|
-
cache, safe_input_buffer_deepcopy=safe_input_buffer_deepcopy
|
|
36
|
+
cache, safe_input_buffer_deepcopy=safe_input_buffer_deepcopy
|
|
37
|
+
)
|
|
36
38
|
if global_parameters is not None:
|
|
37
39
|
global_params = global_parameters
|
|
38
40
|
elif global_context is not None:
|
|
@@ -52,7 +54,8 @@ class PipelineCore:
|
|
|
52
54
|
self.reset_cache()
|
|
53
55
|
if inputs is None:
|
|
54
56
|
logging.warning(
|
|
55
|
-
"Setting a pipeline without input - use inputs=[] to get rid of this warning"
|
|
57
|
+
"Setting a pipeline without input - use inputs=[] to get rid of this warning"
|
|
58
|
+
)
|
|
56
59
|
self.inputs_routing = []
|
|
57
60
|
else:
|
|
58
61
|
self.inputs_routing = inputs
|
|
@@ -60,8 +63,7 @@ class PipelineCore:
|
|
|
60
63
|
self.__initialized_inputs = False
|
|
61
64
|
if outputs is None:
|
|
62
65
|
outputs = self.filters[-1].outputs
|
|
63
|
-
logging.warning(
|
|
64
|
-
f"Using last filter outputs {self.filters[-1]} {outputs}")
|
|
66
|
+
logging.warning(f"Using last filter outputs {self.filters[-1]} {outputs}")
|
|
65
67
|
|
|
66
68
|
self.outputs = outputs # output indexes (routing)
|
|
67
69
|
# You need to set the values to their default_value for each filter
|
|
@@ -74,8 +76,7 @@ class PipelineCore:
|
|
|
74
76
|
filter.reset_cache()
|
|
75
77
|
|
|
76
78
|
def run(self) -> list:
|
|
77
|
-
"""Useful for standalone python acess without gui or disk write
|
|
78
|
-
"""
|
|
79
|
+
"""Useful for standalone python acess without gui or disk write"""
|
|
79
80
|
return self.engine.run(self.filters, imglst=self.inputs)
|
|
80
81
|
|
|
81
82
|
def _reset_global_params(self):
|
|
@@ -110,10 +111,12 @@ class PipelineCore:
|
|
|
110
111
|
"""
|
|
111
112
|
available_filters_names = [filt.name for filt in self.filters]
|
|
112
113
|
for filter_name in new_parameters.keys():
|
|
113
|
-
assert filter_name in available_filters_names,
|
|
114
|
-
f"does not exist {available_filters_names}"
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
assert filter_name in available_filters_names, (
|
|
115
|
+
f"filter {filter_name}" + f"does not exist {available_filters_names}"
|
|
116
|
+
)
|
|
117
|
+
self.filters[available_filters_names.index(filter_name)].values = (
|
|
118
|
+
new_parameters[filter_name]
|
|
119
|
+
)
|
|
117
120
|
|
|
118
121
|
@property
|
|
119
122
|
def inputs(self):
|
|
@@ -126,13 +129,16 @@ class PipelineCore:
|
|
|
126
129
|
if isinstance(inputs, dict):
|
|
127
130
|
provided_keys = list(inputs.keys())
|
|
128
131
|
for idx, input_name in enumerate(self.inputs_routing):
|
|
129
|
-
assert
|
|
132
|
+
assert (
|
|
133
|
+
input_name in provided_keys
|
|
134
|
+
), f"{input_name} is not among {provided_keys}"
|
|
130
135
|
self.__inputs = inputs
|
|
131
136
|
elif isinstance(inputs, list) or isinstance(inputs, tuple):
|
|
132
137
|
# inputs is a list or a tuple
|
|
133
|
-
assert len(inputs) == len(
|
|
134
|
-
|
|
135
|
-
f"inputs vs expected:{len(self.inputs_routing)}"
|
|
138
|
+
assert len(inputs) == len(self.inputs_routing), (
|
|
139
|
+
f"wrong amount of inputs\nprovided {len(inputs)}"
|
|
140
|
+
+ f"inputs vs expected:{len(self.inputs_routing)}"
|
|
141
|
+
)
|
|
136
142
|
self.__inputs = {}
|
|
137
143
|
for idx, input_name in enumerate(self.inputs_routing):
|
|
138
144
|
self.__inputs[input_name] = inputs[idx]
|
|
@@ -145,8 +151,13 @@ class PipelineCore:
|
|
|
145
151
|
# similar to having no input, but explicitly saying that we have initialized it.
|
|
146
152
|
self.__inputs = None
|
|
147
153
|
else:
|
|
148
|
-
assert self.inputs_routing is None or (
|
|
149
|
-
|
|
154
|
+
assert self.inputs_routing is None or (
|
|
155
|
+
(
|
|
156
|
+
isinstance(self.inputs_routing, tuple)
|
|
157
|
+
or isinstance(self.inputs_routing, list)
|
|
158
|
+
)
|
|
159
|
+
and len(self.inputs_routing) == 0
|
|
160
|
+
)
|
|
150
161
|
self.__inputs = None
|
|
151
162
|
self.__initialized_inputs = True
|
|
152
163
|
self.reset_cache()
|
{interactive_pipe-0.8.5 → interactive_pipe-0.8.7}/src/interactive_pipe/data_objects/audio.py
RENAMED
|
@@ -5,6 +5,7 @@ from io import BytesIO
|
|
|
5
5
|
import base64
|
|
6
6
|
import logging
|
|
7
7
|
from typing import Union, Tuple
|
|
8
|
+
|
|
8
9
|
WAVIO_AVAILABLE = False
|
|
9
10
|
WAVIO = "wavio"
|
|
10
11
|
try:
|
|
@@ -16,7 +17,9 @@ except ImportError:
|
|
|
16
17
|
__iter_audio_player = 0
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def audio_to_html(
|
|
20
|
+
def audio_to_html(
|
|
21
|
+
audio: Union[None, str, Path, Tuple[int, np.ndarray]], controls=True
|
|
22
|
+
) -> str:
|
|
20
23
|
if audio is None:
|
|
21
24
|
logging.debug("No audio to display")
|
|
22
25
|
return ""
|
|
@@ -33,11 +36,11 @@ def audio_to_html(audio: Union[None, str, Path, Tuple[int, np.ndarray]], control
|
|
|
33
36
|
audio_base64 = base64.b64encode(audio_bytes.read()).decode("utf-8")
|
|
34
37
|
audio_player = f'<audio src="data:audio/mpeg;base64,{audio_base64}"'
|
|
35
38
|
if controls:
|
|
36
|
-
audio_player +=
|
|
37
|
-
audio_player +=
|
|
39
|
+
audio_player += " controls"
|
|
40
|
+
audio_player += " autoplay></audio>"
|
|
38
41
|
global __iter_audio_player
|
|
39
42
|
# to avoid reusing the same exact id and thus not updating the audio player
|
|
40
|
-
audio_player += "<p hidden>"+str(__iter_audio_player)+"</p>"
|
|
43
|
+
audio_player += "<p hidden>" + str(__iter_audio_player) + "</p>"
|
|
41
44
|
__iter_audio_player += 1
|
|
42
45
|
|
|
43
46
|
return audio_player
|
|
@@ -98,5 +101,5 @@ class Audio(Data):
|
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
if __name__ == "__main__":
|
|
101
|
-
audio_sample_path = Path("demo")/"audio"/"rabbit.mp4"
|
|
104
|
+
audio_sample_path = Path("demo") / "audio" / "rabbit.mp4"
|
|
102
105
|
rate, data = Audio.load_audio(audio_sample_path)
|