interactive-pipe 0.5.8__tar.gz → 0.6.0__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.5.8 → interactive_pipe-0.6.0}/PKG-INFO +35 -3
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/pyproject.toml +38 -2
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/readme.md +12 -2
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/__init__.py +2 -2
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/cache.py +4 -4
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/engine.py +6 -3
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/filter.py +14 -10
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/graph.py +16 -9
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/pipeline.py +21 -14
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/signature.py +2 -1
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/data_objects/curves.py +120 -99
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/data_objects/data.py +26 -16
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/data_objects/image.py +18 -17
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/data_objects/parameters.py +12 -10
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/gui.py +33 -27
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/mpl_control.py +20 -11
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/mpl_gui.py +38 -23
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/mpl_window.py +14 -11
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/nb_control.py +19 -8
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/nb_gui.py +15 -12
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/qt_control.py +65 -46
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/qt_gui.py +169 -111
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/window.py +12 -8
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/headless/control.py +23 -16
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/headless/keyboard.py +15 -14
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/headless/pipeline.py +71 -42
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/_private.py +1 -1
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/choose_backend.py +9 -8
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/control_abbreviation.py +50 -32
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/filter_decorator.py +29 -19
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/keyword_args_analyzer.py +22 -18
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/pipeline_decorator.py +7 -5
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/thirdparty/images_openai_api.py +23 -17
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/thirdparty/music_spotify.py +7 -5
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe.egg-info/PKG-INFO +36 -4
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe.egg-info/SOURCES.txt +1 -0
- interactive_pipe-0.6.0/src/interactive_pipe.egg-info/requires.txt +27 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_cache.py +7 -11
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_control_abbreviations.py +9 -8
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_controller.py +2 -1
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_core.py +1 -10
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_curves.py +24 -17
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_decorator.py +6 -4
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_engine.py +26 -15
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_headless.py +41 -28
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_image.py +3 -1
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_parameters.py +4 -2
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_recorder.py +12 -13
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/LICENSE +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/setup.cfg +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.5.8 → interactive_pipe-0.6.0}/test/test_filter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -11,8 +11,34 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: matplotlib>=3.5.3
|
|
15
|
+
Requires-Dist: numpy>=1.21.6
|
|
16
|
+
Requires-Dist: Pillow>=9.0.1
|
|
17
|
+
Requires-Dist: PyYAML>=5.4.1
|
|
18
|
+
Provides-Extra: qt6
|
|
19
|
+
Requires-Dist: PyQt6>=6.5.2; extra == "qt6"
|
|
20
|
+
Requires-Dist: PyQt6_sip>=13.5.2; extra == "qt6"
|
|
21
|
+
Provides-Extra: qt5
|
|
22
|
+
Requires-Dist: PyQt5>=5.15.9; extra == "qt5"
|
|
23
|
+
Requires-Dist: PyQt5_sip>=12.12.1; extra == "qt5"
|
|
24
|
+
Provides-Extra: notebook
|
|
25
|
+
Requires-Dist: ipywidgets>=8.0.7; extra == "notebook"
|
|
26
|
+
Provides-Extra: pytest
|
|
27
|
+
Requires-Dist: opencv_python_headless>=4.8.1.78; extra == "pytest"
|
|
28
|
+
Requires-Dist: pytest>=6.2.5; extra == "pytest"
|
|
29
|
+
Provides-Extra: full
|
|
30
|
+
Requires-Dist: PyQt6>=6.5.2; extra == "full"
|
|
31
|
+
Requires-Dist: PyQt6_sip>=13.5.2; extra == "full"
|
|
32
|
+
Requires-Dist: opencv_python_headless>=4.8.1.78; extra == "full"
|
|
33
|
+
Requires-Dist: pytest>=6.2.5; extra == "full"
|
|
34
|
+
Requires-Dist: ipywidgets>=8.0.7; extra == "full"
|
|
35
|
+
Requires-Dist: pandas; extra == "full"
|
|
14
36
|
|
|
15
37
|
# `interactive_pipe`
|
|
38
|
+
|
|
39
|
+
Quick setup
|
|
40
|
+
`pip install interactive-pipe[full]`
|
|
41
|
+
|
|
16
42
|
### Concept
|
|
17
43
|
- Develop an algorithm while debugging visually with plots, while checking robustness & continuity to parameters change.
|
|
18
44
|
- Magically create a graphical interface to easily demonstrate a concept or simply tune your algorithm.
|
|
@@ -33,7 +59,7 @@ License-File: LICENSE
|
|
|
33
59
|
----------------------
|
|
34
60
|
|
|
35
61
|
### Setup
|
|
36
|
-
`
|
|
62
|
+
`pip install interactive-pipe[full]`
|
|
37
63
|
|
|
38
64
|
----------------------
|
|
39
65
|
|
|
@@ -56,7 +82,7 @@ License-File: LICENSE
|
|
|
56
82
|
----------------------
|
|
57
83
|
|
|
58
84
|
## :scroll: Features
|
|
59
|
-
**Version 0.
|
|
85
|
+
**Version 0.6.0**
|
|
60
86
|
- Modular multi-image processing filters
|
|
61
87
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
62
88
|
- Support in jupyter notebooks
|
|
@@ -117,6 +143,12 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
117
143
|
# Tutorials
|
|
118
144
|
|
|
119
145
|
### Learn by examples
|
|
146
|
+
#### [Basic image processing](/demo/multi_image.py)
|
|
147
|
+
| GUI | Pipeline |
|
|
148
|
+
|:--:|:--:|
|
|
149
|
+
| |  |
|
|
150
|
+
|
|
151
|
+

|
|
120
152
|
|
|
121
153
|
#### [Speech exploration notebook](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
|
|
122
154
|

|
|
@@ -5,10 +5,17 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "interactive_pipe"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.6.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Balthazar Neveu", email="balthazarneveu@gmail.com" },
|
|
11
11
|
]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"matplotlib>=3.5.3",
|
|
14
|
+
"numpy>=1.21.6",
|
|
15
|
+
"Pillow>=9.0.1",
|
|
16
|
+
"PyYAML>=5.4.1",
|
|
17
|
+
]
|
|
18
|
+
|
|
12
19
|
description = "Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!"
|
|
13
20
|
readme = "readme.md"
|
|
14
21
|
requires-python = ">=3.7"
|
|
@@ -20,4 +27,33 @@ classifiers = [
|
|
|
20
27
|
|
|
21
28
|
[project.urls]
|
|
22
29
|
"Homepage" = "https://github.com/balthazarneveu/interactive_pipe"
|
|
23
|
-
"Bug Tracker" = "https://github.com/balthazarneveu/interactive_pipe/issues"
|
|
30
|
+
"Bug Tracker" = "https://github.com/balthazarneveu/interactive_pipe/issues"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
qt6=[
|
|
34
|
+
"PyQt6>=6.5.2",
|
|
35
|
+
"PyQt6_sip>=13.5.2",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
qt5=[
|
|
39
|
+
"PyQt5>=5.15.9",
|
|
40
|
+
"PyQt5_sip>=12.12.1",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
notebook=[
|
|
44
|
+
"ipywidgets>=8.0.7",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
pytest=[
|
|
48
|
+
"opencv_python_headless>=4.8.1.78",
|
|
49
|
+
"pytest>=6.2.5"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
full=[
|
|
53
|
+
"PyQt6>=6.5.2",
|
|
54
|
+
"PyQt6_sip>=13.5.2",
|
|
55
|
+
"opencv_python_headless>=4.8.1.78",
|
|
56
|
+
"pytest>=6.2.5",
|
|
57
|
+
"ipywidgets>=8.0.7",
|
|
58
|
+
"pandas",
|
|
59
|
+
]
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
# `interactive_pipe`
|
|
2
|
+
|
|
3
|
+
Quick setup
|
|
4
|
+
`pip install interactive-pipe[full]`
|
|
5
|
+
|
|
2
6
|
### Concept
|
|
3
7
|
- Develop an algorithm while debugging visually with plots, while checking robustness & continuity to parameters change.
|
|
4
8
|
- Magically create a graphical interface to easily demonstrate a concept or simply tune your algorithm.
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
----------------------
|
|
20
24
|
|
|
21
25
|
### Setup
|
|
22
|
-
`
|
|
26
|
+
`pip install interactive-pipe[full]`
|
|
23
27
|
|
|
24
28
|
----------------------
|
|
25
29
|
|
|
@@ -42,7 +46,7 @@
|
|
|
42
46
|
----------------------
|
|
43
47
|
|
|
44
48
|
## :scroll: Features
|
|
45
|
-
**Version 0.
|
|
49
|
+
**Version 0.6.0**
|
|
46
50
|
- Modular multi-image processing filters
|
|
47
51
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
48
52
|
- Support in jupyter notebooks
|
|
@@ -103,6 +107,12 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
103
107
|
# Tutorials
|
|
104
108
|
|
|
105
109
|
### Learn by examples
|
|
110
|
+
#### [Basic image processing](/demo/multi_image.py)
|
|
111
|
+
| GUI | Pipeline |
|
|
112
|
+
|:--:|:--:|
|
|
113
|
+
| |  |
|
|
114
|
+
|
|
115
|
+

|
|
106
116
|
|
|
107
117
|
#### [Speech exploration notebook](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
|
|
108
118
|

|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
|
|
2
2
|
from interactive_pipe.headless.control import Control
|
|
3
|
-
from interactive_pipe.headless.keyboard import
|
|
4
|
-
from interactive_pipe.helper.filter_decorator import interactive, interact
|
|
3
|
+
from interactive_pipe.headless.keyboard import KeyboardControl
|
|
4
|
+
from interactive_pipe.helper.filter_decorator import interactive, interact
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from copy import deepcopy
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class CachedResults:
|
|
@@ -8,7 +8,7 @@ class CachedResults:
|
|
|
8
8
|
Helper class to store the results of a Filter.
|
|
9
9
|
|
|
10
10
|
The overall purpose of these classes is to prevent unnecessary computations by reusing the
|
|
11
|
-
results of previous filter operations when the input parameters have not changed.
|
|
11
|
+
results of previous filter operations when the input parameters have not changed.
|
|
12
12
|
This is accomplished by storing the results of the filter operations and the parameters
|
|
13
13
|
used in those operations, and checking for changes in the parameters
|
|
14
14
|
before re-computing the results.
|
|
@@ -20,7 +20,7 @@ class CachedResults:
|
|
|
20
20
|
|
|
21
21
|
Please note that if you use safe_buffer_deepcopy=False,
|
|
22
22
|
only pointers are copied when updating the cache, no deepcopy is performed here.
|
|
23
|
-
You should only use safe_buffer_deepcopy=False
|
|
23
|
+
You should only use safe_buffer_deepcopy=False
|
|
24
24
|
if you're 100% sure you don't do inplace modifications.
|
|
25
25
|
|
|
26
26
|
Underlying class used in the interactive pipe cache mechanism.
|
|
@@ -76,7 +76,7 @@ class CachedResults:
|
|
|
76
76
|
class StateChange:
|
|
77
77
|
"""
|
|
78
78
|
Helper class to check whether or not input parameters have been updated.
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
Underlying class used in the interactive pipe cache mechanism.
|
|
81
81
|
"""
|
|
82
82
|
|
|
@@ -7,6 +7,7 @@ from typing import List
|
|
|
7
7
|
from interactive_pipe.core.cache import CachedResults
|
|
8
8
|
from interactive_pipe.core.filter import FilterCore
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
class PipelineEngine:
|
|
11
12
|
def __init__(self, cache=False, safe_input_buffer_deepcopy=True) -> None:
|
|
12
13
|
self.cache = cache
|
|
@@ -21,7 +22,8 @@ class PipelineEngine:
|
|
|
21
22
|
for input_index, inp in enumerate(imglst):
|
|
22
23
|
if self.safe_input_buffer_deepcopy:
|
|
23
24
|
result[input_index] = deepcopy(inp)
|
|
24
|
-
logging.debug(
|
|
25
|
+
logging.debug(
|
|
26
|
+
f"<<< Deepcopy input images {input_index}")
|
|
25
27
|
else:
|
|
26
28
|
result[input_index] = inp
|
|
27
29
|
elif isinstance(imglst, dict):
|
|
@@ -56,7 +58,8 @@ class PipelineEngine:
|
|
|
56
58
|
if prc.inputs:
|
|
57
59
|
routing_in = [
|
|
58
60
|
result[idi] if idi is not None else None for idi in prc.inputs]
|
|
59
|
-
logging.debug("in types->", [type(inp)
|
|
61
|
+
logging.debug("in types->", [type(inp)
|
|
62
|
+
for inp in routing_in])
|
|
60
63
|
out = prc.run(*routing_in)
|
|
61
64
|
if out is not None:
|
|
62
65
|
logging.debug("out types->", [type(ou) for ou in out])
|
|
@@ -83,4 +86,4 @@ class PipelineEngine:
|
|
|
83
86
|
# Limit result using self.numfigs but with indices pointed by last filter
|
|
84
87
|
logging.info("\n".join(performances))
|
|
85
88
|
logging.info(f"Full buffer: {len(result)}")
|
|
86
|
-
return result
|
|
89
|
+
return result
|
|
@@ -5,13 +5,14 @@ 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
|
+
|
|
8
9
|
class PureFilter:
|
|
9
10
|
def __init__(self, apply_fn: Optional[Callable] = None, name: Optional[str] = None, default_params: dict = {}):
|
|
10
11
|
self.name = name if name else (
|
|
11
12
|
self.__class__.__name__ if not apply_fn else apply_fn.__name__)
|
|
12
13
|
if apply_fn is not None:
|
|
13
14
|
self.apply = apply_fn
|
|
14
|
-
self.__initialize_default_values()
|
|
15
|
+
self.__initialize_default_values() # initialize default values from .apply method
|
|
15
16
|
self.values = deepcopy(default_params)
|
|
16
17
|
|
|
17
18
|
@property
|
|
@@ -34,14 +35,14 @@ class PureFilter:
|
|
|
34
35
|
self.signature = (self.__args_names, self.__kwargs_names)
|
|
35
36
|
else: # skip computing signature
|
|
36
37
|
pass
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
def __initialize_default_values(self):
|
|
39
40
|
assert not hasattr(self, "_values")
|
|
40
41
|
self.check_apply_signature()
|
|
41
42
|
self._values = self.__kwargs_names
|
|
42
43
|
if "global_params" in self.__kwargs_names.keys():
|
|
43
44
|
self._values.pop("global_params")
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
@property
|
|
46
47
|
def values(self):
|
|
47
48
|
return self._values
|
|
@@ -70,6 +71,7 @@ class PureFilter:
|
|
|
70
71
|
|
|
71
72
|
class FilterCore(PureFilter):
|
|
72
73
|
"""PureFilter with cache storage + routing nodes defined (inputs & outputs fields)"""
|
|
74
|
+
|
|
73
75
|
def __init__(self,
|
|
74
76
|
apply_fn: Callable = None,
|
|
75
77
|
name: Optional[str] = None,
|
|
@@ -108,19 +110,21 @@ class FilterCore(PureFilter):
|
|
|
108
110
|
assert len(out) >= len(
|
|
109
111
|
self.outputs), "number of outputs shall be at least greater or equal to what's expected by the filter"
|
|
110
112
|
return out
|
|
111
|
-
|
|
113
|
+
|
|
112
114
|
else:
|
|
113
|
-
logging.debug(
|
|
114
|
-
|
|
115
|
+
logging.debug(
|
|
116
|
+
f"need to return a tuple when you have a single element out {type(out)}")
|
|
117
|
+
assert len(self.outputs) == 1, "returning a single element!"
|
|
115
118
|
return (out,)
|
|
116
|
-
|
|
119
|
+
|
|
117
120
|
def __repr__(self) -> str:
|
|
118
121
|
descr = f"{self.name}: "
|
|
119
122
|
descr += "(" + (", ".join([f"{it}" for it in self.inputs])) + ")"
|
|
120
|
-
descr += " -> " +
|
|
123
|
+
descr += " -> " + \
|
|
124
|
+
"(" + ", ".join([f"{it}" for it in self.outputs]) + ")"
|
|
121
125
|
# descr += "\n"
|
|
122
126
|
return descr
|
|
123
|
-
|
|
127
|
+
|
|
124
128
|
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
125
129
|
control_dict = {}
|
|
126
130
|
if hasattr(self, "controls"):
|
|
@@ -128,4 +132,4 @@ class FilterCore(PureFilter):
|
|
|
128
132
|
control_dict[ctrl_name] = ctrl.value
|
|
129
133
|
merged_kwargs = {**kwargs, **control_dict}
|
|
130
134
|
out = self.apply(*args, **merged_kwargs)
|
|
131
|
-
return out
|
|
135
|
+
return out
|
|
@@ -5,6 +5,7 @@ from typing import List, Callable, Tuple, 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
|
|
|
8
|
+
|
|
8
9
|
def get_name(node: ast.NodeVisitor) -> Union[str, List[str], None]:
|
|
9
10
|
if isinstance(node, ast.Name):
|
|
10
11
|
return node.id
|
|
@@ -14,8 +15,9 @@ def get_name(node: ast.NodeVisitor) -> Union[str, List[str], None]:
|
|
|
14
15
|
return [get_name(elt) for elt in node.elts]
|
|
15
16
|
else:
|
|
16
17
|
return None
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def flatten_target_names(targets: List[Union[list, str, None]], mapping_function: Optional[Callable] = None) -> List[str]:
|
|
19
21
|
output_names = []
|
|
20
22
|
for target in targets:
|
|
21
23
|
target_name = mapping_function(target) if mapping_function else target
|
|
@@ -29,7 +31,7 @@ def flatten_target_names(targets: List[Union[list, str, None]], mapping_function
|
|
|
29
31
|
return output_names
|
|
30
32
|
|
|
31
33
|
|
|
32
|
-
def get_call_graph(func:Callable, global_context=None) -> dict:
|
|
34
|
+
def get_call_graph(func: Callable, global_context=None) -> dict:
|
|
33
35
|
code = inspect.getsource(func)
|
|
34
36
|
tree = ast.parse(code)
|
|
35
37
|
results = []
|
|
@@ -53,10 +55,13 @@ def get_call_graph(func:Callable, global_context=None) -> dict:
|
|
|
53
55
|
value = node.value
|
|
54
56
|
if isinstance(value, ast.Call):
|
|
55
57
|
function_name = get_name(value.func)
|
|
56
|
-
logging.debug(
|
|
57
|
-
|
|
58
|
+
logging.debug(
|
|
59
|
+
f"classic function with assignment {function_name}")
|
|
60
|
+
assert function_name in global_context.keys(
|
|
61
|
+
), f"Probably mispelled {function_name}"
|
|
58
62
|
input_names = [get_name(arg) for arg in value.args]
|
|
59
|
-
output_names = flatten_target_names(
|
|
63
|
+
output_names = flatten_target_names(
|
|
64
|
+
targets, mapping_function=get_name)
|
|
60
65
|
function_object = global_context[function_name]
|
|
61
66
|
if isinstance(function_object, FilterCore):
|
|
62
67
|
# Case of a filter instance
|
|
@@ -73,16 +78,18 @@ def get_call_graph(func:Callable, global_context=None) -> dict:
|
|
|
73
78
|
sig = analyze_apply_fn_signature(function_object)
|
|
74
79
|
function_apply = global_context[function_name]
|
|
75
80
|
else:
|
|
76
|
-
raise TypeError(
|
|
81
|
+
raise TypeError(
|
|
82
|
+
f"Not supported {function_name} - should be function or FilterCore")
|
|
77
83
|
results.append({
|
|
78
84
|
"function_name": function_name,
|
|
79
85
|
"function_object": function_apply,
|
|
80
|
-
"signature"
|
|
86
|
+
"signature": {"args": sig[0], "kwargs": sig[1]},
|
|
81
87
|
"args": input_names,
|
|
82
88
|
"returns": output_names,
|
|
83
89
|
})
|
|
84
90
|
main_function = tree.body[0]
|
|
85
|
-
outputs = [get_name(ret.value)
|
|
91
|
+
outputs = [get_name(ret.value)
|
|
92
|
+
for ret in main_function.body if isinstance(ret, ast.Return)]
|
|
86
93
|
assert len(outputs) <= 1, "cannot return several times!"
|
|
87
94
|
outputs = flatten_target_names(outputs, mapping_function=None)
|
|
88
95
|
inputs, kwargs = analyze_apply_fn_signature(func)
|
|
@@ -3,6 +3,7 @@ from interactive_pipe.core.filter import FilterCore
|
|
|
3
3
|
from interactive_pipe.core.engine import PipelineEngine
|
|
4
4
|
import logging
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class PipelineCore:
|
|
7
8
|
"""A pipeline is defined as the combination of:
|
|
8
9
|
- a list of filters
|
|
@@ -10,19 +11,21 @@ class PipelineCore:
|
|
|
10
11
|
- optionally, some inputs to process
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
|
-
def __init__(self, filters: List[FilterCore], name="pipeline", cache=False, inputs: Optional[list] = None, parameters: dict = {}, global_params={}, outputs: Optional[list]=None, safe_input_buffer_deepcopy: bool=True):
|
|
14
|
+
def __init__(self, filters: List[FilterCore], name="pipeline", cache=False, inputs: Optional[list] = None, parameters: dict = {}, global_params={}, outputs: Optional[list] = None, safe_input_buffer_deepcopy: bool = True):
|
|
14
15
|
if not all(isinstance(f, FilterCore) for f in filters):
|
|
15
16
|
raise ValueError(
|
|
16
17
|
f"All elements in 'filters' must be instances of 'Filter'. {[type(f) for f in filters]}")
|
|
17
18
|
self.filters = filters
|
|
18
|
-
self.engine = PipelineEngine(
|
|
19
|
+
self.engine = PipelineEngine(
|
|
20
|
+
cache, safe_input_buffer_deepcopy=safe_input_buffer_deepcopy)
|
|
19
21
|
self.global_params = global_params
|
|
20
22
|
for filter in self.filters:
|
|
21
23
|
# link each filter to global params
|
|
22
24
|
filter.global_params = self.global_params
|
|
23
25
|
self.reset_cache()
|
|
24
26
|
if inputs is None:
|
|
25
|
-
logging.warning(
|
|
27
|
+
logging.warning(
|
|
28
|
+
"Setting a pipeline without input - use inputs=[] to get rid of this warning")
|
|
26
29
|
self.inputs_routing = []
|
|
27
30
|
else:
|
|
28
31
|
self.inputs_routing = inputs
|
|
@@ -30,9 +33,10 @@ class PipelineCore:
|
|
|
30
33
|
self.__initialized_inputs = False
|
|
31
34
|
if outputs is None:
|
|
32
35
|
outputs = self.filters[-1].outputs
|
|
33
|
-
logging.warning(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
logging.warning(
|
|
37
|
+
f"Using last filter outputs {self.filters[-1]} {outputs}")
|
|
38
|
+
|
|
39
|
+
self.outputs = outputs # output indexes (routing)
|
|
36
40
|
# You need to set the values to their default_value for each filter
|
|
37
41
|
self.parameters = parameters
|
|
38
42
|
self.results = None
|
|
@@ -46,14 +50,14 @@ class PipelineCore:
|
|
|
46
50
|
"""Useful for standalone python acess without gui or disk write
|
|
47
51
|
"""
|
|
48
52
|
return self.engine.run(self.filters, imglst=self.inputs)
|
|
49
|
-
|
|
53
|
+
|
|
50
54
|
@property
|
|
51
55
|
def parameters(self):
|
|
52
56
|
parameters = {}
|
|
53
57
|
for filt in self.filters:
|
|
54
58
|
parameters[filt.name] = filt.values
|
|
55
59
|
return parameters
|
|
56
|
-
|
|
60
|
+
|
|
57
61
|
@parameters.setter
|
|
58
62
|
def parameters(self, new_parameters: Dict[str, any]):
|
|
59
63
|
"""Force tuning parameters
|
|
@@ -76,14 +80,14 @@ class PipelineCore:
|
|
|
76
80
|
available_filters_names = [filt.name for filt in self.filters]
|
|
77
81
|
for filter_name in new_parameters.keys():
|
|
78
82
|
assert filter_name in available_filters_names, f"filter {filter_name} does not exist {available_filters_names}"
|
|
79
|
-
self.filters[available_filters_names.index(
|
|
80
|
-
|
|
83
|
+
self.filters[available_filters_names.index(
|
|
84
|
+
filter_name)].values = new_parameters[filter_name]
|
|
81
85
|
|
|
82
86
|
@property
|
|
83
87
|
def inputs(self):
|
|
84
88
|
assert self.__initialized_inputs, "Cannot access unitialized inputs!"
|
|
85
89
|
return self.__inputs
|
|
86
|
-
|
|
90
|
+
|
|
87
91
|
@inputs.setter
|
|
88
92
|
def inputs(self, inputs: list):
|
|
89
93
|
if inputs is not None:
|
|
@@ -94,7 +98,8 @@ class PipelineCore:
|
|
|
94
98
|
self.__inputs = inputs
|
|
95
99
|
elif isinstance(inputs, list) or isinstance(inputs, tuple):
|
|
96
100
|
# inputs is a list or a tuple
|
|
97
|
-
assert len(inputs) == len(
|
|
101
|
+
assert len(inputs) == len(
|
|
102
|
+
self.inputs_routing), f"wrong amount of inputs\nprovided {len(inputs)} inputs vs expected:{len(self.inputs_routing)}"
|
|
98
103
|
self.__inputs = {}
|
|
99
104
|
for idx, input_name in enumerate(self.inputs_routing):
|
|
100
105
|
self.__inputs[input_name] = inputs[idx]
|
|
@@ -104,9 +109,11 @@ class PipelineCore:
|
|
|
104
109
|
self.__inputs = {self.inputs_routing[0]: inputs}
|
|
105
110
|
assert isinstance(self.__inputs, dict)
|
|
106
111
|
if len(self.__inputs.keys()) == 0:
|
|
107
|
-
|
|
112
|
+
# similar to having no input, but explicitly saying that we have initialized it.
|
|
113
|
+
self.__inputs = None
|
|
108
114
|
else:
|
|
109
|
-
assert self.inputs_routing is None or ((isinstance(self.inputs_routing, tuple) or isinstance(
|
|
115
|
+
assert self.inputs_routing is None or ((isinstance(self.inputs_routing, tuple) or isinstance(
|
|
116
|
+
self.inputs_routing, list)) and len(self.inputs_routing) == 0)
|
|
110
117
|
self.__inputs = None
|
|
111
118
|
self.__initialized_inputs = True
|
|
112
119
|
self.reset_cache()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from typing import Callable, Tuple
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
def analyze_apply_fn_signature(apply_fn: Callable) -> Tuple[dict, dict]:
|
|
5
6
|
signature = inspect.signature(apply_fn)
|
|
6
7
|
keyword_args = {
|
|
@@ -14,4 +15,4 @@ def analyze_apply_fn_signature(apply_fn: Callable) -> Tuple[dict, dict]:
|
|
|
14
15
|
for k, v in signature.parameters.items()
|
|
15
16
|
if v.default is inspect.Parameter.empty
|
|
16
17
|
]
|
|
17
|
-
return positional_args, keyword_args
|
|
18
|
+
return positional_args, keyword_args
|