Library to create flexible interactive image processing pipelines and automatically add a graphical user interface without knowing anything about GUI coding!
Project description
Quick setup pip install interactive-pipe |
| Project website |
Interactive-pipe code
Concept
- Develop an algorithm while debugging visually with plots, while checking robustness & continuity to parameters change.
- Magically create a graphical interface to easily demonstrate a concept or simply tune your algorithm.
❤️ You do not need to learn anything about making a graphical user interface (GUI) ❤️
Examples
| Science notebook | Toddler DIY Jukebox on a raspberry Pi |
|---|---|
| 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 |
| Demo notebook on collab | jukebox_demo.py demo code |
Local setup
git clone git@github.com:balthazarneveu/interactive_pipe.git
cd interactive-pipe
pip install -e ".[full]"
Who is this for?
🎓 Scientific education
- Demonstrate concepts by interacting with curves / images.
- Easy integration in Jupyter Notebooks (popular with Google Collab)
🎁 DIY hobbyist
- You can also use the declarative nature of interactive pipe to make a graphical interface in a few lines of codes.
- For instance, it is possible to code a jukebox for a toddler on a RaspberryPi.
📷 Engineering (computer vision, image/signal processing)
- While prototyping an algorithm or testing a neural network, you may be interested in making small experiments with visual checks. Instead of making a draft quick & dirty code that you'll never keep, you can use interactive pipe to show your team how your library works. A visual demo is always good, it shows that the algorithm is not buggy if anyone can play with it.
- Tune your algorithms with a graphical interface and save your parameters for later batch processing.
- Ready to batch under the hood, the processing engine can be ran without GUI (therefore allowing to use the same code for tuning & batch processing if needed).
- Do not spoil your production code with a huge amount of graphical interface code, keep your algorithms library untouched and simply decorate it.
📜 Features
Version 0.8.9
- Modular multi-image processing filters
- Declarative: Easily make graphical user interface without having to learn anything about pyQt or matplotlib
- Support in jupyter notebooks
- Tuning sliders & check buttons with a GUI
- Cache intermediate results in RAM for much faster processing
KeyboardControl: no slider on UI but exactly the same internal mechanism, update on key press.- Support Curve plots (2D signals).
- Gradio backend (+allows sharing with others).
- Audio support in Gradio (live audio or display several players by returning 1D numpy arrays)
- Circular sliders for Qt Backend
- Text prompt (
free_text=("Hello world!", None),) - TimeControl (possibility to play/pause time using an incrementing timer)
- 🆕 Context API: Direct access to shared context across filters via
get_context(),context,layout,audio - 🆕 MIT License
- 🆕 Panel Panel System: group the sliders in defined panels. allows fine control on GUI layout.
- 🆕 Support Table outputs
⌨️ Keyboard shortcuts
Shortcuts while using the GUI (QT & matplotlib backends)
F1to show the help shortcuts in the terminalF11toggle fullscreen modeWto write full resolution image to diskRto reset parametersIto print parameters dictionary in the command lineEto export parameters dictionary to a yaml fileOto import parameters dictionary from a yaml file (sliders will update)Gto export a pipeline diagram for your interactive pipe (requires graphviz)
Status
- supported backends
- ✅
gui='qt'pyQt/pySide - ✅
gui='mpl'matplotlib - ✅
gui='nb'ipywidget for jupyter notebooks - 🧪
gui='gradio'gradio wrapping (+useshare_gradio_app=Trueto share your app with others)
- ✅
- tested platforms
- ✅ Linux (Ubuntu / KDE Neon)
- ✅ RapsberryPi
- ✅ On google collab (use
gui='nb')
| ⭐ | PyQt / PySide | Matplotlib | Jupyter notebooks including Google collab | Gradio |
|---|---|---|---|---|
| Backend name | qt |
mpl |
nb |
gradio |
| Preview | ||||
| Plot curves | ✅ | ✅ | ✅ | ✅ |
| Change layout | ✅ | ✅ | ✅ | ➖ |
| Keyboard shortcuts / fullscreen | ✅ | ✅ | ➖ | ➖ |
| Audio support | ✅ | ➖ | ➖ | ✅ |
| Image buttons | ✅ | ➖ | ➖ | ➖ |
| Circular slider | ✅ | ➖ | ➖ | ➖ |
| Collapsible Panels | ✅ | ➖ | ➖ | ✅ |
Tutorials
Main tutorial
Tutorial on Hugging Face space
Learn by examples
Basic image processing (python code sample for PyQT GUI)
| GUI | Pipeline |
|---|---|
Speech exploration notebook (colab, signal processing)
🚀 Ultra short code
Since ipywidgets in notebooks are supported, the tutorial is also available in a google collab notebook
Let's define 3 image processing very basic filters exposure, black_and_white & blend.
By design:
- image buffers inputs are arguments
- keyword arguments are the parameters which can be later turned into interactive widgets.
- output buffers are simply returned like you'd do in a regular function.
We use the @interactive() decorator to specify which parameters become interactive widgets. Parameters defined in the decorator as tuple/list will become graphical interactive widgets (slider, tick box, dropdown menu).
The syntax to turn keyword arguments into sliders is pretty simple: @interactive(param=(default, [min, max], name)) will create a float slider for instance.
Finally, we need to the glue to combo these filters. This is where the sample_pipeline function comes in.
By decorating it with @interactive_pipeline(gui="qt"), calling this function will magically turn into a GUI powered image processing pipeline.
from interactive_pipe import interactive, interactive_pipeline
import numpy as np
@interactive(
coeff=(1., [0.5, 2.], "exposure"),
bias=(0., [-0.2, 0.2])
)
def exposure(img, coeff=1., bias=0.):
'''Applies a multiplication by coeff & adds a constant bias to the image'''
# In the GUI, the coeff will be labelled as "exposure".
# As the default tuple provided to bias does not end up with a string,
# the widget label will be "bias", simply named after the keyword arg.
return img*coeff + bias
@interactive(bnw=(True, "black and white"))
def black_and_white(img, bnw=True):
'''Averages the 3 color channels (Black & White) if bnw=True
'''
# Special mention for booleans: using a tuple like (True,) allows creating the tick box.
return np.repeat(np.expand_dims(np.average(img, axis=-1), -1), img.shape[-1], axis=-1) if bnw else img
@interactive(blend_coeff=(0.5, [0., 1.]))
def blend(img0, img1, blend_coeff=0.5):
'''Blends between two image.
- when blend_coeff=0 -> image 0 [slider to the left ]
- when blend_coeff=1 -> image 1 [slider to the right]
'''
return (1-blend_coeff)*img0+ blend_coeff*img1
# you can change the backend to mpl instead of Qt here.
@interactive_pipeline(gui="qt", size="fullscreen")
def sample_pipeline(input_image):
exposed = exposure(input_image)
bnw_image = black_and_white(input_image)
blended = blend(exposed, bnw_image)
return exposed, blended, bnw_image
if __name__ == '__main__':
input_image = np.array([0., 0.5, 0.8])*np.ones((256, 512, 3))
sample_pipeline(input_image)
❤️ This code shall display you a GUI with three images. The middle one is the result of the blend
Notes:
- If you write
@interactive()withdef blend(img0, img1, blend_coeff=0.5):, blend_coeff will simply not be a slider on the GUI. - If you write
@interactive(blend_coeff=[0., 1.])in the decorator, blend_coeff will be a slider initialized to 0.5 - If you write
@interactive(bnw=(True, "black and white", "k")), the checkbox will disappear and be replaced by a keypress event (presskto enable/disable black & white)
💡 Some more tips
from interactive_pipe import interactive, interactive_pipeline, context
import numpy as np
COLOR_DICT = {"red": [1., 0., 0.], "green": [0., 1.,0.], "blue": [0., 0., 1.], "gray": [0.5, 0.5, 0.5]}
@interactive(color_choice=["red", "green", "blue", "gray"])
def generate_flat_colored_image(color_choice="red"):
'''Generate a constant colorful image
'''
flat_array = np.array(COLOR_DICT.get(color_choice)) * np.ones((64, 64, 3))
context["avg"] = np.average(flat_array)
return flat_array
- Note that you can also create filters which take no inputs and simply "generate" images.
- The
color_choicelist will be turned into a nice dropdown menu. Default value here will be red as this is the first element of the list!
💡 Can filters communicate together?
Yes, using the context proxy from interactive_pipe.
- Check carefully how we stored the image average of the flat image in context.
- This value will be available to other filters.
special_image_sliceis going to use that value to set the half bottom image to dark in case the average is high.
def special_image_slice(img):
out_img = img.copy()
if context["avg"] > 0.4:
out_img[out_img.shape[0]//2:, ...] = 0.
return out_img
@interactive(image_index=(0, [0, 2], None, ["pagedown", "pageup", True]))
def switch_image(img1, img2, img3, image_index=0):
'''Switch between 3 images
'''
return [img1, img2, img3][image_index]
Note that you can create a filter to switch between several images. In ["pagedown", "pageup", True], True means that the image_index will wrap around. (it will return to 0 as soon as it goes above the maximum value of 2).
@interactive(top_slice_black=(True, "special", "k"))
def black_top_image_slice(img, top_slice_black=True):
out_img = img.copy()
if top_slice_black:
out_img[:out_img.shape[0]//2, ...] = 0.
return out_img
@interactive_pipeline(gui="qt", size="fullscreen")
def sample_pipeline_generated_image():
flat_img = generate_flat_colored_image()
top_slice_modified = black_top_image_slice(flat_img)
bottom_slice_modified_image = special_image_slice(flat_img)
chosen = switch_image(flat_img, top_slice_modified, bottom_slice_modified_image)
return chosen
if __name__ == '__main__':
sample_pipeline_generated_image()
Release Notes
Version 0.8.9 (February 2026)
New Features:
-
Panel System: Control panel layout and organization
- Flexible panel positioning (left, right, top, bottom)
- Detached control panels for separate windows
- Nested panels and subpanels support
- Grouped controls within panels
- Improved spacing and borders for better visual organization
- Full backend support (Qt, Gradio, matplotlib, notebook)
-
Table Data Type: Display tabular data natively
- Core Table functionality without external dependencies
- Optional pandas DataFrame support for advanced use cases
- Rendering support across all backends (Qt, Gradio, matplotlib)
- Headerless tables option
-
TimeControl Enhancements: Better time-based parameter control
- Improved slider help display
- Additional demos showcasing time-based animations
API Improvements:
- Context support at pipeline initialization
- Backend selection via enum (string format still supported)
- Graph visualization for GUI pipelines (press
G)
Deprecations:
- Inline syntax deprecated (use decorator syntax instead)
output_canvasargument removed- Context aliases (
global_params,statesetc...) deprecated at initialization
Version 0.8.8 (January 2026)
New Features:
- Clean Context API: Access shared context directly without
global_paramspollutionget_context()- Get the shared context dictionarycontext- Direct dict-like access to contextlayout- Access layout configuration directlyaudio- Access audio functionality directly
Code Quality Improvements:
- Replaced all assertions with proper exceptions (
ValueError,TypeError,RuntimeError) - Fixed all mutable default arguments across the codebase (prevents shared state bugs)
- Improved type hints with proper
OptionalandAnytypes - Better error messages for debugging
UX Improvements:
- Dropdown menus are now hidden when only a single choice is available
- Helpful message displayed when Graphviz is not available (when pressing
G) - Fixed warning in linestyle for curves
Bug Fixes:
- Fixed audio initialization order in Qt backend
- Fixed pytest failures for optional dependencies in CI
- Fixed various edge cases in error handling
Migration from old context={} or global_params={} patterns:
# OLD (deprecated) - using context={} or global_params={}
from interactive_pipe import interactive
@interactive(brightness=(0.5, [0., 1.]))
def apply_brightness(img:np.ndarray, brightness: float = 0.5, global_params={}):
global_params["brightness"] = brightness # Storing shared data
global_params["__output_styles"]["output"] = {"title": "Brightened"} # Setting layout
return img * brightness
# NEW (recommended) - using context and layout proxies
from interactive_pipe import interactive, context, layout
@interactive(brightness=(0.5, [0., 1.]))
def apply_brightness(img:np.ndarray, brightness: float = 0.5):
context["brightness"] = brightness # or: context.brightness = brightness
layout.style("output", title="Brightened")
return img * brightness
The new API provides:
context- For sharing data between filters (replacesglobal_params["key"])layout- For controlling output display (replacesglobal_params["__output_styles"])audio- For audio playback controlget_context()- Get the shared context dictionary directly
License:
- Updated to MIT License
History
- Interactive pipe was initially developed by Balthazar Neveu as part of the irdrone project based on matplotlib.
- Later, more contributions were also made by Giuseppe Moschetti and Sylvain Leroy.
- August 2023: rewriting the whole core and supporting several graphical backends!
- September 2024: Gradio backend
- January 2026: Clean Context API and code quality improvements (v0.8.8)
FAQ
- ❓ What is the recommended way to access shared context?
New in v0.8.8: Use the clean context API for direct access:
from interactive_pipe import context, layout, audio, get_context @interactive() def my_filter(img): context["shared_key"] = "shared_value" # Direct dict-like access context.brightness = 0.5 layout.set_title("output_image", "My Image") # Layout helpers return img
- ❓ 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.
Use the
layouthelper to control image arrangement and styling:from interactive_pipe import layout def change_layout(layout: str="side_by_side"): # Arrange outputs in a 2x2 grid if layout == "side_by_side": layout.grid([["input", "result"]]) if layout == "grid2x2": layout.grid([["input", "processed"], ["histogram_graph", "result"]]) # Style individual outputs layout.style("result", title="Final Result") # Note that the string "input", "processed", "histogram_graph", "result" # are the variables used in the pipeline (see below)! def pipeline(input): processed = denoise(input) result = change_brightness(processed) histogram_graph = compute_histo(processed) change_layout() return result
- ❓ Do I have to remove
KeyboardSliderwhen using gradio or notebook backends?
No, don't worry, these will be mapped back to regular sliders!
- ❓ How do I play audio live?
🔊 Inside a processing block, write the audio file to disk and use the audio helper:
from interactive_pipe import audio audio.set_audio(audio_file) # New clean API (v0.8.8) # or legacy: context["__set_audio"](audio_file)
- ❓ Do I have to decorate my processing block using the
@interactive
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)
@interactive(angle=(0., [-360., 360.]))
def processing_block(angle=0.):
...
An alternative is to decorate the processing block outside... in a file dedicated to interactivity for instance
# core_filter.py
def processing_block(angle=0.):
...
# graphical.py
from core_filter import processing_block
def add_interactivity():
interactive(angle=(0., [-360., 360.]))(processing_block)
- ❓ Can I call the pipeline in a command line/batch fashion?
Yes, headless mode is supported. 🔜 documentation needed.
- ❓ Can I use inplace operations?
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.
# Don't do that!
def bad_processing_block(inp):
inp+=1
- ❓ Is there a difference between
global_paramsandcontext?
No,
global_params,global_parameters,global_state,global_context,context,stateall mean the same thing and are all supported for legacy reasons.contextis the preferred wording. However, we now recommend using the clean context API (see above). ⚠️ The oldglobal_params={}/context={}keyword argument style still works for backwards compatibility but is deprecated.
Roadmap and todos
🐛 Want to contribute or interested in adding new features? Enter a new Github issue
🎁 Want to dig into the code? Take a look at code_architecture.md
Development
Code quality checks
Before committing, ensure your code passes the linters, type checker, and tests. The CI runs these checks automatically:
What CI does:
- Ruff formatting check (
.github/workflows/ruff-format.yaml): Runsruff format --checkto verify code formatting - Ruff linting (
.github/workflows/ruff-lint.yaml): Runsruff checkto check code quality (replaces flake8) - Pyright type checking (
.github/workflows/pyright.yaml): Runspyrightfor static type checking (informational, non-blocking) - Pytest tests (
.github/workflows/pytest.yaml): Runspyteston Python 3.9, 3.10, and 3.11
Local commands (match CI):
# Install development tools and test dependencies
pip install -e ".[dev,pytest]"
# Format code (Ruff) - matches CI
ruff format .
# Check formatting (Ruff) - matches CI
ruff format --check .
# Lint code (Ruff) - matches CI (auto-fixes when possible)
ruff check .
# Auto-fix linting issues (Ruff)
ruff check --fix .
# Type check (Pyright) - matches CI (informational)
pyright src/
# Run tests (pytest) - matches CI
pytest
# Pre-commit checklist (run all before committing)
ruff format .
ruff check --fix .
pyright src/ # Optional, won't block commit
pytest
Note: Ruff replaces both Black (formatting) and Flake8 (linting) in a single, faster tool. Pyright provides static type checking to catch type errors early.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file interactive_pipe-0.8.9.tar.gz.
File metadata
- Download URL: interactive_pipe-0.8.9.tar.gz
- Upload date:
- Size: 111.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06e57783d43bb15a7ea1213f77200480b42be7385ff41924f1606ce8ff483897
|
|
| MD5 |
a66e98f7e0459c12f5928b0bb8d94dde
|
|
| BLAKE2b-256 |
a51072041932502c6bedc4228faae335799721d8a70ea600367ecae824baa2dd
|
File details
Details for the file interactive_pipe-0.8.9-py3-none-any.whl.
File metadata
- Download URL: interactive_pipe-0.8.9-py3-none-any.whl
- Upload date:
- Size: 95.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dff4c204a2f2b9e44776b1d64932e7077b717659f140f9f833915efe0a7e9eff
|
|
| MD5 |
e565289fb383fe7b598820b9a5b7ebcd
|
|
| BLAKE2b-256 |
0e2626929e846b89a82c0709adcfc844f5b787ece78d53912bd3ae20601d1257
|