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.
Files changed (61) hide show
  1. {interactive_pipe-0.7.0/src/interactive_pipe.egg-info → interactive_pipe-0.7.2}/PKG-INFO +7 -4
  2. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/pyproject.toml +1 -1
  3. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/readme.md +6 -3
  4. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/__init__.py +2 -1
  5. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/cache.py +0 -2
  6. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/engine.py +2 -3
  7. interactive_pipe-0.7.2/src/interactive_pipe/data_objects/audio.py +35 -0
  8. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gradio_control.py +10 -2
  9. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gradio_gui.py +103 -11
  10. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/qt_control.py +24 -11
  11. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/control.py +14 -0
  12. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2/src/interactive_pipe.egg-info}/PKG-INFO +7 -4
  13. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/SOURCES.txt +1 -0
  14. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/LICENSE +0 -0
  15. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/setup.cfg +0 -0
  16. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/__init__.py +0 -0
  17. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/filter.py +0 -0
  18. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/graph.py +0 -0
  19. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/pipeline.py +0 -0
  20. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/core/signature.py +0 -0
  21. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/__init__.py +0 -0
  22. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/curves.py +0 -0
  23. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/data.py +0 -0
  24. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/image.py +0 -0
  25. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/data_objects/parameters.py +0 -0
  26. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/__init__.py +0 -0
  27. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/gui.py +0 -0
  28. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_control.py +0 -0
  29. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_gui.py +0 -0
  30. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/mpl_window.py +0 -0
  31. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/nb_control.py +0 -0
  32. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/nb_gui.py +0 -0
  33. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/qt_gui.py +0 -0
  34. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/graphical/window.py +0 -0
  35. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/__init__.py +0 -0
  36. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/keyboard.py +0 -0
  37. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/headless/pipeline.py +0 -0
  38. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/__init__.py +0 -0
  39. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/_private.py +0 -0
  40. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/choose_backend.py +0 -0
  41. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
  42. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/filter_decorator.py +0 -0
  43. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/keyword_args_analyzer.py +0 -0
  44. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/helper/pipeline_decorator.py +0 -0
  45. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/thirdparty/images_openai_api.py +0 -0
  46. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe/thirdparty/music_spotify.py +0 -0
  47. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
  48. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/requires.txt +0 -0
  49. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/src/interactive_pipe.egg-info/top_level.txt +0 -0
  50. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_cache.py +0 -0
  51. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_control_abbreviations.py +0 -0
  52. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_controller.py +0 -0
  53. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_core.py +0 -0
  54. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_curves.py +0 -0
  55. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_decorator.py +0 -0
  56. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_engine.py +0 -0
  57. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_filter.py +0 -0
  58. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_headless.py +0 -0
  59. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_image.py +0 -0
  60. {interactive_pipe-0.7.0 → interactive_pipe-0.7.2}/test/test_parameters.py +0 -0
  61. {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.0
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.0**
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: | :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
 
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "interactive_pipe"
8
- version = "0.7.0"
8
+ version = "0.7.2"
9
9
  authors = [
10
10
  { name="Balthazar Neveu", email="balthazarneveu@gmail.com" },
11
11
  ]
@@ -51,7 +51,7 @@ pip install -e ".[full]"
51
51
 
52
52
  ## :scroll: Features
53
53
 
54
- **Version 0.7.0**
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: | :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->", [type(inp)
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->", [type(ou) for ou in out])
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
@@ -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, minimum=self.ctrl.value_range[0], maximum=self.ctrl.value_range[1], label=self.name, step=1)
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], maximum=self.ctrl.value_range[1], label=self.name)
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
 
@@ -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
- # self.set_default_key_bindings()
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
- out_list_gradio_containers.append(gr.Plot())
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
- for elem in self.widget_list:
124
- with gr.Row():
125
- elem.render()
126
- with gr.Row():
127
- gr.Examples([self.default_values], inputs=self.widget_list)
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 = None
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
@@ -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
- slider_class = SilentSlider if self.silent else QSlider
90
- slider = slider_class(Qt.Orientation.Horizontal, self)
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
- # Create a horizontal layout to hold the slider and line edit
116
- # hbox = QHBoxLayout()
117
- slider_class = SilentSlider if self.silent else QSlider
118
-
119
- # Create the slider with integer range and step size
120
- slider = slider_class(Qt.Orientation.Horizontal, self)
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.0
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.0**
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: | :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