Skip to content

Tips & tricks

Generator filters (no inputs)

Filters don't have to take image inputs — they can simply generate data:

from interactive_pipe import interactive, 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"):
    flat_array = np.array(COLOR_DICT.get(color_choice)) * np.ones((64, 64, 3))
    context["avg"] = np.average(flat_array)
    return flat_array

The color_choice list becomes a dropdown menu; the default is the first element.

Switching between images

from interactive_pipe import KeyboardControl

@interactive(image_index=KeyboardControl(0, [0, 2], keydown="pagedown", keyup="pageup", modulo=True))
def switch_image(img1, img2, img3, image_index=0):
    return [img1, img2, img3][image_index]

modulo=True makes the index wrap around (back to 0 past the maximum) — flip through images with Page Up / Page Down.

Avoid in-place operations

Filters route their buffers by reference, so mutating an input in place silently corrupts sibling filters and cached buffers. Since 0.9.1, inputs are handed to filters as read-only views by default (readonly_inputs=True), so the mistake raises immediately instead of producing wrong results:

# Raises since 0.9.1 — img is a read-only view
def bad_processing_block(img):
    img += 1
    return img

If a filter intentionally mutates its inputs, declare it with @interactive(inplace=True) — it then receives private writable copies:

@interactive(inplace=True)
def add_one(img):
    img += 1
    return img

Pass readonly_inputs=False to @interactive_pipeline(...) to restore the old permissive behavior. In-place mutation is also detected for torch tensors (which cannot be marked read-only).

Cache intermediate results

@interactive_pipeline(gui="qt", cache=True) keeps intermediate filter outputs in RAM, so moving a slider only re-runs the filters downstream of the change (in source order) — a big win for heavy pipelines.

For pipelines with independent branches, use cache="graph" instead: this dependency-aware mode recomputes a filter only when it is actually affected — its own sliders moved, one of its producers was recomputed, or a context key it reads changed. Filters on branches unrelated to the change are left untouched.

@interactive_pipeline(gui="qt", cache="graph")
def my_pipeline(img):
    exposed = exposure(img)   # moving the exposure slider...
    smooth = denoise(img)     # ...leaves denoise and annotate cached
    tagged = annotate(smooth)
    return [exposed, tagged]

Use cache="graph-strict" to additionally receive context numpy arrays as read-only views, catching accidental in-place mutation of shared context data.

Export / import tuning

Press E in the GUI to export the current parameters to YAML and O to load them back; headless pipelines expose the same via export_tuning() / load_tuning(). Press G to export a graphviz diagram of the pipeline.

One-shot GUI with @interact

For a quick experiment on a single function — no pipeline needed — @interact opens the GUI immediately at decoration time:

from interactive_pipe import interact
import numpy as np

image = np.random.rand(256, 512, 3)

@interact(image, gain=(1.0, [0.0, 3.0]))
def show(img, gain=1.0):
    return img * gain

Handy in notebooks and throwaway scripts; for anything reusable, prefer @interactive + @interactive_pipeline (decorators guide).

interact

interact(*decorator_args: Any, gui: str = 'auto', disable: bool = False, output_routing: Optional[List[str]] = None, size: Union[str, Tuple[int, int], None] = None, inplace: bool = False, **decorator_controls: Any)

Launch a GUI from a single decorated function (one-shot decorator).

Unlike @interactive, decorating a function with @interact immediately runs it inside a GUI: widgets are created for the declared controls and the window opens right away. Inspired by the ipywidgets interact function, except that returning numpy arrays or Curve instances handles the display automatically — no matplotlib boilerplate.

Parameters:

Name Type Description Default
*decorator_args Any

Positional inputs passed to the function (e.g. the input image).

()
gui str

Backend, same values as interactive_pipeline (default "auto").

'auto'
disable bool

When True, return the function untouched (no GUI).

False
output_routing Optional[List[str]]

Names of the outputs, when they cannot be deduced from a dry run.

None
size Union[str, Tuple[int, int], None]

Window/figure size hint forwarded to the backend.

None
**decorator_controls Any

Controls bound to keyword arguments by name (Control instances or the (default, [min, max]) tuple shorthand).

{}
Example
@interact(image, gain=(1.0, [0.0, 3.0]))
def show(img, gain=1.0):
    return img * gain
Source code in src/interactive_pipe/helper/filter_decorator.py
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def interact(
    *decorator_args: Any,
    gui: str = "auto",
    disable: bool = False,
    output_routing: Optional[List[str]] = None,
    size: Union[str, Tuple[int, int], None] = None,
    inplace: bool = False,
    **decorator_controls: Any,
):
    """Launch a GUI from a single decorated function (one-shot decorator).

    Unlike ``@interactive``, decorating a function with ``@interact``
    immediately runs it inside a GUI: widgets are created for the declared
    controls and the window opens right away. Inspired by the ipywidgets
    ``interact`` function, except that returning numpy arrays or ``Curve``
    instances handles the display automatically — no matplotlib boilerplate.

    Args:
        *decorator_args: Positional inputs passed to the function
            (e.g. the input image).
        gui: Backend, same values as ``interactive_pipeline``
            (default ``"auto"``).
        disable: When True, return the function untouched (no GUI).
        output_routing: Names of the outputs, when they cannot be deduced
            from a dry run.
        size: Window/figure size hint forwarded to the backend.
        **decorator_controls: Controls bound to keyword arguments by name
            (``Control`` instances or the ``(default, [min, max])`` tuple
            shorthand).

    Example:
        ```python
        @interact(image, gain=(1.0, [0.0, 3.0]))
        def show(img, gain=1.0):
            return img * gain
        ```
    """
    omitted_parentheses_flag = False

    def wrapper(func):
        if disable:
            return func
        # this will run the GUI automatically
        filter_instance = filter_from_function(func, inplace=inplace, **decorator_controls)
        filter_instance.run_gui(
            *(decorator_args if not omitted_parentheses_flag else decorator_args[1:]),
            gui=gui,
            output_routing=output_routing,
            size=size,
        )
        # return the original function if you want to keep using it afterwards
        return func

    if len(decorator_args) == 1 and callable(decorator_args[0]):
        omitted_parentheses_flag = True
        return wrapper(decorator_args[0])  # no parenthesis
    return wrapper