interactive-pipe 0.7.0__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {interactive_pipe-0.7.0/src/interactive_pipe.egg-info → interactive_pipe-0.7.2}/PKG-INFO +7 -4
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/pyproject.toml +1 -1
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/readme.md +6 -3
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/__init__.py +2 -1
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/cache.py +0 -2
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/engine.py +2 -3
- interactive_pipe-0.7.2/src/interactive_pipe/data_objects/audio.py +35 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gradio_control.py +10 -2
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gradio_gui.py +103 -11
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/qt_control.py +24 -11
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/control.py +14 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2/src/interactive_pipe.egg-info}/PKG-INFO +7 -4
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/SOURCES.txt +1 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/LICENSE +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/setup.cfg +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/filter.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/graph.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/pipeline.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/signature.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/curves.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/data.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/image.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/parameters.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gui.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_control.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_gui.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_window.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/nb_control.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/nb_gui.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/qt_gui.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/window.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/keyboard.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/pipeline.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/_private.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/choose_backend.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/filter_decorator.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/keyword_args_analyzer.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/pipeline_decorator.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/thirdparty/images_openai_api.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/thirdparty/music_spotify.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/requires.txt +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_cache.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_control_abbreviations.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_controller.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_core.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_curves.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_decorator.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_engine.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_filter.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_headless.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_image.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_parameters.py +0 -0
- {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_recorder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!
|
|
5
5
|
Author-email: Balthazar Neveu <balthazarneveu@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/balthazarneveu/interactive_pipe
|
|
@@ -95,7 +95,7 @@ pip install -e ".[full]"
|
|
|
95
95
|
|
|
96
96
|
## :scroll: Features
|
|
97
97
|
|
|
98
|
-
**Version 0.7.
|
|
98
|
+
**Version 0.7.2**
|
|
99
99
|
- Modular multi-image processing filters
|
|
100
100
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
101
101
|
- Support in jupyter notebooks
|
|
@@ -103,7 +103,9 @@ pip install -e ".[full]"
|
|
|
103
103
|
- Cache intermediate results in RAM for much faster processing
|
|
104
104
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
105
105
|
- Support Curve plots (2D signals)
|
|
106
|
-
- :new: gradio backend (+allows sharing with others)
|
|
106
|
+
- :new: gradio backend (+allows sharing with others).
|
|
107
|
+
- :new: Audio support in Gradio
|
|
108
|
+
- :new: Circular sliders for Qt Backend
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
|
|
@@ -141,8 +143,9 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
141
143
|
| Plot curves | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
|
|
142
144
|
| Auto refreshed layout | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
|
|
143
145
|
| Keyboard shortcuts / fullscreen| :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
|
|
144
|
-
| Audio support | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :
|
|
146
|
+
| Audio support | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: |
|
|
145
147
|
| Image buttons| :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign:|
|
|
148
|
+
| Circular slider| :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign:|
|
|
146
149
|
|
|
147
150
|
|
|
148
151
|
|
|
@@ -51,7 +51,7 @@ pip install -e ".[full]"
|
|
|
51
51
|
|
|
52
52
|
## :scroll: Features
|
|
53
53
|
|
|
54
|
-
**Version 0.7.
|
|
54
|
+
**Version 0.7.2**
|
|
55
55
|
- Modular multi-image processing filters
|
|
56
56
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
57
57
|
- Support in jupyter notebooks
|
|
@@ -59,7 +59,9 @@ pip install -e ".[full]"
|
|
|
59
59
|
- Cache intermediate results in RAM for much faster processing
|
|
60
60
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
61
61
|
- Support Curve plots (2D signals)
|
|
62
|
-
- :new: gradio backend (+allows sharing with others)
|
|
62
|
+
- :new: gradio backend (+allows sharing with others).
|
|
63
|
+
- :new: Audio support in Gradio
|
|
64
|
+
- :new: Circular sliders for Qt Backend
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
|
|
@@ -97,8 +99,9 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
97
99
|
| Plot curves | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
|
|
98
100
|
| Auto refreshed layout | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
|
|
99
101
|
| Keyboard shortcuts / fullscreen| :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
|
|
100
|
-
| Audio support | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :
|
|
102
|
+
| Audio support | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: |
|
|
101
103
|
| Image buttons| :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign:|
|
|
104
|
+
| Circular slider| :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign:|
|
|
102
105
|
|
|
103
106
|
|
|
104
107
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
|
|
2
|
-
from interactive_pipe.headless.control import Control
|
|
2
|
+
from interactive_pipe.headless.control import Control, CircularControl
|
|
3
|
+
from interactive_pipe.data_objects.curves import Curve, SingleCurve
|
|
3
4
|
from interactive_pipe.headless.keyboard import KeyboardControl
|
|
4
5
|
from interactive_pipe.helper.filter_decorator import interactive, interact
|
|
@@ -48,8 +48,6 @@ 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
|
-
if self.result is None: # if no result force initialization
|
|
52
|
-
self.force_change = True
|
|
53
51
|
change_state_from_params_check = self.state_change.has_changed(
|
|
54
52
|
new_params)
|
|
55
53
|
if self.force_change:
|
|
@@ -58,11 +58,10 @@ class PipelineEngine:
|
|
|
58
58
|
if prc.inputs:
|
|
59
59
|
routing_in = [
|
|
60
60
|
result[idi] if idi is not None else None for idi in prc.inputs]
|
|
61
|
-
logging.debug("in types->
|
|
62
|
-
for inp in routing_in])
|
|
61
|
+
logging.debug(f"in types-> {[type(inp) for inp in routing_in]}")
|
|
63
62
|
out = prc.run(*routing_in)
|
|
64
63
|
if out is not None:
|
|
65
|
-
logging.debug("out types->
|
|
64
|
+
logging.debug(f"out types-> {[type(ou) for ou in out]}")
|
|
66
65
|
except Exception as e:
|
|
67
66
|
logging.error(f'Error in {prc.name} filter:')
|
|
68
67
|
logging.error(e)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import numpy as np
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
import base64
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Union, Tuple
|
|
7
|
+
WAVIO_AVAILABLE = False
|
|
8
|
+
try:
|
|
9
|
+
WAVIO_AVAILABLE = True
|
|
10
|
+
import wavio
|
|
11
|
+
except ImportError:
|
|
12
|
+
logging.info("Cannot import wavio")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def audio_to_html(audio: Union[None, str, Path, Tuple[int, np.ndarray]], controls=True) -> str:
|
|
16
|
+
if audio is None:
|
|
17
|
+
logging.debug("No audio to display")
|
|
18
|
+
return ""
|
|
19
|
+
if isinstance(audio, str) or isinstance(audio, Path):
|
|
20
|
+
audio_base64 = base64.b64encode(open(audio, "rb").read()).decode("utf-8")
|
|
21
|
+
elif isinstance(audio, tuple):
|
|
22
|
+
assert WAVIO_AVAILABLE, "wavio is not available"
|
|
23
|
+
assert len(audio) == 2, "audio tuple should have 2 elements: (rate, data)"
|
|
24
|
+
assert isinstance(audio[0], int), "audio[0] should be an integer"
|
|
25
|
+
assert isinstance(audio[1], np.ndarray), "audio[1] should be a numpy array"
|
|
26
|
+
audio_bytes = BytesIO()
|
|
27
|
+
wavio.write(audio_bytes, audio[1].astype(np.float32), audio[0], sampwidth=4)
|
|
28
|
+
audio_bytes.seek(0)
|
|
29
|
+
audio_base64 = base64.b64encode(audio_bytes.read()).decode("utf-8")
|
|
30
|
+
audio_player = f'<audio src="data:audio/mpeg;base64,{audio_base64}"'
|
|
31
|
+
if controls:
|
|
32
|
+
audio_player += ' controls'
|
|
33
|
+
audio_player += ' autoplay></audio>'
|
|
34
|
+
|
|
35
|
+
return audio_player
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gradio_control.py
RENAMED
|
@@ -49,7 +49,12 @@ class IntSliderControl(BaseControl):
|
|
|
49
49
|
|
|
50
50
|
def create(self) -> gr.Slider:
|
|
51
51
|
self.control_widget = gr.Slider(
|
|
52
|
-
value=self.ctrl.value_default,
|
|
52
|
+
value=self.ctrl.value_default,
|
|
53
|
+
minimum=self.ctrl.value_range[0],
|
|
54
|
+
maximum=self.ctrl.value_range[1],
|
|
55
|
+
label=self.name,
|
|
56
|
+
step=1
|
|
57
|
+
)
|
|
53
58
|
return self.control_widget
|
|
54
59
|
|
|
55
60
|
|
|
@@ -60,7 +65,10 @@ class FloatSliderControl(BaseControl):
|
|
|
60
65
|
def create(self) -> gr.Slider:
|
|
61
66
|
self.control_widget = gr.Slider(
|
|
62
67
|
value=self.ctrl.value_default,
|
|
63
|
-
minimum=self.ctrl.value_range[0],
|
|
68
|
+
minimum=self.ctrl.value_range[0],
|
|
69
|
+
maximum=self.ctrl.value_range[1],
|
|
70
|
+
label=self.name,
|
|
71
|
+
)
|
|
64
72
|
|
|
65
73
|
return self.control_widget
|
|
66
74
|
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gradio_gui.py
RENAMED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import gradio as gr
|
|
2
2
|
import numpy as np
|
|
3
|
+
import math
|
|
3
4
|
from typing import List
|
|
4
5
|
from interactive_pipe.headless.pipeline import HeadlessPipeline
|
|
5
6
|
from interactive_pipe.graphical.gradio_control import ControlFactory
|
|
6
7
|
from interactive_pipe.graphical.window import InteractivePipeWindow
|
|
7
8
|
from interactive_pipe.graphical.gui import InteractivePipeGUI
|
|
8
9
|
from interactive_pipe.headless.control import Control
|
|
10
|
+
from interactive_pipe.data_objects.audio import audio_to_html
|
|
9
11
|
import logging
|
|
10
12
|
PYQTVERSION = None
|
|
11
13
|
MPL_SUPPORT = False
|
|
@@ -21,23 +23,48 @@ except ImportError:
|
|
|
21
23
|
class InteractivePipeGradio(InteractivePipeGUI):
|
|
22
24
|
def init_app(self, **kwargs):
|
|
23
25
|
self.window = MainWindow(controls=self.controls, name=self.name,
|
|
24
|
-
pipeline=self.pipeline, size=self.size, main_gui=self, **kwargs)
|
|
26
|
+
pipeline=self.pipeline, size=self.size, main_gui=self, audio=self.audio, **kwargs)
|
|
25
27
|
self.pipeline.global_params["__pipeline"] = self.pipeline
|
|
26
|
-
|
|
28
|
+
if self.audio:
|
|
29
|
+
self.pipeline.global_params["__set_audio"] = self.__set_audio
|
|
30
|
+
self.pipeline.global_params["__play"] = self.__play
|
|
31
|
+
self.pipeline.global_params["__stop"] = self.__stop
|
|
32
|
+
self.pipeline.global_params["__pause"] = self.__pause
|
|
33
|
+
self.__set_audio(None)
|
|
34
|
+
|
|
35
|
+
def __set_audio(self, audio_content):
|
|
36
|
+
self.window.audio_content = audio_content
|
|
37
|
+
|
|
38
|
+
def __play(self):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def __pause(self):
|
|
42
|
+
self.__set_audio(None)
|
|
43
|
+
|
|
44
|
+
def __stop(self):
|
|
45
|
+
self.__set_audio(None)
|
|
27
46
|
|
|
28
47
|
def run(self) -> list:
|
|
29
48
|
assert self.pipeline._PipelineCore__initialized_inputs, "Did you forget to initialize the pipeline inputs?"
|
|
30
49
|
out_list = self.window.process_inputs_fn(*self.window.default_values)
|
|
50
|
+
self.window.refresh_display(out_list)
|
|
31
51
|
out_list_gradio_containers = []
|
|
32
52
|
for idx in range(len(out_list)):
|
|
33
53
|
for idy in range(len(out_list[idx])):
|
|
54
|
+
title = self.window.image_canvas[idx][idy].get("title", f"{idx} {idy}")
|
|
55
|
+
title = title.replace("_", " ")
|
|
34
56
|
if isinstance(out_list[idx][idy], np.ndarray):
|
|
35
|
-
out_list_gradio_containers.append(gr.Image())
|
|
57
|
+
out_list_gradio_containers.append(gr.Image(label=title))
|
|
36
58
|
elif MPL_SUPPORT and isinstance(out_list[idx][idy], Curve):
|
|
37
|
-
|
|
59
|
+
# if out_list[idx][idy].title is not None:
|
|
60
|
+
# title = out_list[idx][idy].title
|
|
61
|
+
out_list_gradio_containers.append(gr.Plot(label=title))
|
|
38
62
|
else:
|
|
39
63
|
raise NotImplementedError(
|
|
40
64
|
f"output type {type(out_list[idx][idy])} not supported")
|
|
65
|
+
if self.window.audio:
|
|
66
|
+
self.window.audio = gr.HTML()
|
|
67
|
+
self.pipeline.global_params["__audio"] = self.window.audio
|
|
41
68
|
self.window.instantiate_gradio_interface(out_list_gradio_containers)
|
|
42
69
|
self.window.refresh()
|
|
43
70
|
self.custom_end()
|
|
@@ -48,14 +75,18 @@ class InteractivePipeGradio(InteractivePipeGUI):
|
|
|
48
75
|
|
|
49
76
|
|
|
50
77
|
class MainWindow(InteractivePipeWindow):
|
|
51
|
-
def __init__(self, *args, controls=[], name="", pipeline: HeadlessPipeline = None, size=None, share_gradio_app=False, **kwargs):
|
|
78
|
+
def __init__(self, *args, controls=[], name="", pipeline: HeadlessPipeline = None, size=None, share_gradio_app=False, markdown_description=None, sliders_layout=None, sliders_per_row_layout=None, audio=False, **kwargs):
|
|
52
79
|
InteractivePipeWindow.__init__(
|
|
53
80
|
self, name=name, pipeline=pipeline, size=size)
|
|
81
|
+
self.markdown_description = markdown_description
|
|
82
|
+
self.sliders_layout = sliders_layout
|
|
83
|
+
self.sliders_per_row_layout = sliders_per_row_layout
|
|
54
84
|
self.init_sliders(controls)
|
|
55
85
|
self.size = size
|
|
56
86
|
self.full_screen_flag = False
|
|
57
87
|
self.pipeline = pipeline
|
|
58
88
|
self.share_gradio_app = share_gradio_app
|
|
89
|
+
self.audio = audio
|
|
59
90
|
# Define the functions that will be called when the input changes for gradio. => gr.Interface(fn=process_fn)
|
|
60
91
|
|
|
61
92
|
def process_outputs_fn(out) -> tuple:
|
|
@@ -92,6 +123,9 @@ class MainWindow(InteractivePipeWindow):
|
|
|
92
123
|
def run_fn(*args) -> tuple:
|
|
93
124
|
out = process_inputs_fn(*args)
|
|
94
125
|
out_tuple = process_outputs_fn(out)
|
|
126
|
+
if self.audio:
|
|
127
|
+
html_audio = audio_to_html(self.audio_content)
|
|
128
|
+
return *out_tuple, html_audio
|
|
95
129
|
return out_tuple
|
|
96
130
|
self.default_values = [self.ctrl[ctrl_key].value for ctrl_key in self.ctrl.keys()]
|
|
97
131
|
self.process_inputs_fn = process_inputs_fn
|
|
@@ -116,15 +150,70 @@ class MainWindow(InteractivePipeWindow):
|
|
|
116
150
|
# Gradio Blocks mode
|
|
117
151
|
# https://www.gradio.app/guides/blocks-and-event-listeners
|
|
118
152
|
with gr.Blocks() as io:
|
|
153
|
+
with gr.Row(variant="compact"):
|
|
154
|
+
gr.Markdown("### " + self.name.replace("_", " "))
|
|
119
155
|
with gr.Row():
|
|
120
156
|
for elem in outputs:
|
|
121
157
|
elem.render()
|
|
158
|
+
if self.audio:
|
|
159
|
+
outputs.append(self.audio)
|
|
122
160
|
with gr.Row():
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
161
|
+
self.audio.render()
|
|
162
|
+
|
|
163
|
+
if self.sliders_layout is None:
|
|
164
|
+
self.sliders_layout = "collapsible"
|
|
165
|
+
if self.sliders_per_row_layout is None:
|
|
166
|
+
self.sliders_per_row_layout = 1
|
|
167
|
+
assert self.sliders_layout in ["compact", "vertical", "collapsible", "smart"]
|
|
168
|
+
ctrl_dict_by_type = {"all": list(range(len(self.ctrl)))}
|
|
169
|
+
categories = ["all"]
|
|
170
|
+
if self.sliders_layout == "compact":
|
|
171
|
+
selected_mode = gr.Row()
|
|
172
|
+
elif self.sliders_layout == "vertical":
|
|
173
|
+
# Use Column to stack elements vertically
|
|
174
|
+
selected_mode = gr.Column()
|
|
175
|
+
elif self.sliders_layout == "collapsible":
|
|
176
|
+
selected_mode = gr.Accordion("Parameters", open=True)
|
|
177
|
+
elif self.sliders_layout == "smart":
|
|
178
|
+
# Group sliders by type
|
|
179
|
+
ctrl_dict_by_type = {}
|
|
180
|
+
for ctrl_index, ctrl_key in enumerate(self.ctrl.keys()):
|
|
181
|
+
ctrl_type = self.ctrl[ctrl_key]._type
|
|
182
|
+
ctrl_type = str(ctrl_type).split("<class '")[1].replace("'>", "")
|
|
183
|
+
if ctrl_type == "int":
|
|
184
|
+
ctrl_type = "float"
|
|
185
|
+
if ctrl_type not in ctrl_dict_by_type:
|
|
186
|
+
ctrl_dict_by_type[ctrl_type] = []
|
|
187
|
+
ctrl_dict_by_type[ctrl_type].append(ctrl_index)
|
|
188
|
+
categories = ["str", "bool", "float"]
|
|
189
|
+
selected_mode = gr.Column()
|
|
190
|
+
else:
|
|
191
|
+
raise NotImplementedError(f"Sliders layout {self.sliders_layout} not supported")
|
|
192
|
+
for ctrl_type in categories:
|
|
193
|
+
ctrl_indices = ctrl_dict_by_type.get(ctrl_type, [])
|
|
194
|
+
if len(ctrl_indices) == 0:
|
|
195
|
+
continue
|
|
196
|
+
if self.sliders_per_row_layout == 1:
|
|
197
|
+
with selected_mode:
|
|
198
|
+
for idx in ctrl_indices:
|
|
199
|
+
elem = self.widget_list[idx]
|
|
200
|
+
elem.render()
|
|
201
|
+
else:
|
|
202
|
+
with selected_mode:
|
|
203
|
+
for split_num in range(math.ceil(len(ctrl_indices)/self.sliders_per_row_layout)):
|
|
204
|
+
with gr.Row():
|
|
205
|
+
start = split_num*self.sliders_per_row_layout
|
|
206
|
+
end = min((split_num+1)*self.sliders_per_row_layout, len(ctrl_indices))
|
|
207
|
+
for idx in ctrl_indices[start:end]:
|
|
208
|
+
elem = self.widget_list[idx]
|
|
209
|
+
elem.render()
|
|
210
|
+
|
|
211
|
+
with gr.Accordion("Reset to default values", open=False):
|
|
212
|
+
gr.Examples([self.default_values], inputs=self.widget_list, label="Presets")
|
|
213
|
+
|
|
214
|
+
if self.markdown_description is not None:
|
|
215
|
+
with gr.Row():
|
|
216
|
+
gr.Markdown(self.markdown_description)
|
|
128
217
|
io.load(fn=self.run_fn, inputs=self.widget_list, outputs=outputs)
|
|
129
218
|
for idx in range(len(self.widget_list)):
|
|
130
219
|
self.widget_list[idx].change(
|
|
@@ -167,7 +256,10 @@ class MainWindow(InteractivePipeWindow):
|
|
|
167
256
|
|
|
168
257
|
def add_image_placeholder(self, row, col):
|
|
169
258
|
ax_placeholder = None
|
|
170
|
-
text_label =
|
|
259
|
+
text_label = self.get_current_style(row, col).get("title", "")
|
|
171
260
|
image_label = None
|
|
172
261
|
self.image_canvas[row][col] = {
|
|
173
262
|
"image": image_label, "title": text_label, "ax_placeholder": ax_placeholder}
|
|
263
|
+
|
|
264
|
+
def update_image(self, content, row, col):
|
|
265
|
+
pass
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/qt_control.py
RENAMED
|
@@ -8,14 +8,14 @@ PYQTVERSION = None
|
|
|
8
8
|
|
|
9
9
|
if not PYQTVERSION:
|
|
10
10
|
try:
|
|
11
|
-
from PyQt6.QtWidgets import QWidget, QLabel, QSlider, QHBoxLayout, QLineEdit, QComboBox, QCheckBox, QPushButton, QHBoxLayout
|
|
11
|
+
from PyQt6.QtWidgets import QWidget, QLabel, QSlider, QHBoxLayout, QLineEdit, QComboBox, QCheckBox, QPushButton, QHBoxLayout, QDial
|
|
12
12
|
from PyQt6.QtCore import Qt, QSize
|
|
13
13
|
from PyQt6.QtGui import QIcon
|
|
14
14
|
PYQTVERSION = 6
|
|
15
15
|
except ImportError:
|
|
16
16
|
logging.warning("Cannot import PyQt")
|
|
17
17
|
try:
|
|
18
|
-
from PyQt5.QtWidgets import QWidget, QLabel, QSlider, QHBoxLayout, QLineEdit, QComboBox, QCheckBox, QPushButton, QHBoxLayout
|
|
18
|
+
from PyQt5.QtWidgets import QWidget, QLabel, QSlider, QHBoxLayout, QLineEdit, QComboBox, QCheckBox, QPushButton, QHBoxLayout, QDial
|
|
19
19
|
from PyQt5.QtCore import Qt, QSize
|
|
20
20
|
from PyQt5.QtGui import QIcon
|
|
21
21
|
PYQTVERSION = 5
|
|
@@ -23,7 +23,7 @@ if not PYQTVERSION:
|
|
|
23
23
|
raise ModuleNotFoundError("No PyQt")
|
|
24
24
|
if not PYQTVERSION:
|
|
25
25
|
try:
|
|
26
|
-
from PySide6.QtWidgets import QWidget, QLabel, QSlider, QHBoxLayout, QLineEdit, QComboBox, QCheckBox, QPushButton, QHBoxLayout
|
|
26
|
+
from PySide6.QtWidgets import QWidget, QLabel, QSlider, QHBoxLayout, QLineEdit, QComboBox, QCheckBox, QPushButton, QHBoxLayout, QDial
|
|
27
27
|
from PySide6.QtCore import Qt, QSize
|
|
28
28
|
from PySide6.QtGui import QIcon
|
|
29
29
|
PYQTVERSION = 6
|
|
@@ -86,8 +86,17 @@ class IntSliderControl(BaseControl):
|
|
|
86
86
|
assert self.ctrl._type == int
|
|
87
87
|
|
|
88
88
|
def create(self):
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
if self.silent:
|
|
90
|
+
slider_class = SilentSlider
|
|
91
|
+
slider = slider_class(Qt.Orientation.Horizontal, self)
|
|
92
|
+
else:
|
|
93
|
+
if hasattr(self.ctrl, 'modulo') and self.ctrl.modulo:
|
|
94
|
+
slider_class = QDial
|
|
95
|
+
slider = slider_class(self)
|
|
96
|
+
else:
|
|
97
|
+
slider_class = QSlider
|
|
98
|
+
slider = slider_class(Qt.Orientation.Horizontal, self)
|
|
99
|
+
|
|
91
100
|
slider.setRange(self.ctrl.value_range[0], self.ctrl.value_range[1])
|
|
92
101
|
slider.setValue(self.ctrl.value_default)
|
|
93
102
|
slider.setSingleStep(1)
|
|
@@ -112,12 +121,16 @@ class FloatSliderControl(BaseControl):
|
|
|
112
121
|
return self.ctrl.value_range[0] + (self.ctrl.value_range[1]-self.ctrl.value_range[0])*val/1000
|
|
113
122
|
|
|
114
123
|
def create(self):
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
if self.silent:
|
|
125
|
+
slider_class = SilentSlider
|
|
126
|
+
slider = slider_class(Qt.Orientation.Horizontal, self)
|
|
127
|
+
else:
|
|
128
|
+
if hasattr(self.ctrl, 'modulo') and self.ctrl.modulo:
|
|
129
|
+
slider_class = QDial
|
|
130
|
+
slider = slider_class(self)
|
|
131
|
+
else:
|
|
132
|
+
slider_class = QSlider
|
|
133
|
+
slider = slider_class(Qt.Orientation.Horizontal, self)
|
|
121
134
|
self.ctrl.convert_int_to_value = self.convert_int_to_value
|
|
122
135
|
slider.setRange(self.convert_value_to_int(
|
|
123
136
|
self.ctrl.value_range[0]), self.convert_value_to_int(self.ctrl.value_range[1]))
|
|
@@ -143,3 +143,17 @@ class Control():
|
|
|
143
143
|
self.update_param_func = update_param_func
|
|
144
144
|
self.parameter_name_to_connect = parameter_name
|
|
145
145
|
self.filter_to_connect = filter
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class CircularControl(Control):
|
|
149
|
+
"""
|
|
150
|
+
Replace a slider by a circular slider
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, value_default: Union[int, float, bool, str], value_range: List[Union[int, float, str]] = None, modulo=True, name=None, step=None, filter_to_connect: Optional[FilterCore] = None, parameter_name_to_connect: Optional[str] = None) -> None:
|
|
154
|
+
super().__init__(value_default=value_default, value_range=value_range, name=name, step=step,
|
|
155
|
+
filter_to_connect=filter_to_connect, parameter_name_to_connect=parameter_name_to_connect, icons=None)
|
|
156
|
+
self.modulo = modulo
|
|
157
|
+
|
|
158
|
+
def __repr__(self) -> str:
|
|
159
|
+
return super().__repr__() + f"| modulo:{self.modulo}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!
|
|
5
5
|
Author-email: Balthazar Neveu <balthazarneveu@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/balthazarneveu/interactive_pipe
|
|
@@ -95,7 +95,7 @@ pip install -e ".[full]"
|
|
|
95
95
|
|
|
96
96
|
## :scroll: Features
|
|
97
97
|
|
|
98
|
-
**Version 0.7.
|
|
98
|
+
**Version 0.7.2**
|
|
99
99
|
- Modular multi-image processing filters
|
|
100
100
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
101
101
|
- Support in jupyter notebooks
|
|
@@ -103,7 +103,9 @@ pip install -e ".[full]"
|
|
|
103
103
|
- Cache intermediate results in RAM for much faster processing
|
|
104
104
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
105
105
|
- Support Curve plots (2D signals)
|
|
106
|
-
- :new: gradio backend (+allows sharing with others)
|
|
106
|
+
- :new: gradio backend (+allows sharing with others).
|
|
107
|
+
- :new: Audio support in Gradio
|
|
108
|
+
- :new: Circular sliders for Qt Backend
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
|
|
@@ -141,8 +143,9 @@ Shortcuts while using the GUI (QT & matplotlib backends)
|
|
|
141
143
|
| Plot curves | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
|
|
142
144
|
| Auto refreshed layout | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
|
|
143
145
|
| Keyboard shortcuts / fullscreen| :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
|
|
144
|
-
| Audio support | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :
|
|
146
|
+
| Audio support | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: |
|
|
145
147
|
| Image buttons| :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign:|
|
|
148
|
+
| Circular slider| :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign:|
|
|
146
149
|
|
|
147
150
|
|
|
148
151
|
|
|
@@ -15,6 +15,7 @@ src/interactive_pipe/core/graph.py
|
|
|
15
15
|
src/interactive_pipe/core/pipeline.py
|
|
16
16
|
src/interactive_pipe/core/signature.py
|
|
17
17
|
src/interactive_pipe/data_objects/__init__.py
|
|
18
|
+
src/interactive_pipe/data_objects/audio.py
|
|
18
19
|
src/interactive_pipe/data_objects/curves.py
|
|
19
20
|
src/interactive_pipe/data_objects/data.py
|
|
20
21
|
src/interactive_pipe/data_objects/image.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/__init__.py
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/curves.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/image.py
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/parameters.py
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_control.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_window.py
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/nb_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
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/choose_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/filter_decorator.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/pipeline_decorator.py
RENAMED
|
File without changes
|
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/thirdparty/music_spotify.py
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/requires.txt
RENAMED
|
File without changes
|
{interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/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
|
|
File without changes
|