interactive-pipe 0.5.6__tar.gz → 0.5.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.5.6 → interactive_pipe-0.5.7}/PKG-INFO +4 -4
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/pyproject.toml +1 -1
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/readme.md +3 -3
- interactive_pipe-0.5.7/src/interactive_pipe/__init__.py +4 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/filter.py +12 -4
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/graph.py +18 -2
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/curves.py +3 -1
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/gui.py +4 -2
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/qt_gui.py +20 -3
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/headless/pipeline.py +26 -8
- interactive_pipe-0.5.7/src/interactive_pipe/helper/_private.py +2 -0
- interactive_pipe-0.5.7/src/interactive_pipe/helper/choose_backend.py +39 -0
- interactive_pipe-0.5.7/src/interactive_pipe/helper/filter_decorator.py +111 -0
- interactive_pipe-0.5.7/src/interactive_pipe/helper/keyword_args_analyzer.py +72 -0
- interactive_pipe-0.5.7/src/interactive_pipe/helper/pipeline_decorator.py +32 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe.egg-info/PKG-INFO +4 -4
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe.egg-info/SOURCES.txt +5 -1
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_decorator.py +1 -1
- interactive_pipe-0.5.6/src/interactive_pipe/__init__.py +0 -3
- interactive_pipe-0.5.6/src/interactive_pipe/helper/decorator.py +0 -90
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/LICENSE +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/setup.cfg +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/cache.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/engine.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/pipeline.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/core/signature.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/data.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/image.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/parameters.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/mpl_control.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/mpl_gui.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/mpl_window.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/nb_control.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/nb_gui.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/qt_control.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/window.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/headless/control.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/headless/keyboard.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/thirdparty/images_openai_api.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/thirdparty/music_spotify.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_cache.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_control_abbreviations.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_controller.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_core.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_curves.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_engine.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_filter.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_headless.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_image.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_parameters.py +0 -0
- {interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/test/test_recorder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -26,8 +26,8 @@ License-File: LICENSE
|
|
|
26
26
|
| Science notebook | Toddler DIY Jukebox on a raspberry Pi |
|
|
27
27
|
|:-----: |:-----:|
|
|
28
28
|
|  | 
|
|
29
|
-
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code.| Plays some music when you touch the icon. Images can be generated using the OpenAI Dall-E API helpers.
|
|
30
|
-
| [
|
|
29
|
+
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon. Images can be generated using the OpenAI [Dall-E API helpers](https://github.com/balthazarneveu/interactive_pipe/blob/master/src/interactive_pipe/thirdparty/images_openai_api.py) . Caption added through the title mechanism. Music samples generated by prompting [MusicGen](https://huggingface.co/spaces/facebook/MusicGen) |
|
|
30
|
+
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [jukebox.py demo code](/demo/jukebox.py)|
|
|
31
31
|
|
|
32
32
|
----------------------
|
|
33
33
|
|
|
@@ -55,7 +55,7 @@ License-File: LICENSE
|
|
|
55
55
|
----------------------
|
|
56
56
|
|
|
57
57
|
## :scroll: Features
|
|
58
|
-
**Version 0.5.
|
|
58
|
+
**Version 0.5.7**
|
|
59
59
|
- Modular multi-image processing filters
|
|
60
60
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
61
61
|
- Support in jupyter notebooks
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
| Science notebook | Toddler DIY Jukebox on a raspberry Pi |
|
|
13
13
|
|:-----: |:-----:|
|
|
14
14
|
|  | 
|
|
15
|
-
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code.| Plays some music when you touch the icon. Images can be generated using the OpenAI Dall-E API helpers.
|
|
16
|
-
| [
|
|
15
|
+
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon. Images can be generated using the OpenAI [Dall-E API helpers](https://github.com/balthazarneveu/interactive_pipe/blob/master/src/interactive_pipe/thirdparty/images_openai_api.py) . Caption added through the title mechanism. Music samples generated by prompting [MusicGen](https://huggingface.co/spaces/facebook/MusicGen) |
|
|
16
|
+
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [jukebox.py demo code](/demo/jukebox.py)|
|
|
17
17
|
|
|
18
18
|
----------------------
|
|
19
19
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
----------------------
|
|
42
42
|
|
|
43
43
|
## :scroll: Features
|
|
44
|
-
**Version 0.5.
|
|
44
|
+
**Version 0.5.7**
|
|
45
45
|
- Modular multi-image processing filters
|
|
46
46
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
47
47
|
- Support in jupyter notebooks
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
|
|
2
|
+
from interactive_pipe.headless.control import Control
|
|
3
|
+
from interactive_pipe.headless.keyboard import KeyboardControl
|
|
4
|
+
from interactive_pipe.helper.filter_decorator import interactive, interact
|
|
@@ -27,14 +27,14 @@ class PureFilter:
|
|
|
27
27
|
assert isinstance(new_global_params, dict)
|
|
28
28
|
self._global_params = new_global_params
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
30
|
def check_apply_signature(self):
|
|
33
31
|
if not hasattr(self, "__args_names") or not hasattr(self, "__kwargs_names"):
|
|
34
32
|
self.__args_names, self.__kwargs_names = analyze_apply_fn_signature(
|
|
35
33
|
self.apply)
|
|
34
|
+
self.signature = (self.__args_names, self.__kwargs_names)
|
|
36
35
|
else: # skip computing signature
|
|
37
36
|
pass
|
|
37
|
+
|
|
38
38
|
def __initialize_default_values(self):
|
|
39
39
|
assert not hasattr(self, "_values")
|
|
40
40
|
self.check_apply_signature()
|
|
@@ -50,7 +50,6 @@ class PureFilter:
|
|
|
50
50
|
def values(self, new_values):
|
|
51
51
|
assert isinstance(
|
|
52
52
|
new_values, dict), f"{new_values} is not a dictionary"
|
|
53
|
-
|
|
54
53
|
self._values = {**self._values, **new_values}
|
|
55
54
|
|
|
56
55
|
def run(self, *imgs) -> Tuple[Any]:
|
|
@@ -120,4 +119,13 @@ class FilterCore(PureFilter):
|
|
|
120
119
|
descr += "(" + (", ".join([f"{it}" for it in self.inputs])) + ")"
|
|
121
120
|
descr += " -> " + "(" + ", ".join([f"{it}" for it in self.outputs]) + ")"
|
|
122
121
|
# descr += "\n"
|
|
123
|
-
return descr
|
|
122
|
+
return descr
|
|
123
|
+
|
|
124
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
125
|
+
control_dict = {}
|
|
126
|
+
if hasattr(self, "controls"):
|
|
127
|
+
for ctrl_name, ctrl in self.controls.items():
|
|
128
|
+
control_dict[ctrl_name] = ctrl.value
|
|
129
|
+
merged_kwargs = {**kwargs, **control_dict}
|
|
130
|
+
out = self.apply(*args, **merged_kwargs)
|
|
131
|
+
return out
|
|
@@ -2,6 +2,7 @@ import ast
|
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
4
|
from typing import List, Callable, Tuple, Optional, Union
|
|
5
|
+
from interactive_pipe.core.filter import FilterCore
|
|
5
6
|
from interactive_pipe.core.signature import analyze_apply_fn_signature
|
|
6
7
|
|
|
7
8
|
def get_name(node: ast.NodeVisitor) -> Union[str, List[str], None]:
|
|
@@ -57,10 +58,25 @@ def get_call_graph(func:Callable, global_context=None) -> dict:
|
|
|
57
58
|
input_names = [get_name(arg) for arg in value.args]
|
|
58
59
|
output_names = flatten_target_names(targets, mapping_function=get_name)
|
|
59
60
|
function_object = global_context[function_name]
|
|
60
|
-
|
|
61
|
+
if isinstance(function_object, FilterCore):
|
|
62
|
+
# Case of a filter instance
|
|
63
|
+
function_object.check_apply_signature()
|
|
64
|
+
# ---> forcing filter instance .name to be the name of the object used in the func
|
|
65
|
+
function_object.name = function_name
|
|
66
|
+
# you could use instead: # function_name = function_object.name to keep the original name
|
|
67
|
+
sig = function_object.signature
|
|
68
|
+
function_object.inputs = input_names
|
|
69
|
+
function_object.outputs = output_names
|
|
70
|
+
function_apply = function_object
|
|
71
|
+
elif isinstance(function_object, Callable):
|
|
72
|
+
# Case of a decorated function
|
|
73
|
+
sig = analyze_apply_fn_signature(function_object)
|
|
74
|
+
function_apply = global_context[function_name]
|
|
75
|
+
else:
|
|
76
|
+
raise TypeError(f"Not supported {function_name} - should be function or FilterCore")
|
|
61
77
|
results.append({
|
|
62
78
|
"function_name": function_name,
|
|
63
|
-
"function_object":
|
|
79
|
+
"function_object": function_apply,
|
|
64
80
|
"signature" : {"args": sig[0], "kwargs": sig[1]},
|
|
65
81
|
"args": input_names,
|
|
66
82
|
"returns": output_names,
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/curves.py
RENAMED
|
@@ -419,7 +419,9 @@ class Curve(Data):
|
|
|
419
419
|
|
|
420
420
|
@staticmethod
|
|
421
421
|
def _update_plot(data, plt_obj, ax=None):
|
|
422
|
-
|
|
422
|
+
legend = ax.get_legend()
|
|
423
|
+
if legend is not None:
|
|
424
|
+
texts = legend.get_texts()
|
|
423
425
|
linear_index = 0
|
|
424
426
|
for curve_idx, curve in enumerate(data["curves"]):
|
|
425
427
|
if curve.data.get("x", None) is not None:
|
|
@@ -33,9 +33,11 @@ class InteractivePipeGUI():
|
|
|
33
33
|
self.audio = audio
|
|
34
34
|
self.name = name
|
|
35
35
|
self.size = size
|
|
36
|
+
merged_controls = []
|
|
37
|
+
merged_controls+= controls
|
|
36
38
|
if hasattr(pipeline, "controls"):
|
|
37
|
-
|
|
38
|
-
self.controls =
|
|
39
|
+
merged_controls += pipeline.controls
|
|
40
|
+
self.controls = merged_controls
|
|
39
41
|
if self.pipeline.outputs:
|
|
40
42
|
if not isinstance(self.pipeline.outputs[0], list):
|
|
41
43
|
self.pipeline.outputs = [self.pipeline.outputs]
|
|
@@ -56,7 +56,11 @@ import time
|
|
|
56
56
|
|
|
57
57
|
class InteractivePipeQT(InteractivePipeGUI):
|
|
58
58
|
def init_app(self, **kwargs):
|
|
59
|
-
|
|
59
|
+
if not QApplication.instance():
|
|
60
|
+
self.app = QApplication(sys.argv)
|
|
61
|
+
else:
|
|
62
|
+
self.app = QApplication.instance()
|
|
63
|
+
|
|
60
64
|
if self.audio:
|
|
61
65
|
self.audio_player()
|
|
62
66
|
self.window = MainWindow(controls=self.controls, name=self.name, pipeline=self.pipeline, size=self.size, main_gui=self, **kwargs)
|
|
@@ -365,8 +369,20 @@ class MainWindow(QWidget, InteractivePipeWindow):
|
|
|
365
369
|
elif img_widget is not None:
|
|
366
370
|
img_widget.setParent(None)
|
|
367
371
|
|
|
368
|
-
def update_image(self,
|
|
369
|
-
if isinstance(
|
|
372
|
+
def update_image(self, image_array_original, row, col):
|
|
373
|
+
if isinstance(image_array_original, np.ndarray):
|
|
374
|
+
if len(image_array_original.shape) == 2:
|
|
375
|
+
# Consider black & white
|
|
376
|
+
image_array = image_array_original.copy()
|
|
377
|
+
c = 3
|
|
378
|
+
image_array = np.expand_dims(image_array, axis=-1)
|
|
379
|
+
image_array = np.repeat(image_array, c, axis=-1)
|
|
380
|
+
elif len(image_array_original.shape) == 3:
|
|
381
|
+
assert isinstance(image_array_original, np.ndarray)
|
|
382
|
+
assert(image_array_original.shape[-1]) == 3
|
|
383
|
+
image_array = image_array_original
|
|
384
|
+
else:
|
|
385
|
+
raise NotImplementedError(f"{image_array_original.shape}4 dimensions image or more like burst are not supported")
|
|
370
386
|
h, w, c = image_array.shape
|
|
371
387
|
bytes_per_line = c * w
|
|
372
388
|
image = QImage(image_array.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)
|
|
@@ -374,6 +390,7 @@ class MainWindow(QWidget, InteractivePipeWindow):
|
|
|
374
390
|
image_label = self.image_canvas[row][col]["image"]
|
|
375
391
|
image_label.setPixmap(pixmap)
|
|
376
392
|
else:
|
|
393
|
+
image_array = image_array_original
|
|
377
394
|
if MPL_SUPPORT and isinstance(image_array, Curve):
|
|
378
395
|
image_label = FigureCanvas(Figure())
|
|
379
396
|
if self.image_canvas[row][col]["ax_placeholder"] is None:
|
|
@@ -74,14 +74,22 @@ class HeadlessPipeline(PipelineCore):
|
|
|
74
74
|
inputs_filt = filt_dict["args"]
|
|
75
75
|
outputs_filt = filt_dict["returns"]
|
|
76
76
|
logging.debug("----------------->", filt_name, inputs_filt, outputs_filt)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
inputs=inputs_filt
|
|
80
|
-
outputs = outputs_filt
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
if isinstance(filt_dict["function_object"], FilterCore):
|
|
78
|
+
filter = filt_dict["function_object"]
|
|
79
|
+
filter.inputs = inputs_filt
|
|
80
|
+
filter.outputs = outputs_filt
|
|
81
|
+
filter.name = filt_name
|
|
82
|
+
params_to_analyze = filter.controls
|
|
83
|
+
else:
|
|
84
|
+
# when using the @interactive decorator
|
|
85
|
+
filter = FilterCore(
|
|
86
|
+
name=filt_name,
|
|
87
|
+
inputs=inputs_filt,
|
|
88
|
+
outputs = outputs_filt,
|
|
89
|
+
apply_fn=filt_dict["function_object"],
|
|
90
|
+
)
|
|
91
|
+
func_kwargs = analyze_apply_fn_signature(filt_dict["function_object"])[1]
|
|
92
|
+
params_to_analyze = {**func_kwargs, **Control.get_controls(filt_name)}
|
|
85
93
|
for param_name, param_value in params_to_analyze.items():
|
|
86
94
|
if isinstance(param_value, Control):
|
|
87
95
|
param_value.connect_filter(filter, param_name)
|
|
@@ -146,7 +154,17 @@ class HeadlessPipeline(PipelineCore):
|
|
|
146
154
|
ret += "}"
|
|
147
155
|
return ret
|
|
148
156
|
|
|
157
|
+
def update_parameters_from_controls(self):
|
|
158
|
+
if not hasattr(self, "controls"):
|
|
159
|
+
# Not having .controls attribute
|
|
160
|
+
# This happens for headless pipelines which have no list of controls
|
|
161
|
+
return
|
|
162
|
+
for ctrl in self.controls:
|
|
163
|
+
logging.info(f"{ctrl.filter_to_connect.name}, {ctrl.parameter_name_to_connect}, {ctrl.value}")
|
|
164
|
+
self.parameters = {ctrl.filter_to_connect.name: {ctrl.parameter_name_to_connect: ctrl.value}}
|
|
165
|
+
|
|
149
166
|
def __run(self):
|
|
167
|
+
self.update_parameters_from_controls()
|
|
150
168
|
result_full = super().run()
|
|
151
169
|
if self.outputs is not None:
|
|
152
170
|
output_indexes = self.outputs
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from interactive_pipe.helper import _private
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
def get_interactive_pipeline_class(gui="auto"):
|
|
5
|
+
selected_gui = None
|
|
6
|
+
if gui is None or gui == "auto":
|
|
7
|
+
if _private.auto_gui is not None:
|
|
8
|
+
|
|
9
|
+
selected_gui = _private.auto_gui #skip selection, used cached one
|
|
10
|
+
logging.debug(f"auto gui already selected {selected_gui}")
|
|
11
|
+
else:
|
|
12
|
+
try:
|
|
13
|
+
__IPYTHON__ # noqa: F821
|
|
14
|
+
selected_gui = "nb"
|
|
15
|
+
except Exception:
|
|
16
|
+
try:
|
|
17
|
+
from interactive_pipe.graphical.qt_gui import InteractivePipeQT as ChosenGui
|
|
18
|
+
selected_gui = "qt"
|
|
19
|
+
except Exception as qt_import_execption:
|
|
20
|
+
try:
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
selected_gui = "mpl"
|
|
23
|
+
except Exception as mpl_import_exception:
|
|
24
|
+
raise NameError("Error in auto backend choice:\n"+
|
|
25
|
+
f"Could not import Qt: {qt_import_execption}\n"+
|
|
26
|
+
f"Could not import Matplotlib {mpl_import_exception}"
|
|
27
|
+
)
|
|
28
|
+
_private.auto_gui = selected_gui
|
|
29
|
+
else:
|
|
30
|
+
selected_gui = gui
|
|
31
|
+
if selected_gui == "qt":
|
|
32
|
+
from interactive_pipe.graphical.qt_gui import InteractivePipeQT as ChosenGui
|
|
33
|
+
elif selected_gui == "mpl":
|
|
34
|
+
from interactive_pipe.graphical.mpl_gui import InteractivePipeMatplotlib as ChosenGui
|
|
35
|
+
elif selected_gui == "nb":
|
|
36
|
+
from interactive_pipe.graphical.nb_gui import InteractivePipeJupyter as ChosenGui
|
|
37
|
+
else:
|
|
38
|
+
raise NotImplementedError(f"Gui {gui} not available")
|
|
39
|
+
return ChosenGui
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from interactive_pipe.headless.control import Control
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
from interactive_pipe.helper.keyword_args_analyzer import get_controls_from_decorated_function_declaration
|
|
5
|
+
from interactive_pipe.helper import _private #import registered_controls_names
|
|
6
|
+
from interactive_pipe.headless.pipeline import HeadlessPipeline
|
|
7
|
+
|
|
8
|
+
from interactive_pipe.core.filter import FilterCore
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from interactive_pipe.helper.choose_backend import get_interactive_pipeline_class
|
|
12
|
+
|
|
13
|
+
class EnhancedFilterCore(FilterCore):
|
|
14
|
+
"""Internally creates a pipeline with a single filter
|
|
15
|
+
so you can graphically test a single filter without instantiating a pipeline.
|
|
16
|
+
Internally used by `@interact` decorator.
|
|
17
|
+
"""
|
|
18
|
+
def get_gui(self, gui="auto", output_routing=None):
|
|
19
|
+
self.inputs = list(range(len(self.signature[0])))
|
|
20
|
+
if output_routing is None:
|
|
21
|
+
logging.warning("Single output assumed, cannot deduce the number of outputs from your code, please provide output_routing!")
|
|
22
|
+
self.outputs = ["output",]
|
|
23
|
+
else:
|
|
24
|
+
self.outputs = output_routing
|
|
25
|
+
pipeline = HeadlessPipeline(filters=[self], inputs=[], outputs=None)
|
|
26
|
+
for ctrl_name, _ctrl in self.controls.items():
|
|
27
|
+
self.controls[ctrl_name].filter_to_connect = self
|
|
28
|
+
self.controls[ctrl_name].parameter_name_to_connect = ctrl_name
|
|
29
|
+
controls = [ctrl for _, ctrl in self.controls.items()]
|
|
30
|
+
pipeline.controls = controls
|
|
31
|
+
return get_interactive_pipeline_class(gui=gui)(pipeline=pipeline)
|
|
32
|
+
|
|
33
|
+
def run_gui(self, *args, gui="auto", output_routing=None):
|
|
34
|
+
try:
|
|
35
|
+
self.inputs = args
|
|
36
|
+
out = self.run(*args)
|
|
37
|
+
output_routing = [f"output {idx}" for idx in range(len(out))]
|
|
38
|
+
self.outputs = output_routing
|
|
39
|
+
except Exception as exc:
|
|
40
|
+
logging.warning(f"cannot automatically deduce the output routing\n{exc}")
|
|
41
|
+
gui_pipeline = self.get_gui(gui=gui, output_routing=output_routing)
|
|
42
|
+
gui_pipeline.pipeline.inputs_routing = list(range(len(self.signature[0])))
|
|
43
|
+
gui_pipeline(*args)
|
|
44
|
+
gui_pipeline.close()
|
|
45
|
+
gui_pipeline.controls = []
|
|
46
|
+
del gui_pipeline
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def interact(*decorator_args, gui="auto", disable=False, output_routing = None, **decorator_controls):
|
|
50
|
+
"""interact decorator allows you to launch a GUI from a single function
|
|
51
|
+
|
|
52
|
+
This will directly launch a GUI.
|
|
53
|
+
This is a "One shot decorator
|
|
54
|
+
|
|
55
|
+
___________________________________________________________________________
|
|
56
|
+
|
|
57
|
+
Note: `@interact` is a lot inspired by the `iPyWidget` interact function you'd usually use...
|
|
58
|
+
except that you can return numpy arrays & Curve class instances to automatically deal with the plot mechanism
|
|
59
|
+
Which avoids writing a lot of matplotlib boiler plate code
|
|
60
|
+
"""
|
|
61
|
+
ommitted_parentheses_flag = False
|
|
62
|
+
def wrapper(func):
|
|
63
|
+
if disable:
|
|
64
|
+
return func
|
|
65
|
+
# this will run the GUI automatically
|
|
66
|
+
_private.registered_controls_names = []
|
|
67
|
+
filter_instance = filter_from_function(func, **decorator_controls)
|
|
68
|
+
filter_instance.run_gui(*(decorator_args if not ommitted_parentheses_flag else decorator_args[1:]), gui=gui, output_routing=output_routing)
|
|
69
|
+
# return the original function if you want to keep using it afterwards
|
|
70
|
+
return func
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if len(decorator_args)==1 and callable(decorator_args[0]):
|
|
74
|
+
ommitted_parentheses_flag = True
|
|
75
|
+
return wrapper(decorator_args[0]) # no parenthesis
|
|
76
|
+
return wrapper
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def filter_from_function(apply_fn, default_params={}, **kwargs) -> EnhancedFilterCore:
|
|
80
|
+
controls = get_controls_from_decorated_function_declaration(apply_fn, kwargs)
|
|
81
|
+
filter_instance = EnhancedFilterCore(apply_fn=apply_fn, default_params=default_params)
|
|
82
|
+
filter_instance.controls = controls
|
|
83
|
+
return filter_instance
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def interactive(**decorator_controls):
|
|
87
|
+
"""Decorator to declare some controls linked to keyword arguments.
|
|
88
|
+
Parameters will become "variable" and sliders automatically appear in the GUI.
|
|
89
|
+
|
|
90
|
+
`@interactive` differs from `interact` in the sense that it will not launch a gui.
|
|
91
|
+
It is simply used to declare some sliders and allows re-using these functions afterwards.
|
|
92
|
+
Function decorator to add some controls
|
|
93
|
+
"""
|
|
94
|
+
def wrapper(func):
|
|
95
|
+
controls = get_controls_from_decorated_function_declaration(func, decorator_controls)
|
|
96
|
+
|
|
97
|
+
@functools.wraps(func)
|
|
98
|
+
def inner(*args, **kwargs):
|
|
99
|
+
# Combine args and kwargs into a single dictionary
|
|
100
|
+
merged_kwargs = {**kwargs, **controls}
|
|
101
|
+
bound_args = inspect.signature(func).bind(*args, **merged_kwargs)
|
|
102
|
+
bound_args.apply_defaults()
|
|
103
|
+
|
|
104
|
+
for k, v in bound_args.arguments.items():
|
|
105
|
+
if isinstance(v, Control):
|
|
106
|
+
bound_args.arguments[k] = v.value
|
|
107
|
+
|
|
108
|
+
# Call the original function with the processed arguments
|
|
109
|
+
return func(*bound_args.args, **bound_args.kwargs)
|
|
110
|
+
return inner
|
|
111
|
+
return wrapper
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from interactive_pipe.headless.control import Control
|
|
2
|
+
from interactive_pipe.helper.control_abbreviation import control_from_tuple
|
|
3
|
+
from typing import Callable, Union
|
|
4
|
+
from interactive_pipe.core.graph import analyze_apply_fn_signature
|
|
5
|
+
from interactive_pipe.helper import _private
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def __create_control_from_keyword_argument(
|
|
9
|
+
param_name: str,
|
|
10
|
+
unknown_keyword_arg: Union[Control, list, tuple]
|
|
11
|
+
) -> Union[None, Control]:
|
|
12
|
+
"""Create a Control from a given keyword argument named param_name with value unknown_keyword_arg
|
|
13
|
+
|
|
14
|
+
- If unknown_keyword_arg is already a Control, nothing to do.
|
|
15
|
+
- If unknown_keyword_arg is a tuple or a list or something else,
|
|
16
|
+
guess the Slider declaration automatically (see `control_from_tuple`)
|
|
17
|
+
|
|
18
|
+
You cannot have several controls which have the same attribute .name
|
|
19
|
+
See https://github.com/balthazarneveu/interactive_pipe/issues/35 for more details
|
|
20
|
+
"""
|
|
21
|
+
chosen_control = None
|
|
22
|
+
if isinstance(unknown_keyword_arg, Control): # This includes KeyboardControl aswell!!
|
|
23
|
+
if unknown_keyword_arg.name is None or unknown_keyword_arg._auto_named:
|
|
24
|
+
unknown_keyword_arg.name = param_name
|
|
25
|
+
chosen_control = unknown_keyword_arg
|
|
26
|
+
else:
|
|
27
|
+
if isinstance(unknown_keyword_arg, list) or isinstance(unknown_keyword_arg, tuple):
|
|
28
|
+
try:
|
|
29
|
+
chosen_control = control_from_tuple(unknown_keyword_arg, param_name=param_name)
|
|
30
|
+
except Exception as exc_1:
|
|
31
|
+
try:
|
|
32
|
+
chosen_control = control_from_tuple((unknown_keyword_arg,), param_name=param_name)
|
|
33
|
+
except Exception as exc:
|
|
34
|
+
raise Exception(exc)
|
|
35
|
+
# NOTE: for keyword args, setting a boolean will not trigger a tickmark (although it is possible)
|
|
36
|
+
# Use (True) instead of True if you want to make a tickbox
|
|
37
|
+
if chosen_control is not None:
|
|
38
|
+
assert chosen_control.name not in _private.registered_controls_names, f"{chosen_control.name} already attributed - {_private.registered_controls_names}"
|
|
39
|
+
_private.registered_controls_names.append(chosen_control.name)
|
|
40
|
+
return chosen_control
|
|
41
|
+
|
|
42
|
+
def get_controls_from_decorated_function_declaration(func: Callable, decorator_controls: dict):
|
|
43
|
+
controls = {}
|
|
44
|
+
keyword_args = analyze_apply_fn_signature(func)[1]
|
|
45
|
+
keyword_names = list(keyword_args.keys())
|
|
46
|
+
|
|
47
|
+
# Analyze at 2 levels (function keyword args & decorator keyword args) then register controls when necessary.
|
|
48
|
+
#-------------------------------------------
|
|
49
|
+
# @interactive(param_2=Control(...), )
|
|
50
|
+
# def func(img1, img2, param_1=Control(...)):
|
|
51
|
+
#-------------------------------------------
|
|
52
|
+
|
|
53
|
+
# 1. Analyzing function keyword args
|
|
54
|
+
# def func(img1, img2, param_1=Control(...))
|
|
55
|
+
|
|
56
|
+
for param_name, unknown_keyword_arg in keyword_args.items():
|
|
57
|
+
chosen_control = __create_control_from_keyword_argument(param_name, unknown_keyword_arg)
|
|
58
|
+
if chosen_control is not None:
|
|
59
|
+
controls[param_name] = chosen_control
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# 2. Analyzing decorator keyword args
|
|
63
|
+
# @interactive(param_2=Control(...))
|
|
64
|
+
for param_name, unknown_keyword_arg in decorator_controls.items():
|
|
65
|
+
assert param_name in keyword_names, f"typo: control {param_name} passed through the decorator does not match any of the function keyword args {keyword_names}"
|
|
66
|
+
chosen_control = __create_control_from_keyword_argument(param_name, unknown_keyword_arg)
|
|
67
|
+
if chosen_control is not None:
|
|
68
|
+
controls[param_name] = chosen_control
|
|
69
|
+
|
|
70
|
+
for param_name, unknown_control in controls.items():
|
|
71
|
+
Control.register(func.__name__, param_name, controls[param_name])
|
|
72
|
+
return controls
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from interactive_pipe.headless.pipeline import HeadlessPipeline
|
|
2
|
+
from interactive_pipe.graphical.gui import InteractivePipeGUI
|
|
3
|
+
|
|
4
|
+
import functools
|
|
5
|
+
from typing import Callable,Union
|
|
6
|
+
from interactive_pipe.helper.choose_backend import get_interactive_pipeline_class
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pipeline(pipeline_function:Callable, **kwargs) -> HeadlessPipeline:
|
|
10
|
+
return HeadlessPipeline.from_function(pipeline_function, **kwargs)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def interactive_pipeline(gui="auto", cache=False, output_canvas=None, **kwargs_gui) -> Union[HeadlessPipeline, InteractivePipeGUI]:
|
|
14
|
+
"""@interactive_pipeline
|
|
15
|
+
|
|
16
|
+
Function decorator to add some controls @interactive_pipeline
|
|
17
|
+
"""
|
|
18
|
+
def wrapper(pipeline_function):
|
|
19
|
+
headless_pipeline = HeadlessPipeline.from_function(pipeline_function, cache=cache)
|
|
20
|
+
if output_canvas is not None:
|
|
21
|
+
headless_pipeline.outputs = output_canvas
|
|
22
|
+
if gui is None:
|
|
23
|
+
return headless_pipeline
|
|
24
|
+
else:
|
|
25
|
+
InteractivePipeGui = get_interactive_pipeline_class(gui)
|
|
26
|
+
gui_pipeline = InteractivePipeGui(pipeline=headless_pipeline, name=pipeline_function.__name__, **kwargs_gui)
|
|
27
|
+
|
|
28
|
+
@functools.wraps(pipeline_function)
|
|
29
|
+
def inner(*args, **kwargs):
|
|
30
|
+
return gui_pipeline.__call__(*args, **kwargs)
|
|
31
|
+
return inner
|
|
32
|
+
return wrapper
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: interactive-pipe
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -26,8 +26,8 @@ License-File: LICENSE
|
|
|
26
26
|
| Science notebook | Toddler DIY Jukebox on a raspberry Pi |
|
|
27
27
|
|:-----: |:-----:|
|
|
28
28
|
|  | 
|
|
29
|
-
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code.| Plays some music when you touch the icon. Images can be generated using the OpenAI Dall-E API helpers.
|
|
30
|
-
| [
|
|
29
|
+
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon. Images can be generated using the OpenAI [Dall-E API helpers](https://github.com/balthazarneveu/interactive_pipe/blob/master/src/interactive_pipe/thirdparty/images_openai_api.py) . Caption added through the title mechanism. Music samples generated by prompting [MusicGen](https://huggingface.co/spaces/facebook/MusicGen) |
|
|
30
|
+
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [jukebox.py demo code](/demo/jukebox.py)|
|
|
31
31
|
|
|
32
32
|
----------------------
|
|
33
33
|
|
|
@@ -55,7 +55,7 @@ License-File: LICENSE
|
|
|
55
55
|
----------------------
|
|
56
56
|
|
|
57
57
|
## :scroll: Features
|
|
58
|
-
**Version 0.5.
|
|
58
|
+
**Version 0.5.7**
|
|
59
59
|
- Modular multi-image processing filters
|
|
60
60
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
61
61
|
- Support in jupyter notebooks
|
|
@@ -33,8 +33,12 @@ src/interactive_pipe/headless/control.py
|
|
|
33
33
|
src/interactive_pipe/headless/keyboard.py
|
|
34
34
|
src/interactive_pipe/headless/pipeline.py
|
|
35
35
|
src/interactive_pipe/helper/__init__.py
|
|
36
|
+
src/interactive_pipe/helper/_private.py
|
|
37
|
+
src/interactive_pipe/helper/choose_backend.py
|
|
36
38
|
src/interactive_pipe/helper/control_abbreviation.py
|
|
37
|
-
src/interactive_pipe/helper/
|
|
39
|
+
src/interactive_pipe/helper/filter_decorator.py
|
|
40
|
+
src/interactive_pipe/helper/keyword_args_analyzer.py
|
|
41
|
+
src/interactive_pipe/helper/pipeline_decorator.py
|
|
38
42
|
src/interactive_pipe/thirdparty/images_openai_api.py
|
|
39
43
|
src/interactive_pipe/thirdparty/music_spotify.py
|
|
40
44
|
test/test_cache.py
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from interactive_pipe.headless.control import Control
|
|
2
|
-
from interactive_pipe.helper.control_abbreviation import control_from_tuple
|
|
3
|
-
from interactive_pipe.headless.pipeline import HeadlessPipeline
|
|
4
|
-
from interactive_pipe.graphical.gui import InteractivePipeGUI
|
|
5
|
-
from interactive_pipe.core.graph import analyze_apply_fn_signature
|
|
6
|
-
import functools
|
|
7
|
-
import inspect
|
|
8
|
-
from typing import Callable,Union
|
|
9
|
-
import logging
|
|
10
|
-
|
|
11
|
-
def interactive(**decorator_controls):
|
|
12
|
-
"""Function decorator to add some controls
|
|
13
|
-
"""
|
|
14
|
-
def wrapper(func):
|
|
15
|
-
controls = {}
|
|
16
|
-
keyword_args = analyze_apply_fn_signature(func)[1]
|
|
17
|
-
keyword_names = list(keyword_args.keys())
|
|
18
|
-
for param_name, unknown_keyword_arg in keyword_args.items():
|
|
19
|
-
if isinstance(unknown_keyword_arg, Control):
|
|
20
|
-
if unknown_keyword_arg.name is None or unknown_keyword_arg._auto_named:
|
|
21
|
-
unknown_keyword_arg.name = param_name
|
|
22
|
-
controls[param_name] = unknown_keyword_arg
|
|
23
|
-
else:
|
|
24
|
-
if isinstance(unknown_keyword_arg, list) or isinstance(unknown_keyword_arg, tuple):
|
|
25
|
-
try:
|
|
26
|
-
controls[param_name] = control_from_tuple(unknown_keyword_arg, param_name=param_name)
|
|
27
|
-
except Exception as exc_1:
|
|
28
|
-
try:
|
|
29
|
-
controls[param_name] = control_from_tuple((unknown_keyword_arg,), param_name=param_name)
|
|
30
|
-
except Exception as exc:
|
|
31
|
-
print(exc_1)
|
|
32
|
-
raise Exception(exc)
|
|
33
|
-
# NOTE: for keyword args, setting a boolean will not trigger a tickmark (although it is possible)
|
|
34
|
-
# Use (True) instead of True if you want to make a tickbox
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for param_name, unknown_control in decorator_controls.items():
|
|
38
|
-
assert param_name in keyword_names, f"typo: control {param_name} passed through the decorator does not match any of the function keyword args {keyword_names}"
|
|
39
|
-
if isinstance(unknown_control, Control):
|
|
40
|
-
controls[param_name] = unknown_control
|
|
41
|
-
else: # you may get a tuple, list or a boolean
|
|
42
|
-
controls[param_name] = control_from_tuple(unknown_control, param_name=param_name)
|
|
43
|
-
|
|
44
|
-
for param_name, unknown_control in controls.items():
|
|
45
|
-
Control.register(func.__name__, param_name, controls[param_name])
|
|
46
|
-
@functools.wraps(func)
|
|
47
|
-
def inner(*args, **kwargs):
|
|
48
|
-
# Combine args and kwargs into a single dictionary
|
|
49
|
-
merged_kwargs = {**kwargs, **controls}
|
|
50
|
-
bound_args = inspect.signature(func).bind(*args, **merged_kwargs)
|
|
51
|
-
bound_args.apply_defaults()
|
|
52
|
-
|
|
53
|
-
for k, v in bound_args.arguments.items():
|
|
54
|
-
if isinstance(v, Control):
|
|
55
|
-
bound_args.arguments[k] = v.value
|
|
56
|
-
|
|
57
|
-
# Call the original function with the processed arguments
|
|
58
|
-
return func(*bound_args.args, **bound_args.kwargs)
|
|
59
|
-
return inner
|
|
60
|
-
return wrapper
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def pipeline(pipeline_function:Callable, **kwargs) -> HeadlessPipeline:
|
|
64
|
-
return HeadlessPipeline.from_function(pipeline_function, **kwargs)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def interactive_pipeline(gui=None, cache=False, output_canvas=None, **kwargs_gui) -> Union[HeadlessPipeline, InteractivePipeGUI]:
|
|
68
|
-
"""Function decorator to add some controls
|
|
69
|
-
"""
|
|
70
|
-
def wrapper(pipeline_function):
|
|
71
|
-
headless_pipeline = HeadlessPipeline.from_function(pipeline_function, cache=cache)
|
|
72
|
-
if output_canvas is not None:
|
|
73
|
-
headless_pipeline.outputs = output_canvas
|
|
74
|
-
if gui is None:
|
|
75
|
-
return headless_pipeline
|
|
76
|
-
if gui == "qt":
|
|
77
|
-
from interactive_pipe.graphical.qt_gui import InteractivePipeQT as InteractivePipeGui
|
|
78
|
-
elif gui == "mpl":
|
|
79
|
-
from interactive_pipe.graphical.mpl_gui import InteractivePipeMatplotlib as InteractivePipeGui
|
|
80
|
-
elif gui == "nb":
|
|
81
|
-
from interactive_pipe.graphical.nb_gui import InteractivePipeJupyter as InteractivePipeGui
|
|
82
|
-
else:
|
|
83
|
-
raise NotImplementedError(f"Gui {gui} not available")
|
|
84
|
-
gui_pipeline = InteractivePipeGui(pipeline=headless_pipeline, name=pipeline_function.__name__, **kwargs_gui)
|
|
85
|
-
|
|
86
|
-
@functools.wraps(pipeline_function)
|
|
87
|
-
def inner(*args, **kwargs):
|
|
88
|
-
return gui_pipeline.__call__(*args, **kwargs)
|
|
89
|
-
return inner
|
|
90
|
-
return wrapper
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/image.py
RENAMED
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/data_objects/parameters.py
RENAMED
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/__init__.py
RENAMED
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/mpl_control.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/mpl_window.py
RENAMED
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/nb_control.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/graphical/qt_control.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe/thirdparty/music_spotify.py
RENAMED
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{interactive_pipe-0.5.6 → interactive_pipe-0.5.7}/src/interactive_pipe.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|