interactive-pipe 0.7.3__tar.gz → 0.7.5__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 (62) hide show
  1. {interactive_pipe-0.7.3/src/interactive_pipe.egg-info → interactive_pipe-0.7.5}/PKG-INFO +60 -14
  2. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/pyproject.toml +1 -1
  3. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/readme.md +59 -13
  4. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/filter.py +15 -7
  5. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/pipeline.py +32 -3
  6. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/gradio_gui.py +7 -1
  7. interactive_pipe-0.7.5/src/interactive_pipe/helper/pipeline_decorator.py +57 -0
  8. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/thirdparty/images_openai_api.py +9 -1
  9. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5/src/interactive_pipe.egg-info}/PKG-INFO +60 -14
  10. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_core.py +1 -1
  11. interactive_pipe-0.7.3/src/interactive_pipe/helper/pipeline_decorator.py +0 -34
  12. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/LICENSE +0 -0
  13. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/setup.cfg +0 -0
  14. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/__init__.py +0 -0
  15. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/__init__.py +0 -0
  16. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/cache.py +0 -0
  17. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/engine.py +0 -0
  18. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/graph.py +0 -0
  19. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/core/signature.py +0 -0
  20. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/data_objects/__init__.py +0 -0
  21. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/data_objects/audio.py +0 -0
  22. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/data_objects/curves.py +0 -0
  23. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/data_objects/data.py +0 -0
  24. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/data_objects/image.py +0 -0
  25. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/data_objects/parameters.py +0 -0
  26. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/__init__.py +0 -0
  27. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/gradio_control.py +0 -0
  28. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/gui.py +0 -0
  29. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/mpl_control.py +0 -0
  30. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/mpl_gui.py +0 -0
  31. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/mpl_window.py +0 -0
  32. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/nb_control.py +0 -0
  33. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/nb_gui.py +0 -0
  34. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/qt_control.py +0 -0
  35. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/qt_gui.py +0 -0
  36. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/graphical/window.py +0 -0
  37. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/headless/__init__.py +0 -0
  38. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/headless/control.py +0 -0
  39. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/headless/keyboard.py +0 -0
  40. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/headless/pipeline.py +0 -0
  41. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/helper/__init__.py +0 -0
  42. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/helper/_private.py +0 -0
  43. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/helper/choose_backend.py +0 -0
  44. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
  45. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/helper/filter_decorator.py +0 -0
  46. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/helper/keyword_args_analyzer.py +0 -0
  47. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe/thirdparty/music_spotify.py +0 -0
  48. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe.egg-info/SOURCES.txt +0 -0
  49. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
  50. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe.egg-info/requires.txt +0 -0
  51. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/src/interactive_pipe.egg-info/top_level.txt +0 -0
  52. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_cache.py +0 -0
  53. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_control_abbreviations.py +0 -0
  54. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_controller.py +0 -0
  55. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_curves.py +0 -0
  56. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_decorator.py +0 -0
  57. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_engine.py +0 -0
  58. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_filter.py +0 -0
  59. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_headless.py +0 -0
  60. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_image.py +0 -0
  61. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/test/test_parameters.py +0 -0
  62. {interactive_pipe-0.7.3 → interactive_pipe-0.7.5}/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
3
+ Version: 0.7.5
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
@@ -100,10 +100,13 @@ pip install -e ".[full]"
100
100
 
101
101
 
102
102
  ----------------------
103
+ ## :scroll: Terminology
104
+
105
+ ![interactive_pipe_concept](static/interact-pipe-concept.svg)
103
106
 
104
107
  ## :scroll: Features
105
108
 
106
- **Version 0.7.3**
109
+ **Version 0.7.5**
107
110
  - Modular multi-image processing filters
108
111
  - Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
109
112
  - Support in jupyter notebooks
@@ -147,7 +150,7 @@ Shortcuts while using the GUI (QT & matplotlib backends)
147
150
  | :star: | *PyQt / PySide* | *Matplotlib* | *Jupyter notebooks including Google collab* | *Gradio* |
148
151
  |:-----: |:-----:|:------:|:----: |:----: |
149
152
  | Backend name | `qt` | `mpl` | `nb`| `gradio` |
150
- | Preview | ![qt backend](/doc/images/qt_backend.jpg) | ![mpl backend](/doc/images/mpl_backend.jpg) | ![nb backend](/doc/images/notebook_backend.jpg) | |
153
+ | Preview | ![qt backend](/doc/images/qt_backend.jpg) | ![mpl backend](/doc/images/mpl_backend.jpg) | ![nb backend](/doc/images/notebook_backend.jpg) | ![mpl backend](/doc/images/gradio_backend.jpg)|
151
154
  | Plot curves | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
152
155
  | Auto refreshed layout | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
153
156
  | Keyboard shortcuts / fullscreen| :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
@@ -158,26 +161,29 @@ Shortcuts while using the GUI (QT & matplotlib backends)
158
161
 
159
162
 
160
163
  # Tutorials
164
+ ### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
165
+ ![tuto](/doc/images/tutorial.png)
161
166
 
162
167
  ### Learn by examples
163
- #### [Basic image processing](/demo/multi_image.py)
168
+ #### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
164
169
  | GUI | Pipeline |
165
170
  |:--:|:--:|
166
171
  |![](/doc/images/image_selector.png) | ![](/doc/images/image_selector_graph.png) |
167
172
 
168
173
  ![](/doc/images/demo_multi_image.gif)
169
174
 
170
- #### [Speech exploration notebook](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
175
+ #### [Speech exploration notebook (colab, signal processing)](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
171
176
  ![Speech processing exploration in a notebook](/doc/images/jupyter_integration_speech.png)
172
177
 
173
178
 
174
179
 
175
180
 
181
+ ## :rocket: Ultra short code
182
+
176
183
 
177
- # Tutorials
178
184
  *Since ipywidgets in notebooks are supported, the tutorial is also available in a [google collab notebook](https://colab.research.google.com/drive/1PZn8P_5TABVCugT3IcLespvZG-gxnFbO?usp=sharing)*
179
185
 
180
- ## :rocket: Ultra short code
186
+
181
187
  Let's define 3 image processing very basic filters `exposure`, `black_and_white` & `blend`.
182
188
 
183
189
  By design:
@@ -253,11 +259,11 @@ import numpy as np
253
259
 
254
260
  COLOR_DICT = {"red": [1., 0., 0.], "green": [0., 1.,0.], "blue": [0., 0., 1.], "gray": [0.5, 0.5, 0.5]}
255
261
  @interactive()
256
- def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], global_params={}):
262
+ def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], context={}):
257
263
  '''Generate a constant colorful image
258
264
  '''
259
265
  flat_array = np.array(COLOR_DICT.get(color_choice)) * np.ones((64, 64, 3))
260
- global_params["avg"] = np.average(flat_array)
266
+ context["avg"] = np.average(flat_array)
261
267
  return flat_array
262
268
  ```
263
269
 
@@ -266,15 +272,15 @@ def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], g
266
272
  ----------
267
273
 
268
274
  :bulb: Can filters communicate together?
269
- Yes, using the special keyword argument `global_params={}`.
270
- - Check carefully how we stored the image average of the flat image in global_params.
275
+ Yes, using the special keyword argument `context={}`.
276
+ - Check carefully how we stored the image average of the flat image in context.
271
277
  - This value will be available to other filters.
272
278
  `special_image_slice` is going to use that value to set the half bottom image to dark in case the average is high.
273
279
 
274
280
  ```python
275
281
  @interactive()
276
- def special_image_slice(img, global_params={}):
277
- if global_params["avg"] > 0.4:
282
+ def special_image_slice(img, context={}):
283
+ if context["avg"] > 0.4:
278
284
  out_img[out_img.shape[0]//2:, ...] = 0.
279
285
  return out_img
280
286
  ```
@@ -292,7 +298,7 @@ Note that you can create a filter to switch between several images. In `["pagedo
292
298
 
293
299
  ```python
294
300
  @interactive()
295
- def black_top_image_slice(img, top_slice_black=(True, "special", "k"), global_params={}):
301
+ def black_top_image_slice(img, top_slice_black=(True, "special", "k"), context={}):
296
302
  out_img = img.copy()
297
303
  if top_slice_black:
298
304
  out_img[:out_img.shape[0]//2, ...] = 0.
@@ -320,7 +326,47 @@ if __name__ == '__main__':
320
326
  - September 2024: Gradio backend
321
327
 
322
328
 
329
+ ### FAQ
330
+ - :question: Is there a difference between `global_params` and `context` ?
331
+ > No, `global_params`, `global_parameters`, `global_state`, `global_context`, `context`, `state` all mean the same thing and are all supported for legacy reasons. `context` is the preferred wording.
332
+ - :question: Do I have to remove `KeyboardSlider` when using gradio or notebook backends?
333
+ > No, don't worry, these will be mapped back to regular sliders!
334
+ - :question: How do I play audio live?
335
+ > :sound: Inside a processing block, write the audio file to disk
336
+ > and use `context["__set_audio"](audio_file)`
337
+ - :question: Do I have to decorate my processing block using the `@interactive`
338
+ > If you use the `@` decoration style, your function won't be useable in a regular manner (wich may be problematic in a serious development environment)
339
+ ```python
340
+ @interactive(angle=(0., [-360., 360.]))
341
+ def processing_block(angle=0.):
342
+ ...
343
+ ```
323
344
 
345
+ > An alternative is to decorate the processing block outside... in a file dedicated to interactivity for instance
346
+ ```python
347
+ # core_filter.py
348
+ def processing_block(angle=0.):
349
+ ...
350
+ ```
351
+
352
+ ```python
353
+ # graphical.py
354
+ from core_filter import processing_block
355
+
356
+ def add_interactivity():
357
+ interactive(angle=(0., [-360., 360.]))(processing_block)
358
+ ```
359
+ - :question: Can I call the pipeline in a command line/batch fashion?
360
+ > Yes, headless mode is supported. :soon: documentation needed.
361
+
362
+
363
+ - :question: Can I use inplace operations?
364
+ > Better avoid these in general. To avoid making extra copies, computing hashes everywhere and avoid loosing precious computation time, there are no checks that inputs are not modified in place.
365
+ ```python
366
+ # Don't do that!
367
+ def bad_processing_block(inp):
368
+ inp+=1
369
+ ```
324
370
  # Roadmap and todos
325
371
  :bug: Want to contribute or interested in adding new features? Enter a new [Github issue](https://github.com/balthazarneveu/interactive_pipe/issues)
326
372
 
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "interactive_pipe"
8
- version = "0.7.3"
8
+ version = "0.7.5"
9
9
  authors = [
10
10
  { name="Balthazar Neveu", email="balthazarneveu@gmail.com" },
11
11
  ]
@@ -56,10 +56,13 @@ pip install -e ".[full]"
56
56
 
57
57
 
58
58
  ----------------------
59
+ ## :scroll: Terminology
60
+
61
+ ![interactive_pipe_concept](static/interact-pipe-concept.svg)
59
62
 
60
63
  ## :scroll: Features
61
64
 
62
- **Version 0.7.3**
65
+ **Version 0.7.5**
63
66
  - Modular multi-image processing filters
64
67
  - Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
65
68
  - Support in jupyter notebooks
@@ -103,7 +106,7 @@ Shortcuts while using the GUI (QT & matplotlib backends)
103
106
  | :star: | *PyQt / PySide* | *Matplotlib* | *Jupyter notebooks including Google collab* | *Gradio* |
104
107
  |:-----: |:-----:|:------:|:----: |:----: |
105
108
  | Backend name | `qt` | `mpl` | `nb`| `gradio` |
106
- | Preview | ![qt backend](/doc/images/qt_backend.jpg) | ![mpl backend](/doc/images/mpl_backend.jpg) | ![nb backend](/doc/images/notebook_backend.jpg) | |
109
+ | Preview | ![qt backend](/doc/images/qt_backend.jpg) | ![mpl backend](/doc/images/mpl_backend.jpg) | ![nb backend](/doc/images/notebook_backend.jpg) | ![mpl backend](/doc/images/gradio_backend.jpg)|
107
110
  | Plot curves | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
108
111
  | Auto refreshed layout | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
109
112
  | Keyboard shortcuts / fullscreen| :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
@@ -114,26 +117,29 @@ Shortcuts while using the GUI (QT & matplotlib backends)
114
117
 
115
118
 
116
119
  # Tutorials
120
+ ### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
121
+ ![tuto](/doc/images/tutorial.png)
117
122
 
118
123
  ### Learn by examples
119
- #### [Basic image processing](/demo/multi_image.py)
124
+ #### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
120
125
  | GUI | Pipeline |
121
126
  |:--:|:--:|
122
127
  |![](/doc/images/image_selector.png) | ![](/doc/images/image_selector_graph.png) |
123
128
 
124
129
  ![](/doc/images/demo_multi_image.gif)
125
130
 
126
- #### [Speech exploration notebook](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
131
+ #### [Speech exploration notebook (colab, signal processing)](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
127
132
  ![Speech processing exploration in a notebook](/doc/images/jupyter_integration_speech.png)
128
133
 
129
134
 
130
135
 
131
136
 
137
+ ## :rocket: Ultra short code
138
+
132
139
 
133
- # Tutorials
134
140
  *Since ipywidgets in notebooks are supported, the tutorial is also available in a [google collab notebook](https://colab.research.google.com/drive/1PZn8P_5TABVCugT3IcLespvZG-gxnFbO?usp=sharing)*
135
141
 
136
- ## :rocket: Ultra short code
142
+
137
143
  Let's define 3 image processing very basic filters `exposure`, `black_and_white` & `blend`.
138
144
 
139
145
  By design:
@@ -209,11 +215,11 @@ import numpy as np
209
215
 
210
216
  COLOR_DICT = {"red": [1., 0., 0.], "green": [0., 1.,0.], "blue": [0., 0., 1.], "gray": [0.5, 0.5, 0.5]}
211
217
  @interactive()
212
- def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], global_params={}):
218
+ def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], context={}):
213
219
  '''Generate a constant colorful image
214
220
  '''
215
221
  flat_array = np.array(COLOR_DICT.get(color_choice)) * np.ones((64, 64, 3))
216
- global_params["avg"] = np.average(flat_array)
222
+ context["avg"] = np.average(flat_array)
217
223
  return flat_array
218
224
  ```
219
225
 
@@ -222,15 +228,15 @@ def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], g
222
228
  ----------
223
229
 
224
230
  :bulb: Can filters communicate together?
225
- Yes, using the special keyword argument `global_params={}`.
226
- - Check carefully how we stored the image average of the flat image in global_params.
231
+ Yes, using the special keyword argument `context={}`.
232
+ - Check carefully how we stored the image average of the flat image in context.
227
233
  - This value will be available to other filters.
228
234
  `special_image_slice` is going to use that value to set the half bottom image to dark in case the average is high.
229
235
 
230
236
  ```python
231
237
  @interactive()
232
- def special_image_slice(img, global_params={}):
233
- if global_params["avg"] > 0.4:
238
+ def special_image_slice(img, context={}):
239
+ if context["avg"] > 0.4:
234
240
  out_img[out_img.shape[0]//2:, ...] = 0.
235
241
  return out_img
236
242
  ```
@@ -248,7 +254,7 @@ Note that you can create a filter to switch between several images. In `["pagedo
248
254
 
249
255
  ```python
250
256
  @interactive()
251
- def black_top_image_slice(img, top_slice_black=(True, "special", "k"), global_params={}):
257
+ def black_top_image_slice(img, top_slice_black=(True, "special", "k"), context={}):
252
258
  out_img = img.copy()
253
259
  if top_slice_black:
254
260
  out_img[:out_img.shape[0]//2, ...] = 0.
@@ -276,7 +282,47 @@ if __name__ == '__main__':
276
282
  - September 2024: Gradio backend
277
283
 
278
284
 
285
+ ### FAQ
286
+ - :question: Is there a difference between `global_params` and `context` ?
287
+ > No, `global_params`, `global_parameters`, `global_state`, `global_context`, `context`, `state` all mean the same thing and are all supported for legacy reasons. `context` is the preferred wording.
288
+ - :question: Do I have to remove `KeyboardSlider` when using gradio or notebook backends?
289
+ > No, don't worry, these will be mapped back to regular sliders!
290
+ - :question: How do I play audio live?
291
+ > :sound: Inside a processing block, write the audio file to disk
292
+ > and use `context["__set_audio"](audio_file)`
293
+ - :question: Do I have to decorate my processing block using the `@interactive`
294
+ > If you use the `@` decoration style, your function won't be useable in a regular manner (wich may be problematic in a serious development environment)
295
+ ```python
296
+ @interactive(angle=(0., [-360., 360.]))
297
+ def processing_block(angle=0.):
298
+ ...
299
+ ```
279
300
 
301
+ > An alternative is to decorate the processing block outside... in a file dedicated to interactivity for instance
302
+ ```python
303
+ # core_filter.py
304
+ def processing_block(angle=0.):
305
+ ...
306
+ ```
307
+
308
+ ```python
309
+ # graphical.py
310
+ from core_filter import processing_block
311
+
312
+ def add_interactivity():
313
+ interactive(angle=(0., [-360., 360.]))(processing_block)
314
+ ```
315
+ - :question: Can I call the pipeline in a command line/batch fashion?
316
+ > Yes, headless mode is supported. :soon: documentation needed.
317
+
318
+
319
+ - :question: Can I use inplace operations?
320
+ > Better avoid these in general. To avoid making extra copies, computing hashes everywhere and avoid loosing precious computation time, there are no checks that inputs are not modified in place.
321
+ ```python
322
+ # Don't do that!
323
+ def bad_processing_block(inp):
324
+ inp+=1
325
+ ```
280
326
  # Roadmap and todos
281
327
  :bug: Want to contribute or interested in adding new features? Enter a new [Github issue](https://github.com/balthazarneveu/interactive_pipe/issues)
282
328
 
@@ -5,6 +5,8 @@ from typing import Callable, List, Optional, Union, Tuple, Any
5
5
  from interactive_pipe.core.cache import CachedResults
6
6
  from interactive_pipe.core.signature import analyze_apply_fn_signature
7
7
 
8
+ EQUIVALENT_STATE_KEYS = ["global_params", "global_parameters", "global_state", "global_context", "context", "state"]
9
+
8
10
 
9
11
  class PureFilter:
10
12
  def __init__(self, apply_fn: Optional[Callable] = None, name: Optional[str] = None, default_params: dict = {}):
@@ -12,6 +14,7 @@ class PureFilter:
12
14
  self.__class__.__name__ if not apply_fn else apply_fn.__name__)
13
15
  if apply_fn is not None:
14
16
  self.apply = apply_fn
17
+ self._global_params = {}
15
18
  self.__initialize_default_values() # initialize default values from .apply method
16
19
  self.values = deepcopy(default_params)
17
20
 
@@ -40,8 +43,9 @@ class PureFilter:
40
43
  assert not hasattr(self, "_values")
41
44
  self.check_apply_signature()
42
45
  self._values = self.__kwargs_names
43
- if "global_params" in self.__kwargs_names.keys():
44
- self._values.pop("global_params")
46
+ for global_key in EQUIVALENT_STATE_KEYS:
47
+ if global_key in self.__kwargs_names.keys():
48
+ self._values.pop(global_key)
45
49
 
46
50
  @property
47
51
  def values(self):
@@ -60,11 +64,15 @@ class PureFilter:
60
64
  for key, val in self.values.items():
61
65
  assert key in self.__kwargs_names.keys(
62
66
  ), f"{self.name} : {key} not in {self.__kwargs_names.keys()}"
63
- if "global_params" in self.__kwargs_names.keys():
64
- # special key to provide the context dictionary
65
- out = self.apply(
66
- *imgs, global_params=self.global_params, **self.values)
67
- else:
67
+ global_key_found = False
68
+ for global_key in EQUIVALENT_STATE_KEYS:
69
+ if global_key in self.__kwargs_names.keys():
70
+ # special key to provide the context dictionary
71
+ out = self.apply(
72
+ *imgs, **{**{global_key: self.global_params}, **self.values})
73
+ global_key_found = True
74
+ break
75
+ if not global_key_found:
68
76
  out = self.apply(*imgs, **self.values)
69
77
  return out
70
78
 
@@ -11,13 +11,40 @@ class PipelineCore:
11
11
  - optionally, some inputs to process
12
12
  """
13
13
 
14
- def __init__(self, filters: List[FilterCore], name="pipeline", cache=False, inputs: Optional[list] = None, parameters: dict = {}, global_params={}, outputs: Optional[list] = None, safe_input_buffer_deepcopy: bool = True):
14
+ def __init__(
15
+ self,
16
+ filters: List[FilterCore],
17
+ name="pipeline",
18
+ cache=False,
19
+ inputs: Optional[list] = None,
20
+ parameters: dict = {},
21
+ global_params: Optional[dict] = None,
22
+ global_parameters: Optional[dict] = None, # alias for global_params
23
+ global_state: Optional[dict] = None, # alias for global_params
24
+ global_context: Optional[dict] = None, # alias for global_params
25
+ context: Optional[dict] = None, # alias for global_params
26
+ state: Optional[dict] = None, # alias for global_params
27
+ outputs: Optional[list] = None,
28
+ safe_input_buffer_deepcopy: bool = True
29
+ ):
15
30
  if not all(isinstance(f, FilterCore) for f in filters):
16
31
  raise ValueError(
17
32
  f"All elements in 'filters' must be instances of 'Filter'. {[type(f) for f in filters]}")
18
33
  self.filters = filters
19
34
  self.engine = PipelineEngine(
20
35
  cache, safe_input_buffer_deepcopy=safe_input_buffer_deepcopy)
36
+ if global_parameters is not None:
37
+ global_params = global_parameters
38
+ elif global_context is not None:
39
+ global_params = global_context
40
+ elif global_state is not None:
41
+ global_params = global_state
42
+ elif state is not None:
43
+ global_params = state
44
+ elif context is not None:
45
+ global_params = context
46
+ if global_params is None:
47
+ global_params = {}
21
48
  self.global_params = global_params
22
49
  for filter in self.filters:
23
50
  # link each filter to global params
@@ -79,7 +106,8 @@ class PipelineCore:
79
106
  """
80
107
  available_filters_names = [filt.name for filt in self.filters]
81
108
  for filter_name in new_parameters.keys():
82
- assert filter_name in available_filters_names, f"filter {filter_name} does not exist {available_filters_names}"
109
+ assert filter_name in available_filters_names, f"filter {filter_name}" + \
110
+ f"does not exist {available_filters_names}"
83
111
  self.filters[available_filters_names.index(
84
112
  filter_name)].values = new_parameters[filter_name]
85
113
 
@@ -99,7 +127,8 @@ class PipelineCore:
99
127
  elif isinstance(inputs, list) or isinstance(inputs, tuple):
100
128
  # inputs is a list or a tuple
101
129
  assert len(inputs) == len(
102
- self.inputs_routing), f"wrong amount of inputs\nprovided {len(inputs)} inputs vs expected:{len(self.inputs_routing)}"
130
+ self.inputs_routing), f"wrong amount of inputs\nprovided {len(inputs)}" + \
131
+ f"inputs vs expected:{len(self.inputs_routing)}"
103
132
  self.__inputs = {}
104
133
  for idx, input_name in enumerate(self.inputs_routing):
105
134
  self.__inputs[input_name] = inputs[idx]
@@ -212,7 +212,13 @@ class MainWindow(InteractivePipeWindow):
212
212
  gr.Examples([self.default_values], inputs=self.widget_list, label="Presets")
213
213
 
214
214
  if self.markdown_description is not None:
215
- with gr.Row():
215
+ title = "Description"
216
+ try:
217
+ if self.markdown_description.startswith("#"):
218
+ title = self.markdown_description.split("\n")[0][1:]
219
+ except Exception as _e:
220
+ pass
221
+ with gr.Accordion(title, open=False):
216
222
  gr.Markdown(self.markdown_description)
217
223
  io.load(fn=self.run_fn, inputs=self.widget_list, outputs=outputs)
218
224
  for idx in range(len(self.widget_list)):
@@ -0,0 +1,57 @@
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, Optional
6
+ from interactive_pipe.helper.choose_backend import get_interactive_pipeline_class
7
+
8
+ None
9
+
10
+
11
+ def pipeline(pipeline_function: Callable, **kwargs) -> HeadlessPipeline:
12
+ return HeadlessPipeline.from_function(pipeline_function, **kwargs)
13
+
14
+
15
+ def interactive_pipeline(
16
+ gui="auto",
17
+ safe_input_buffer_deepcopy=True,
18
+ cache=False,
19
+ output_canvas=None,
20
+ global_params: Optional[dict] = None,
21
+ global_parameters: Optional[dict] = None, # alias for global_params
22
+ global_state: Optional[dict] = None, # alias for global_params
23
+ global_context: Optional[dict] = None, # alias for global_params
24
+ context: Optional[dict] = None, # alias for global_params
25
+ state: Optional[dict] = None, # alias for global_params
26
+ **kwargs_gui,
27
+ ) -> Union[HeadlessPipeline, InteractivePipeGUI]:
28
+ """@interactive_pipeline
29
+
30
+ Function decorator to add some controls @interactive_pipeline
31
+ """
32
+ def wrapper(pipeline_function):
33
+ headless_pipeline = HeadlessPipeline.from_function(
34
+ pipeline_function,
35
+ safe_input_buffer_deepcopy=safe_input_buffer_deepcopy,
36
+ cache=cache,
37
+ global_params=global_params,
38
+ global_parameters=global_parameters, # alias for global_params
39
+ global_state=global_state, # alias for global_params
40
+ global_context=global_context, # alias for global_params
41
+ context=context, # alias for global_params
42
+ state=state, # alias for global_params
43
+ )
44
+ if output_canvas is not None:
45
+ headless_pipeline.outputs = output_canvas
46
+ if gui is None:
47
+ return headless_pipeline
48
+ else:
49
+ InteractivePipeGui = get_interactive_pipeline_class(gui)
50
+ gui_pipeline = InteractivePipeGui(
51
+ pipeline=headless_pipeline, name=pipeline_function.__name__, **kwargs_gui)
52
+
53
+ @ functools.wraps(pipeline_function)
54
+ def inner(*args, **kwargs):
55
+ return gui_pipeline.__call__(*args, **kwargs)
56
+ return inner
57
+ return wrapper
@@ -6,7 +6,12 @@ from typing import Union
6
6
  from interactive_pipe.data_objects.parameters import Parameters
7
7
  from interactive_pipe.data_objects.image import Image
8
8
  import os
9
- import openai
9
+ OPENAI_SUPPORT = True
10
+ try:
11
+ import openai
12
+ except ImportError:
13
+ OPENAI_SUPPORT = False
14
+ logging.warning("openai not installed, please install it with pip install openai")
10
15
 
11
16
 
12
17
  class ImageFromPrompt(Image):
@@ -57,6 +62,7 @@ class ImageFromPrompt(Image):
57
62
 
58
63
  @staticmethod
59
64
  def login(api_key=None, organization=None):
65
+ assert OPENAI_SUPPORT, "openai not installed"
60
66
  if api_key is not None:
61
67
  openai.api_key = api_key
62
68
  openai.organization = organization
@@ -75,6 +81,7 @@ class ImageFromPrompt(Image):
75
81
 
76
82
  @staticmethod
77
83
  def __check_login():
84
+ assert OPENAI_SUPPORT, "openai not installed"
78
85
  assert openai.api_key is not None
79
86
  try:
80
87
  response = openai.Model.list()
@@ -95,6 +102,7 @@ class ImageFromPrompt(Image):
95
102
  path = Path(path)
96
103
  path = path.with_suffix(ImageFromPrompt.SUFFIX)
97
104
  return path
105
+ assert OPENAI_SUPPORT, "openai not installed"
98
106
  try:
99
107
  # You need the API key at this moment
100
108
  ImageFromPrompt.login(api_key=api_key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: interactive_pipe
3
- Version: 0.7.3
3
+ Version: 0.7.5
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
@@ -100,10 +100,13 @@ pip install -e ".[full]"
100
100
 
101
101
 
102
102
  ----------------------
103
+ ## :scroll: Terminology
104
+
105
+ ![interactive_pipe_concept](static/interact-pipe-concept.svg)
103
106
 
104
107
  ## :scroll: Features
105
108
 
106
- **Version 0.7.3**
109
+ **Version 0.7.5**
107
110
  - Modular multi-image processing filters
108
111
  - Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
109
112
  - Support in jupyter notebooks
@@ -147,7 +150,7 @@ Shortcuts while using the GUI (QT & matplotlib backends)
147
150
  | :star: | *PyQt / PySide* | *Matplotlib* | *Jupyter notebooks including Google collab* | *Gradio* |
148
151
  |:-----: |:-----:|:------:|:----: |:----: |
149
152
  | Backend name | `qt` | `mpl` | `nb`| `gradio` |
150
- | Preview | ![qt backend](/doc/images/qt_backend.jpg) | ![mpl backend](/doc/images/mpl_backend.jpg) | ![nb backend](/doc/images/notebook_backend.jpg) | |
153
+ | Preview | ![qt backend](/doc/images/qt_backend.jpg) | ![mpl backend](/doc/images/mpl_backend.jpg) | ![nb backend](/doc/images/notebook_backend.jpg) | ![mpl backend](/doc/images/gradio_backend.jpg)|
151
154
  | Plot curves | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
152
155
  | Auto refreshed layout | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: |
153
156
  | Keyboard shortcuts / fullscreen| :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
@@ -158,26 +161,29 @@ Shortcuts while using the GUI (QT & matplotlib backends)
158
161
 
159
162
 
160
163
  # Tutorials
164
+ ### [Main tutorial](https://huggingface.co/spaces/balthou/interactive-pipe-tutorial)
165
+ ![tuto](/doc/images/tutorial.png)
161
166
 
162
167
  ### Learn by examples
163
- #### [Basic image processing](/demo/multi_image.py)
168
+ #### [Basic image processing (python code sample for PyQT GUI)](/demo/multi_image.py)
164
169
  | GUI | Pipeline |
165
170
  |:--:|:--:|
166
171
  |![](/doc/images/image_selector.png) | ![](/doc/images/image_selector_graph.png) |
167
172
 
168
173
  ![](/doc/images/demo_multi_image.gif)
169
174
 
170
- #### [Speech exploration notebook](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
175
+ #### [Speech exploration notebook (colab, signal processing)](https://colab.research.google.com/drive/1mUX2FW0qflWn-v3nIx90P_KvRxnXlBpz#scrollTo=qDTaIwvaJQ6R)
171
176
  ![Speech processing exploration in a notebook](/doc/images/jupyter_integration_speech.png)
172
177
 
173
178
 
174
179
 
175
180
 
181
+ ## :rocket: Ultra short code
182
+
176
183
 
177
- # Tutorials
178
184
  *Since ipywidgets in notebooks are supported, the tutorial is also available in a [google collab notebook](https://colab.research.google.com/drive/1PZn8P_5TABVCugT3IcLespvZG-gxnFbO?usp=sharing)*
179
185
 
180
- ## :rocket: Ultra short code
186
+
181
187
  Let's define 3 image processing very basic filters `exposure`, `black_and_white` & `blend`.
182
188
 
183
189
  By design:
@@ -253,11 +259,11 @@ import numpy as np
253
259
 
254
260
  COLOR_DICT = {"red": [1., 0., 0.], "green": [0., 1.,0.], "blue": [0., 0., 1.], "gray": [0.5, 0.5, 0.5]}
255
261
  @interactive()
256
- def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], global_params={}):
262
+ def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], context={}):
257
263
  '''Generate a constant colorful image
258
264
  '''
259
265
  flat_array = np.array(COLOR_DICT.get(color_choice)) * np.ones((64, 64, 3))
260
- global_params["avg"] = np.average(flat_array)
266
+ context["avg"] = np.average(flat_array)
261
267
  return flat_array
262
268
  ```
263
269
 
@@ -266,15 +272,15 @@ def generate_flat_colored_image(color_choice=["red", "green", "blue", "gray"], g
266
272
  ----------
267
273
 
268
274
  :bulb: Can filters communicate together?
269
- Yes, using the special keyword argument `global_params={}`.
270
- - Check carefully how we stored the image average of the flat image in global_params.
275
+ Yes, using the special keyword argument `context={}`.
276
+ - Check carefully how we stored the image average of the flat image in context.
271
277
  - This value will be available to other filters.
272
278
  `special_image_slice` is going to use that value to set the half bottom image to dark in case the average is high.
273
279
 
274
280
  ```python
275
281
  @interactive()
276
- def special_image_slice(img, global_params={}):
277
- if global_params["avg"] > 0.4:
282
+ def special_image_slice(img, context={}):
283
+ if context["avg"] > 0.4:
278
284
  out_img[out_img.shape[0]//2:, ...] = 0.
279
285
  return out_img
280
286
  ```
@@ -292,7 +298,7 @@ Note that you can create a filter to switch between several images. In `["pagedo
292
298
 
293
299
  ```python
294
300
  @interactive()
295
- def black_top_image_slice(img, top_slice_black=(True, "special", "k"), global_params={}):
301
+ def black_top_image_slice(img, top_slice_black=(True, "special", "k"), context={}):
296
302
  out_img = img.copy()
297
303
  if top_slice_black:
298
304
  out_img[:out_img.shape[0]//2, ...] = 0.
@@ -320,7 +326,47 @@ if __name__ == '__main__':
320
326
  - September 2024: Gradio backend
321
327
 
322
328
 
329
+ ### FAQ
330
+ - :question: Is there a difference between `global_params` and `context` ?
331
+ > No, `global_params`, `global_parameters`, `global_state`, `global_context`, `context`, `state` all mean the same thing and are all supported for legacy reasons. `context` is the preferred wording.
332
+ - :question: Do I have to remove `KeyboardSlider` when using gradio or notebook backends?
333
+ > No, don't worry, these will be mapped back to regular sliders!
334
+ - :question: How do I play audio live?
335
+ > :sound: Inside a processing block, write the audio file to disk
336
+ > and use `context["__set_audio"](audio_file)`
337
+ - :question: Do I have to decorate my processing block using the `@interactive`
338
+ > If you use the `@` decoration style, your function won't be useable in a regular manner (wich may be problematic in a serious development environment)
339
+ ```python
340
+ @interactive(angle=(0., [-360., 360.]))
341
+ def processing_block(angle=0.):
342
+ ...
343
+ ```
323
344
 
345
+ > An alternative is to decorate the processing block outside... in a file dedicated to interactivity for instance
346
+ ```python
347
+ # core_filter.py
348
+ def processing_block(angle=0.):
349
+ ...
350
+ ```
351
+
352
+ ```python
353
+ # graphical.py
354
+ from core_filter import processing_block
355
+
356
+ def add_interactivity():
357
+ interactive(angle=(0., [-360., 360.]))(processing_block)
358
+ ```
359
+ - :question: Can I call the pipeline in a command line/batch fashion?
360
+ > Yes, headless mode is supported. :soon: documentation needed.
361
+
362
+
363
+ - :question: Can I use inplace operations?
364
+ > Better avoid these in general. To avoid making extra copies, computing hashes everywhere and avoid loosing precious computation time, there are no checks that inputs are not modified in place.
365
+ ```python
366
+ # Don't do that!
367
+ def bad_processing_block(inp):
368
+ inp+=1
369
+ ```
324
370
  # Roadmap and todos
325
371
  :bug: Want to contribute or interested in adding new features? Enter a new [Github issue](https://github.com/balthazarneveu/interactive_pipe/issues)
326
372
 
@@ -7,7 +7,7 @@ class MultiplyFilter(FilterCore):
7
7
 
8
8
 
9
9
  def test_pure_filter():
10
- def mult(img, global_parameters={}, scalar=8, add=5):
10
+ def mult(img, context={}, scalar=8, add=5):
11
11
  return [[value * scalar + add for value in row] for row in img]
12
12
  filt = PureFilter(apply_fn=mult, name="multiplication",
13
13
  default_params={"scalar": 2, "add": 0})
@@ -1,34 +0,0 @@
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", safe_input_buffer_deepcopy=True, 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(
20
- pipeline_function, safe_input_buffer_deepcopy=safe_input_buffer_deepcopy, cache=cache)
21
- if output_canvas is not None:
22
- headless_pipeline.outputs = output_canvas
23
- if gui is None:
24
- return headless_pipeline
25
- else:
26
- InteractivePipeGui = get_interactive_pipeline_class(gui)
27
- gui_pipeline = InteractivePipeGui(
28
- pipeline=headless_pipeline, name=pipeline_function.__name__, **kwargs_gui)
29
-
30
- @functools.wraps(pipeline_function)
31
- def inner(*args, **kwargs):
32
- return gui_pipeline.__call__(*args, **kwargs)
33
- return inner
34
- return wrapper