interactive-pipe 0.8.7__tar.gz → 0.8.8__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.8.8/LICENSE +21 -0
- {interactive_pipe-0.8.7/src/interactive_pipe.egg-info → interactive_pipe-0.8.8}/PKG-INFO +155 -23
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/pyproject.toml +2 -2
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/readme.md +151 -20
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/__init__.py +13 -1
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/cache.py +6 -5
- interactive_pipe-0.8.8/src/interactive_pipe/core/context.py +471 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/engine.py +94 -5
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/filter.py +85 -37
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/graph.py +13 -3
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/pipeline.py +47 -25
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/data_objects/audio.py +16 -7
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/data_objects/curves.py +88 -49
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/data_objects/data.py +24 -12
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/data_objects/image.py +14 -7
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/data_objects/parameters.py +12 -8
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/gradio_control.py +55 -14
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/gradio_gui.py +33 -14
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/gui.py +46 -13
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/mpl_control.py +24 -7
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/mpl_gui.py +19 -9
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/mpl_window.py +7 -3
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/nb_control.py +51 -9
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/nb_gui.py +12 -8
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/qt_control.py +79 -35
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/qt_gui.py +42 -22
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/headless/control.py +56 -31
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/headless/keyboard.py +9 -6
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/headless/pipeline.py +56 -16
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/filter_decorator.py +6 -4
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8/src/interactive_pipe.egg-info}/PKG-INFO +155 -23
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe.egg-info/SOURCES.txt +10 -2
- interactive_pipe-0.8.8/test/test_context.py +630 -0
- interactive_pipe-0.8.8/test/test_context_compatibility.py +427 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_control_abbreviations.py +25 -10
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_controller.py +5 -5
- interactive_pipe-0.8.8/test/test_core_exceptions.py +230 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_curves.py +2 -2
- interactive_pipe-0.8.8/test/test_data_objects_exceptions.py +297 -0
- interactive_pipe-0.8.8/test/test_edge_cases.py +248 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_filter.py +0 -1
- interactive_pipe-0.8.8/test/test_graphical_exceptions.py +322 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_headless.py +6 -6
- interactive_pipe-0.8.8/test/test_headless_exceptions.py +146 -0
- interactive_pipe-0.8.8/test/test_legacy_warnings.py +176 -0
- interactive_pipe-0.8.8/test/test_mutable_defaults.py +134 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_parameters.py +2 -2
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_recorder.py +0 -1
- interactive_pipe-0.8.7/LICENSE +0 -24
- interactive_pipe-0.8.7/src/interactive_pipe/thirdparty/images_openai_api.py +0 -159
- interactive_pipe-0.8.7/src/interactive_pipe/thirdparty/music_spotify.py +0 -55
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/setup.cfg +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/__init__.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/core/signature.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/data_objects/__init__.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/__init__.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/graphical/window.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/headless/__init__.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/__init__.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/_private.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/choose_backend.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/control_abbreviation.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/keyword_args_analyzer.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe/helper/pipeline_decorator.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe.egg-info/dependency_links.txt +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe.egg-info/requires.txt +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/src/interactive_pipe.egg-info/top_level.txt +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_cache.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_core.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_decorator.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_engine.py +0 -0
- {interactive_pipe-0.8.7 → interactive_pipe-0.8.8}/test/test_image.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Balthazar Neveu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: interactive_pipe
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.8
|
|
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
|
|
7
7
|
Project-URL: Bug Tracker, https://github.com/balthazarneveu/interactive_pipe/issues
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved ::
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
@@ -41,6 +41,7 @@ Requires-Dist: pytest>=6.2.5; extra == "full"
|
|
|
41
41
|
Requires-Dist: ipywidgets>=7.7.1; extra == "full"
|
|
42
42
|
Requires-Dist: pandas; extra == "full"
|
|
43
43
|
Requires-Dist: gradio; extra == "full"
|
|
44
|
+
Dynamic: license-file
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
<center>
|
|
@@ -69,8 +70,8 @@ Requires-Dist: gradio; extra == "full"
|
|
|
69
70
|
| Science notebook | Toddler DIY Jukebox on a raspberry Pi |
|
|
70
71
|
|:-----: |:-----:|
|
|
71
72
|
|  | 
|
|
72
|
-
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon.
|
|
73
|
-
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [
|
|
73
|
+
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon. Caption added through the title mechanism. Music samples generated by prompting [MusicGen](https://huggingface.co/spaces/facebook/MusicGen) |
|
|
74
|
+
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [jukebox_demo.py demo code](/demo/jukebox_demo.py)|
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
----------------------
|
|
@@ -107,19 +108,21 @@ pip install -e ".[full]"
|
|
|
107
108
|
|
|
108
109
|
## :scroll: Features
|
|
109
110
|
|
|
110
|
-
**Version 0.8.
|
|
111
|
+
**Version 0.8.8**
|
|
111
112
|
- Modular multi-image processing filters
|
|
112
113
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
113
114
|
- Support in jupyter notebooks
|
|
114
115
|
- Tuning sliders & check buttons with a GUI
|
|
115
116
|
- Cache intermediate results in RAM for much faster processing
|
|
116
117
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
117
|
-
- Support Curve plots (2D
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
118
|
+
- Support Curve plots (2D signals)
|
|
119
|
+
- Gradio backend (+allows sharing with others).
|
|
120
|
+
- Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
|
|
121
|
+
- Circular sliders for Qt Backend
|
|
122
|
+
- Text prompt (`free_text=("Hello world!", None),`)
|
|
123
|
+
- TimeControl (possibility to play/pause time using an incrementing timer)
|
|
124
|
+
- :new: **Context API**: Direct access to shared context across filters via `get_context()`, `context`, `layout`, `audio`
|
|
125
|
+
- :new: MIT License
|
|
123
126
|
|
|
124
127
|
|
|
125
128
|
|
|
@@ -327,21 +330,119 @@ if __name__ == '__main__':
|
|
|
327
330
|
|
|
328
331
|
----------
|
|
329
332
|
|
|
333
|
+
### Release Notes
|
|
334
|
+
|
|
335
|
+
#### Version 0.8.8 (January 2026)
|
|
336
|
+
|
|
337
|
+
**New Features:**
|
|
338
|
+
- **Clean Context API**: Access shared context directly without `global_params` pollution
|
|
339
|
+
- `get_context()` - Get the shared context dictionary
|
|
340
|
+
- `context` - Direct dict-like access to context
|
|
341
|
+
- `layout` - Access layout configuration directly
|
|
342
|
+
- `audio` - Access audio functionality directly
|
|
343
|
+
|
|
344
|
+
**Code Quality Improvements:**
|
|
345
|
+
- Replaced all assertions with proper exceptions (`ValueError`, `TypeError`, `RuntimeError`)
|
|
346
|
+
- Fixed all mutable default arguments across the codebase (prevents shared state bugs)
|
|
347
|
+
- Improved type hints with proper `Optional` and `Any` types
|
|
348
|
+
- Better error messages for debugging
|
|
349
|
+
|
|
350
|
+
**UX Improvements:**
|
|
351
|
+
- Dropdown menus are now hidden when only a single choice is available
|
|
352
|
+
- Helpful message displayed when Graphviz is not available (when pressing `G`)
|
|
353
|
+
- Fixed warning in linestyle for curves
|
|
354
|
+
|
|
355
|
+
**Bug Fixes:**
|
|
356
|
+
- Fixed audio initialization order in Qt backend
|
|
357
|
+
- Fixed pytest failures for optional dependencies in CI
|
|
358
|
+
- Fixed various edge cases in error handling
|
|
359
|
+
|
|
360
|
+
**Migration of old context or global_params:**
|
|
361
|
+
- Use `global_params=SharedContext.injected()` to let code interpreters know that there's no need to pass this parameter.
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
# Before
|
|
365
|
+
def apply_brightness(img:np.ndarray, brightness: float = 0.5, global_params:dict =None):
|
|
366
|
+
global_params["brightness"] = brightness
|
|
367
|
+
return img * brightness
|
|
368
|
+
|
|
369
|
+
# Progressive migration
|
|
370
|
+
def apply_brightness(img:np.ndarray, brightness: float = 0.5, global_params:dict ==SharedContext.injected()):
|
|
371
|
+
global_params["brightness"] = brightness
|
|
372
|
+
return img * brightness
|
|
373
|
+
|
|
374
|
+
# Recommended version
|
|
375
|
+
from interactive_pipe import context
|
|
376
|
+
def apply_brightness(img:np.ndarray, brightness: float = 0.5):
|
|
377
|
+
context.shared_brightness = brightness # shared with all filters
|
|
378
|
+
return img * brightness
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
**License:**
|
|
385
|
+
- Updated to MIT License
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
330
389
|
### History
|
|
331
|
-
- Interactive pipe was initially
|
|
390
|
+
- Interactive pipe was initially developed by [Balthazar Neveu](https://github.com/balthazarneveu) as part of the [irdrone project](https://github.com/wisescootering/infrareddrone/tree/master/interactive) based on matplotlib.
|
|
332
391
|
- Later, more contributions were also made by [Giuseppe Moschetti](https://github.com/g-moschetti) and Sylvain Leroy.
|
|
333
392
|
- August 2023: rewriting the whole core and supporting several graphical backends!
|
|
334
393
|
- September 2024: Gradio backend
|
|
394
|
+
- January 2026: Clean Context API and code quality improvements (v0.8.8)
|
|
335
395
|
|
|
336
396
|
|
|
337
397
|
### FAQ
|
|
338
|
-
- :question:
|
|
339
|
-
>
|
|
398
|
+
- :question: What is the recommended way to access shared context?
|
|
399
|
+
> **New in v0.8.8**: Use the clean context API for direct access:
|
|
400
|
+
> ```python
|
|
401
|
+
> from interactive_pipe import context, layout, audio, get_context
|
|
402
|
+
>
|
|
403
|
+
> @interactive()
|
|
404
|
+
> def my_filter(img):
|
|
405
|
+
> context["shared_key"] = "shared_value" # Direct dict-like access
|
|
406
|
+
> context.brightness = 0.5
|
|
407
|
+
> layout.set_title("output_image", "My Image") # Layout helpers
|
|
408
|
+
>
|
|
409
|
+
> return img
|
|
410
|
+
> ```
|
|
411
|
+
|
|
412
|
+
- :question: How do I change the layout? *Can I change the grid layout of images live? (like you compare 2 images side by side and you want to start comparing 4 images in a 2x2 fashion for debugging purpose)*. It is possible with Qt backend.
|
|
413
|
+
|
|
414
|
+
> Use the `layout` helper to control image arrangement and styling:
|
|
415
|
+
> ```python
|
|
416
|
+
> from interactive_pipe import layout
|
|
417
|
+
>
|
|
418
|
+
> def change_layout(layout: str="side_by_side"):
|
|
419
|
+
> # Arrange outputs in a 2x2 grid
|
|
420
|
+
> if layout == "side_by_side":
|
|
421
|
+
> layout.grid([["input", "result"]])
|
|
422
|
+
> if layout == "grid2x2":
|
|
423
|
+
> layout.grid([["input", "processed"], ["histogram_graph", "result"]])
|
|
424
|
+
> # Style individual outputs
|
|
425
|
+
> layout.style("result", title="Final Result")
|
|
426
|
+
> # Note that the string "input", "processed", "histogram_graph", "result"
|
|
427
|
+
> # are the variables used in the pipeline (see below)!
|
|
428
|
+
>
|
|
429
|
+
> def pipeline(input):
|
|
430
|
+
> processed = denoise(input)
|
|
431
|
+
> result = change_brightness(processed)
|
|
432
|
+
> histogram_graph = compute_histo(processed)
|
|
433
|
+
> change_layout()
|
|
434
|
+
> return result
|
|
435
|
+
> ```
|
|
436
|
+
|
|
340
437
|
- :question: Do I have to remove `KeyboardSlider` when using gradio or notebook backends?
|
|
341
438
|
> No, don't worry, these will be mapped back to regular sliders!
|
|
342
439
|
- :question: How do I play audio live?
|
|
343
|
-
> :sound: Inside a processing block, write the audio file to disk
|
|
344
|
-
>
|
|
440
|
+
> :sound: Inside a processing block, write the audio file to disk and use the audio helper:
|
|
441
|
+
> ```python
|
|
442
|
+
> from interactive_pipe import audio
|
|
443
|
+
> audio.set_audio(audio_file) # New clean API (v0.8.8)
|
|
444
|
+
> # or legacy: context["__set_audio"](audio_file)
|
|
445
|
+
> ```
|
|
345
446
|
- :question: Do I have to decorate my processing block using the `@interactive`
|
|
346
447
|
> 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)
|
|
347
448
|
```python
|
|
@@ -374,7 +475,14 @@ def add_interactivity():
|
|
|
374
475
|
# Don't do that!
|
|
375
476
|
def bad_processing_block(inp):
|
|
376
477
|
inp+=1
|
|
377
|
-
```
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
- :question: Is there a difference between `global_params` and `context` ?
|
|
481
|
+
> 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. However, we now recommend using the clean context API (see above).
|
|
482
|
+
> :warning: The old `global_params={}` / `context={}` keyword argument style still works for backwards compatibility but is deprecated.
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
378
486
|
# Roadmap and todos
|
|
379
487
|
:bug: Want to contribute or interested in adding new features? Enter a new [Github issue](https://github.com/balthazarneveu/interactive_pipe/issues)
|
|
380
488
|
|
|
@@ -402,12 +510,36 @@ def bad_processing_block(inp):
|
|
|
402
510
|
|
|
403
511
|

|
|
404
512
|
|
|
513
|
+
----------------------
|
|
514
|
+
|
|
515
|
+
### Development
|
|
516
|
+
|
|
517
|
+
#### Code quality checks
|
|
518
|
+
|
|
519
|
+
Before committing, ensure your code passes the linters and tests. The CI runs these checks automatically:
|
|
520
|
+
|
|
521
|
+
**What CI does:**
|
|
522
|
+
- **Black formatting check** (`.github/workflows/formatting.yaml`): Runs `black --check` to verify code formatting
|
|
523
|
+
- **Flake8 linting** (`.github/workflows/flake8.yaml`): Runs `flake8` to check code quality
|
|
524
|
+
- **Pytest tests** (`.github/workflows/pytest.yaml`): Runs `pytest` on Python 3.9, 3.10, and 3.11
|
|
405
525
|
|
|
526
|
+
**Local commands (match CI):**
|
|
406
527
|
|
|
528
|
+
```bash
|
|
529
|
+
# Install linting tools and test dependencies
|
|
530
|
+
pip install black flake8
|
|
531
|
+
pip install -e ".[pytest]"
|
|
407
532
|
|
|
533
|
+
# Check code formatting (Black) - matches CI
|
|
534
|
+
black --check .
|
|
535
|
+
|
|
536
|
+
# Auto-format code (Black) - run this if check fails
|
|
537
|
+
black .
|
|
538
|
+
|
|
539
|
+
# Run linting checks (flake8) - matches CI
|
|
540
|
+
flake8 .
|
|
541
|
+
|
|
542
|
+
# Run tests (pytest) - matches CI
|
|
543
|
+
pytest
|
|
544
|
+
```
|
|
408
545
|
|
|
409
|
-
#### :test_tube: Experimental features
|
|
410
|
-
- Custom events on specific key press
|
|
411
|
-
- Display the execution graph of the pipeline `G` key
|
|
412
|
-
- [thirdparty/music](/src/interactive_pipe/thirdparty/music.py) Play audio (Qt backend only). Play songs on spotify (linux only) when the spotify app is running.
|
|
413
|
-
- [thirdparty/images_openai_api](/src/interactive_pipe/thirdparty/images_openai_api.py) Generate images from prompt using OpenAI API image generation DALL-E Model (:dollar: paid service ~ 2cents/image)
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "interactive_pipe"
|
|
8
|
-
version = "0.8.
|
|
8
|
+
version = "0.8.8"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Balthazar Neveu", email="balthazarneveu@gmail.com" },
|
|
11
11
|
]
|
|
@@ -28,7 +28,7 @@ readme = "readme.md"
|
|
|
28
28
|
requires-python = ">=3.7"
|
|
29
29
|
classifiers = [
|
|
30
30
|
"Programming Language :: Python :: 3",
|
|
31
|
-
"License :: OSI Approved ::
|
|
31
|
+
"License :: OSI Approved :: MIT License",
|
|
32
32
|
"Operating System :: OS Independent",
|
|
33
33
|
]
|
|
34
34
|
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
| Science notebook | Toddler DIY Jukebox on a raspberry Pi |
|
|
26
26
|
|:-----: |:-----:|
|
|
27
27
|
|  | 
|
|
28
|
-
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon.
|
|
29
|
-
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [
|
|
28
|
+
| Sliders are added automatically in your jupyter notebook. This works on Google Collab and the code takes about 40 lines of code. No Widgets, event handlers or matplotlib knowledge required.| Plays some music when you touch the icon. Caption added through the title mechanism. Music samples generated by prompting [MusicGen](https://huggingface.co/spaces/facebook/MusicGen) |
|
|
29
|
+
| [Demo notebook on collab](https://colab.research.google.com/drive/1AwHyjZH8MnzZqwsvbmxBoB15btuMIwtk?usp=sharing) | [jukebox_demo.py demo code](/demo/jukebox_demo.py)|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
----------------------
|
|
@@ -63,19 +63,21 @@ pip install -e ".[full]"
|
|
|
63
63
|
|
|
64
64
|
## :scroll: Features
|
|
65
65
|
|
|
66
|
-
**Version 0.8.
|
|
66
|
+
**Version 0.8.8**
|
|
67
67
|
- Modular multi-image processing filters
|
|
68
68
|
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
|
|
69
69
|
- Support in jupyter notebooks
|
|
70
70
|
- Tuning sliders & check buttons with a GUI
|
|
71
71
|
- Cache intermediate results in RAM for much faster processing
|
|
72
72
|
- `KeyboardControl` : no slider on UI but exactly the same internal mechanism, update on key press.
|
|
73
|
-
- Support Curve plots (2D
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
73
|
+
- Support Curve plots (2D signals)
|
|
74
|
+
- Gradio backend (+allows sharing with others).
|
|
75
|
+
- Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
|
|
76
|
+
- Circular sliders for Qt Backend
|
|
77
|
+
- Text prompt (`free_text=("Hello world!", None),`)
|
|
78
|
+
- TimeControl (possibility to play/pause time using an incrementing timer)
|
|
79
|
+
- :new: **Context API**: Direct access to shared context across filters via `get_context()`, `context`, `layout`, `audio`
|
|
80
|
+
- :new: MIT License
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
|
|
@@ -283,21 +285,119 @@ if __name__ == '__main__':
|
|
|
283
285
|
|
|
284
286
|
----------
|
|
285
287
|
|
|
288
|
+
### Release Notes
|
|
289
|
+
|
|
290
|
+
#### Version 0.8.8 (January 2026)
|
|
291
|
+
|
|
292
|
+
**New Features:**
|
|
293
|
+
- **Clean Context API**: Access shared context directly without `global_params` pollution
|
|
294
|
+
- `get_context()` - Get the shared context dictionary
|
|
295
|
+
- `context` - Direct dict-like access to context
|
|
296
|
+
- `layout` - Access layout configuration directly
|
|
297
|
+
- `audio` - Access audio functionality directly
|
|
298
|
+
|
|
299
|
+
**Code Quality Improvements:**
|
|
300
|
+
- Replaced all assertions with proper exceptions (`ValueError`, `TypeError`, `RuntimeError`)
|
|
301
|
+
- Fixed all mutable default arguments across the codebase (prevents shared state bugs)
|
|
302
|
+
- Improved type hints with proper `Optional` and `Any` types
|
|
303
|
+
- Better error messages for debugging
|
|
304
|
+
|
|
305
|
+
**UX Improvements:**
|
|
306
|
+
- Dropdown menus are now hidden when only a single choice is available
|
|
307
|
+
- Helpful message displayed when Graphviz is not available (when pressing `G`)
|
|
308
|
+
- Fixed warning in linestyle for curves
|
|
309
|
+
|
|
310
|
+
**Bug Fixes:**
|
|
311
|
+
- Fixed audio initialization order in Qt backend
|
|
312
|
+
- Fixed pytest failures for optional dependencies in CI
|
|
313
|
+
- Fixed various edge cases in error handling
|
|
314
|
+
|
|
315
|
+
**Migration of old context or global_params:**
|
|
316
|
+
- Use `global_params=SharedContext.injected()` to let code interpreters know that there's no need to pass this parameter.
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
# Before
|
|
320
|
+
def apply_brightness(img:np.ndarray, brightness: float = 0.5, global_params:dict =None):
|
|
321
|
+
global_params["brightness"] = brightness
|
|
322
|
+
return img * brightness
|
|
323
|
+
|
|
324
|
+
# Progressive migration
|
|
325
|
+
def apply_brightness(img:np.ndarray, brightness: float = 0.5, global_params:dict ==SharedContext.injected()):
|
|
326
|
+
global_params["brightness"] = brightness
|
|
327
|
+
return img * brightness
|
|
328
|
+
|
|
329
|
+
# Recommended version
|
|
330
|
+
from interactive_pipe import context
|
|
331
|
+
def apply_brightness(img:np.ndarray, brightness: float = 0.5):
|
|
332
|
+
context.shared_brightness = brightness # shared with all filters
|
|
333
|
+
return img * brightness
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
**License:**
|
|
340
|
+
- Updated to MIT License
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
286
344
|
### History
|
|
287
|
-
- Interactive pipe was initially
|
|
345
|
+
- Interactive pipe was initially developed by [Balthazar Neveu](https://github.com/balthazarneveu) as part of the [irdrone project](https://github.com/wisescootering/infrareddrone/tree/master/interactive) based on matplotlib.
|
|
288
346
|
- Later, more contributions were also made by [Giuseppe Moschetti](https://github.com/g-moschetti) and Sylvain Leroy.
|
|
289
347
|
- August 2023: rewriting the whole core and supporting several graphical backends!
|
|
290
348
|
- September 2024: Gradio backend
|
|
349
|
+
- January 2026: Clean Context API and code quality improvements (v0.8.8)
|
|
291
350
|
|
|
292
351
|
|
|
293
352
|
### FAQ
|
|
294
|
-
- :question:
|
|
295
|
-
>
|
|
353
|
+
- :question: What is the recommended way to access shared context?
|
|
354
|
+
> **New in v0.8.8**: Use the clean context API for direct access:
|
|
355
|
+
> ```python
|
|
356
|
+
> from interactive_pipe import context, layout, audio, get_context
|
|
357
|
+
>
|
|
358
|
+
> @interactive()
|
|
359
|
+
> def my_filter(img):
|
|
360
|
+
> context["shared_key"] = "shared_value" # Direct dict-like access
|
|
361
|
+
> context.brightness = 0.5
|
|
362
|
+
> layout.set_title("output_image", "My Image") # Layout helpers
|
|
363
|
+
>
|
|
364
|
+
> return img
|
|
365
|
+
> ```
|
|
366
|
+
|
|
367
|
+
- :question: How do I change the layout? *Can I change the grid layout of images live? (like you compare 2 images side by side and you want to start comparing 4 images in a 2x2 fashion for debugging purpose)*. It is possible with Qt backend.
|
|
368
|
+
|
|
369
|
+
> Use the `layout` helper to control image arrangement and styling:
|
|
370
|
+
> ```python
|
|
371
|
+
> from interactive_pipe import layout
|
|
372
|
+
>
|
|
373
|
+
> def change_layout(layout: str="side_by_side"):
|
|
374
|
+
> # Arrange outputs in a 2x2 grid
|
|
375
|
+
> if layout == "side_by_side":
|
|
376
|
+
> layout.grid([["input", "result"]])
|
|
377
|
+
> if layout == "grid2x2":
|
|
378
|
+
> layout.grid([["input", "processed"], ["histogram_graph", "result"]])
|
|
379
|
+
> # Style individual outputs
|
|
380
|
+
> layout.style("result", title="Final Result")
|
|
381
|
+
> # Note that the string "input", "processed", "histogram_graph", "result"
|
|
382
|
+
> # are the variables used in the pipeline (see below)!
|
|
383
|
+
>
|
|
384
|
+
> def pipeline(input):
|
|
385
|
+
> processed = denoise(input)
|
|
386
|
+
> result = change_brightness(processed)
|
|
387
|
+
> histogram_graph = compute_histo(processed)
|
|
388
|
+
> change_layout()
|
|
389
|
+
> return result
|
|
390
|
+
> ```
|
|
391
|
+
|
|
296
392
|
- :question: Do I have to remove `KeyboardSlider` when using gradio or notebook backends?
|
|
297
393
|
> No, don't worry, these will be mapped back to regular sliders!
|
|
298
394
|
- :question: How do I play audio live?
|
|
299
|
-
> :sound: Inside a processing block, write the audio file to disk
|
|
300
|
-
>
|
|
395
|
+
> :sound: Inside a processing block, write the audio file to disk and use the audio helper:
|
|
396
|
+
> ```python
|
|
397
|
+
> from interactive_pipe import audio
|
|
398
|
+
> audio.set_audio(audio_file) # New clean API (v0.8.8)
|
|
399
|
+
> # or legacy: context["__set_audio"](audio_file)
|
|
400
|
+
> ```
|
|
301
401
|
- :question: Do I have to decorate my processing block using the `@interactive`
|
|
302
402
|
> 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)
|
|
303
403
|
```python
|
|
@@ -330,7 +430,14 @@ def add_interactivity():
|
|
|
330
430
|
# Don't do that!
|
|
331
431
|
def bad_processing_block(inp):
|
|
332
432
|
inp+=1
|
|
333
|
-
```
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- :question: Is there a difference between `global_params` and `context` ?
|
|
436
|
+
> 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. However, we now recommend using the clean context API (see above).
|
|
437
|
+
> :warning: The old `global_params={}` / `context={}` keyword argument style still works for backwards compatibility but is deprecated.
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
|
|
334
441
|
# Roadmap and todos
|
|
335
442
|
:bug: Want to contribute or interested in adding new features? Enter a new [Github issue](https://github.com/balthazarneveu/interactive_pipe/issues)
|
|
336
443
|
|
|
@@ -358,12 +465,36 @@ def bad_processing_block(inp):
|
|
|
358
465
|
|
|
359
466
|

|
|
360
467
|
|
|
468
|
+
----------------------
|
|
469
|
+
|
|
470
|
+
### Development
|
|
471
|
+
|
|
472
|
+
#### Code quality checks
|
|
473
|
+
|
|
474
|
+
Before committing, ensure your code passes the linters and tests. The CI runs these checks automatically:
|
|
475
|
+
|
|
476
|
+
**What CI does:**
|
|
477
|
+
- **Black formatting check** (`.github/workflows/formatting.yaml`): Runs `black --check` to verify code formatting
|
|
478
|
+
- **Flake8 linting** (`.github/workflows/flake8.yaml`): Runs `flake8` to check code quality
|
|
479
|
+
- **Pytest tests** (`.github/workflows/pytest.yaml`): Runs `pytest` on Python 3.9, 3.10, and 3.11
|
|
361
480
|
|
|
481
|
+
**Local commands (match CI):**
|
|
362
482
|
|
|
483
|
+
```bash
|
|
484
|
+
# Install linting tools and test dependencies
|
|
485
|
+
pip install black flake8
|
|
486
|
+
pip install -e ".[pytest]"
|
|
363
487
|
|
|
488
|
+
# Check code formatting (Black) - matches CI
|
|
489
|
+
black --check .
|
|
490
|
+
|
|
491
|
+
# Auto-format code (Black) - run this if check fails
|
|
492
|
+
black .
|
|
493
|
+
|
|
494
|
+
# Run linting checks (flake8) - matches CI
|
|
495
|
+
flake8 .
|
|
496
|
+
|
|
497
|
+
# Run tests (pytest) - matches CI
|
|
498
|
+
pytest
|
|
499
|
+
```
|
|
364
500
|
|
|
365
|
-
#### :test_tube: Experimental features
|
|
366
|
-
- Custom events on specific key press
|
|
367
|
-
- Display the execution graph of the pipeline `G` key
|
|
368
|
-
- [thirdparty/music](/src/interactive_pipe/thirdparty/music.py) Play audio (Qt backend only). Play songs on spotify (linux only) when the spotify app is running.
|
|
369
|
-
- [thirdparty/images_openai_api](/src/interactive_pipe/thirdparty/images_openai_api.py) Generate images from prompt using OpenAI API image generation DALL-E Model (:dollar: paid service ~ 2cents/image)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.8.
|
|
1
|
+
__version__ = "0.8.8"
|
|
2
2
|
from interactive_pipe.helper.pipeline_decorator import pipeline, interactive_pipeline
|
|
3
3
|
from interactive_pipe.headless.control import (
|
|
4
4
|
Control,
|
|
@@ -11,6 +11,18 @@ from interactive_pipe.data_objects.image import Image
|
|
|
11
11
|
from interactive_pipe.headless.keyboard import KeyboardControl
|
|
12
12
|
from interactive_pipe.helper.filter_decorator import interactive, interact
|
|
13
13
|
|
|
14
|
+
# Clean context API - no more global_params pollution!
|
|
15
|
+
from interactive_pipe.core.context import (
|
|
16
|
+
get_context,
|
|
17
|
+
context, # Direct dict-like access
|
|
18
|
+
layout,
|
|
19
|
+
audio,
|
|
20
|
+
SharedContext, # For legacy code migration with explicit injection
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Clean exception handling
|
|
24
|
+
from interactive_pipe.core.engine import FilterError
|
|
25
|
+
|
|
14
26
|
# Allowing more straightforward naming convention
|
|
15
27
|
block = interactive
|
|
16
28
|
pipeline = interactive_pipeline
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from copy import deepcopy
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class CachedResults:
|
|
@@ -26,7 +26,7 @@ class CachedResults:
|
|
|
26
26
|
Underlying class used in the interactive pipe cache mechanism.
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
|
-
def __init__(self, name: str = None, safe_buffer_deepcopy: bool = True):
|
|
29
|
+
def __init__(self, name: Optional[str] = None, safe_buffer_deepcopy: bool = True):
|
|
30
30
|
self.name = name
|
|
31
31
|
self.result = None
|
|
32
32
|
self.state_change = StateChange(name=name)
|
|
@@ -68,7 +68,7 @@ class CachedResults:
|
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
def __repr__(self) -> str:
|
|
71
|
-
return self.name
|
|
71
|
+
return self.name if self.name is not None else "CachedResults"
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
class StateChange:
|
|
@@ -78,7 +78,7 @@ class StateChange:
|
|
|
78
78
|
Underlying class used in the interactive pipe cache mechanism.
|
|
79
79
|
"""
|
|
80
80
|
|
|
81
|
-
def __init__(self, name: str = None):
|
|
81
|
+
def __init__(self, name: Optional[str] = None):
|
|
82
82
|
self.name = name
|
|
83
83
|
self._stored_params = None
|
|
84
84
|
self._update_needed = False
|
|
@@ -106,6 +106,7 @@ class StateChange:
|
|
|
106
106
|
self._update_needed = value
|
|
107
107
|
|
|
108
108
|
def __repr__(self) -> str:
|
|
109
|
-
|
|
109
|
+
name_str = self.name if self.name is not None else "StateChange"
|
|
110
|
+
return f"{name_str}: " + (
|
|
110
111
|
"needs update" if self.update_needed else "no update needed"
|
|
111
112
|
)
|