interactive-pipe 0.8.4__tar.gz → 0.8.6__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.4/src/interactive_pipe.egg-info → interactive_pipe-0.8.6}/PKG-INFO +7 -2
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/pyproject.toml +1 -1
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/readme.md +6 -1
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/__init__.py +1 -1
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/cache.py +7 -5
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/engine.py +15 -11
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/filter.py +43 -24
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/graph.py +32 -25
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/pipeline.py +30 -19
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/data_objects/audio.py +8 -5
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/data_objects/curves.py +86 -65
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/data_objects/data.py +16 -14
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/data_objects/image.py +10 -8
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/data_objects/parameters.py +24 -13
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/gradio_control.py +20 -13
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/gradio_gui.py +98 -30
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/gui.py +29 -19
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/mpl_control.py +27 -11
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/mpl_gui.py +71 -34
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/mpl_window.py +17 -3
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/nb_control.py +42 -14
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/nb_gui.py +33 -9
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/qt_control.py +93 -29
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/qt_gui.py +165 -71
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/window.py +16 -9
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/headless/control.py +78 -22
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/headless/keyboard.py +44 -11
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/headless/pipeline.py +81 -54
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/choose_backend.py +23 -10
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/control_abbreviation.py +84 -41
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/filter_decorator.py +34 -16
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/keyword_args_analyzer.py +28 -13
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/pipeline_decorator.py +12 -3
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/thirdparty/images_openai_api.py +30 -13
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/thirdparty/music_spotify.py +9 -11
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6/src/interactive_pipe.egg-info}/PKG-INFO +7 -2
- interactive_pipe-0.8.6/test/test_cache.py +33 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_control_abbreviations.py +27 -25
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_core.py +9 -9
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_curves.py +33 -23
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_decorator.py +3 -3
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_engine.py +31 -26
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_filter.py +13 -12
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_headless.py +68 -47
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_image.py +1 -4
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_parameters.py +7 -6
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_recorder.py +17 -11
- interactive_pipe-0.8.4/test/test_cache.py +0 -33
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/LICENSE +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/setup.cfg +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/core/signature.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe/helper/_private.py +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe.egg-info/SOURCES.txt +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe.egg-info/requires.txt +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.8.4 → interactive_pipe-0.8.6}/test/test_controller.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6
|
|
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
|
|
@@ -106,7 +106,7 @@ pip install -e ".[full]"
|
|
|
106
106
|
|
|
107
107
|
## :scroll: Features
|
|
108
108
|
|
|
109
|
-
**Version 0.8.
|
|
109
|
+
**Version 0.8.6**
|
|
110
110
|
- Modular multi-image processing filters
|
|
111
111
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
112
112
|
- Support in jupyter notebooks
|
|
@@ -165,6 +165,11 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
165
165
|
### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
166
166
|

|
|
167
167
|
|
|
168
|
+
[Tutorial on Hugging Face space](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
[Tutorial in a Colab notebook](https://colab.research.google.com/github/livalgo/interactive-pipe-examples/blob/main/interactive_pipe_tutorial.ipynb#scrollTo=U38Oh7coVKwf)
|
|
172
|
+
|
|
168
173
|
### Learn by examples
|
|
169
174
|
#### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
|
|
170
175
|
| GUI | Pipeline |
|
|
@@ -62,7 +62,7 @@ pip install -e ".[full]"
|
|
|
62
62
|
|
|
63
63
|
## :scroll: Features
|
|
64
64
|
|
|
65
|
-
**Version 0.8.
|
|
65
|
+
**Version 0.8.6**
|
|
66
66
|
- Modular multi-image processing filters
|
|
67
67
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
68
68
|
- Support in jupyter notebooks
|
|
@@ -121,6 +121,11 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
121
121
|
### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
122
122
|

|
|
123
123
|
|
|
124
|
+
[Tutorial on Hugging Face space](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
[Tutorial in a Colab notebook](https://colab.research.google.com/github/livalgo/interactive-pipe-examples/blob/main/interactive_pipe_tutorial.ipynb#scrollTo=U38Oh7coVKwf)
|
|
128
|
+
|
|
124
129
|
### Learn by examples
|
|
125
130
|
#### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
|
|
126
131
|
| GUI | Pipeline |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.8.
|
|
1
|
+
__version__ = "0.8.6"
|
|
2
2
|
from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
|
|
3
3
|
from interactive_pipe.headless.control import Control, CircularControl, TextPrompt
|
|
4
4
|
from interactive_pipe.data_objects.curves import Curve, SingleCurve
|
|
@@ -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.4 → interactive_pipe-0.8.6}/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)
|