Skip to content

Decorators

The two entry points of the library: @interactive declares the controls on a filter, @interactive_pipeline turns the function chaining the filters into a GUI application. block and pipeline are top-level aliases of interactive and interactive_pipeline respectively, so you can write @ip.block(...) / @ip.pipeline(...) after import interactive_pipe as ip.

There is also a one-shot @interact decorator for quickly opening a GUI on a single function — see Tips & tricks.

interactive

interactive(inplace: bool = False, **decorator_controls: Any)

Declare controls bound to a filter's keyword arguments.

The decorated function keeps working as a plain function, but when used inside an @interactive_pipeline the declared controls appear as GUI widgets. Unlike @interact, no GUI is launched at decoration time, so the filter can be reused across pipelines.

Parameters:

Name Type Description Default
inplace bool

Declare that this filter mutates its input buffers. The engine then hands it private writable copies instead of read-only views. Prefer copying explicitly (img = img.copy()) in new code.

False
**decorator_controls Any

Mapping of keyword-argument name to a control declaration — a Control instance (or subclass such as KeyboardControl) or the (default, [min, max]) tuple shorthand.

{}
Example
@interactive(gain=(1.0, [0.0, 3.0]))
def amplify(img, gain=1.0):
    return gain * img
Source code in src/interactive_pipe/helper/filter_decorator.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def interactive(inplace: bool = False, **decorator_controls: Any):
    """Declare controls bound to a filter's keyword arguments.

    The decorated function keeps working as a plain function, but when used
    inside an ``@interactive_pipeline`` the declared controls appear as GUI
    widgets. Unlike ``@interact``, no GUI is launched at decoration time,
    so the filter can be reused across pipelines.

    Args:
        inplace: Declare that this filter mutates its input buffers. The
            engine then hands it private writable copies instead of
            read-only views. Prefer copying explicitly
            (``img = img.copy()``) in new code.
        **decorator_controls: Mapping of keyword-argument name to a control
            declaration — a ``Control`` instance (or subclass such as
            ``KeyboardControl``) or the ``(default, [min, max])`` tuple
            shorthand.

    Example:
        ```python
        @interactive(gain=(1.0, [0.0, 3.0]))
        def amplify(img, gain=1.0):
            return gain * img
        ```
    """

    def wrapper(func):
        controls = get_controls_from_decorated_function_declaration(func, decorator_controls)

        @functools.wraps(func)
        def inner(*args, **kwargs):
            # Explicit kwargs take precedence: the pipeline engine passes each
            # filter instance's own values (a repeated filter gets per-instance
            # control clones). Registered controls fill the rest so a direct
            # call of the decorated function uses the live control values.
            merged_kwargs = {**controls, **kwargs}
            bound_args = inspect.signature(func).bind(*args, **merged_kwargs)
            bound_args.apply_defaults()

            for k, v in bound_args.arguments.items():
                if isinstance(v, Control):
                    bound_args.arguments[k] = v.value

            # Call the original function with the processed arguments
            return func(*bound_args.args, **bound_args.kwargs)

        # picked up by HeadlessPipeline.from_function when building the FilterCore
        setattr(inner, "__interactive_pipe_inplace__", inplace)
        return inner

    return wrapper

interactive_pipeline

interactive_pipeline(gui: Union[str, Backend, None] = 'auto', safe_input_buffer_deepcopy: bool = True, cache: Union[bool, str] = False, readonly_inputs: bool = True, context: Optional[dict] = None, markdown_description: Optional[str] = None, name: Optional[str] = None, **kwargs_gui: Any) -> Callable[[Callable[..., Any]], Union[HeadlessPipeline, Callable[..., Any]]]

Decorator turning a pipeline function into an interactive GUI application.

The decorated function chains filters (typically declared with @interactive) and is analyzed statically to build the execution graph. Its body must therefore contain only filter-function calls, assignments and a return statement — no control flow (if/for/while) or arithmetic; put such logic inside the filters themselves.

Parameters:

Name Type Description Default
gui Union[str, Backend, None]

Backend used to display the pipeline. One of "auto", "qt", "mpl", "nb" (Jupyter widgets), "gradio" or a Backend enum value. None or "headless" returns a HeadlessPipeline without launching any GUI.

'auto'
safe_input_buffer_deepcopy bool

Deep-copy the input buffers before each run so filters cannot mutate the original inputs.

True
readonly_inputs bool

Hand filters their numpy inputs as read-only views (default True), so in-place mutation (img += 1) raises immediately instead of silently corrupting sibling filters or cached buffers. Declare @interactive(inplace=True) on a filter that intentionally mutates its inputs (it then receives private writable copies), or pass False to restore the permissive behavior.

True
cache Union[bool, str]

Cache intermediate filter outputs between runs. - False (default): recompute every filter on every interaction. - True: sequential prefix cache - a change recomputes every filter after it in source order. - "graph": dependency-aware cache - only filters actually affected by a change are recomputed, following the data-flow graph and runtime-tracked context usage. - "graph-strict": same as "graph", but context reads return numpy arrays as read-only views so accidental in-place mutation raises at the offending line (debug helper).

False
context Optional[dict]

Initial content of the shared context dictionary, readable and writable from filters through the context proxy.

None
markdown_description Optional[str]

Description displayed by backends that support it (e.g. Gradio).

None
name Optional[str]

Window/application title. Defaults to the function name.

None
**kwargs_gui Any

Extra options forwarded to the GUI backend, e.g. size.

{}

Returns:

Type Description
Callable[[Callable[..., Any]], Union[HeadlessPipeline, Callable[..., Any]]]

A decorator. Applied to a pipeline function it returns either a

Callable[[Callable[..., Any]], Union[HeadlessPipeline, Callable[..., Any]]]

HeadlessPipeline (when gui is None or "headless") or a

Callable[[Callable[..., Any]], Union[HeadlessPipeline, Callable[..., Any]]]

callable that launches the GUI when invoked with the pipeline inputs.

Raises:

Type Description
TypeError

If a removed pre-0.9.0 context alias (global_params, state, ...) is passed; use context={...} instead.

Example
@interactive_pipeline(gui="qt", cache=True)
def my_pipeline(img):
    flipped = flip(img)
    amplified = amplify(flipped)
    return flipped, amplified

my_pipeline(input_image)  # opens the interactive window
Source code in src/interactive_pipe/helper/pipeline_decorator.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 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
122
123
124
125
126
127
128
129
130
def interactive_pipeline(
    gui: Union[str, Backend, None] = "auto",
    safe_input_buffer_deepcopy: bool = True,
    cache: Union[bool, str] = False,
    readonly_inputs: bool = True,
    context: Optional[dict] = None,
    markdown_description: Optional[str] = None,
    name: Optional[str] = None,
    **kwargs_gui: Any,
) -> Callable[[Callable[..., Any]], Union[HeadlessPipeline, Callable[..., Any]]]:
    """Decorator turning a pipeline function into an interactive GUI application.

    The decorated function chains filters (typically declared with
    ``@interactive``) and is analyzed statically to build the execution
    graph. Its body must therefore contain only filter-function calls,
    assignments and a return statement — no control flow (if/for/while)
    or arithmetic; put such logic inside the filters themselves.

    Args:
        gui: Backend used to display the pipeline. One of ``"auto"``,
            ``"qt"``, ``"mpl"``, ``"nb"`` (Jupyter widgets), ``"gradio"``
            or a ``Backend`` enum value. ``None`` or ``"headless"``
            returns a ``HeadlessPipeline`` without launching any GUI.
        safe_input_buffer_deepcopy: Deep-copy the input buffers before each
            run so filters cannot mutate the original inputs.
        readonly_inputs: Hand filters their numpy inputs as read-only views
            (default True), so in-place mutation (``img += 1``) raises
            immediately instead of silently corrupting sibling filters or
            cached buffers. Declare ``@interactive(inplace=True)`` on a filter
            that intentionally mutates its inputs (it then receives private
            writable copies), or pass False to restore the permissive
            behavior.
        cache: Cache intermediate filter outputs between runs.
            - False (default): recompute every filter on every interaction.
            - True: sequential prefix cache - a change recomputes every
              filter after it in source order.
            - "graph": dependency-aware cache - only filters actually
              affected by a change are recomputed, following the data-flow
              graph and runtime-tracked ``context`` usage.
            - "graph-strict": same as "graph", but context reads return
              numpy arrays as read-only views so accidental in-place
              mutation raises at the offending line (debug helper).
        context: Initial content of the shared context dictionary, readable
            and writable from filters through the ``context`` proxy.
        markdown_description: Description displayed by backends that support
            it (e.g. Gradio).
        name: Window/application title. Defaults to the function name.
        **kwargs_gui: Extra options forwarded to the GUI backend,
            e.g. ``size``.

    Returns:
        A decorator. Applied to a pipeline function it returns either a
        ``HeadlessPipeline`` (when ``gui`` is None or ``"headless"``) or a
        callable that launches the GUI when invoked with the pipeline inputs.

    Raises:
        TypeError: If a removed pre-0.9.0 context alias (``global_params``,
            ``state``, ...) is passed; use ``context={...}`` instead.

    Example:
        ```python
        @interactive_pipeline(gui="qt", cache=True)
        def my_pipeline(img):
            flipped = flip(img)
            amplified = amplify(flipped)
            return flipped, amplified

        my_pipeline(input_image)  # opens the interactive window
        ```
    """
    # Reject removed aliases of the 'context' parameter with a clear message
    for alias in REMOVED_CONTEXT_ALIASES:
        if alias in kwargs_gui:
            raise TypeError(
                f"@interactive_pipeline: '{alias}' argument was removed in interactive_pipe 0.9.0; "
                "pass context={...} instead."
            )

    def wrapper(pipeline_function: Callable[..., Any]) -> Union[HeadlessPipeline, Callable[..., Any]]:
        headless_pipeline = HeadlessPipeline.from_function(
            pipeline_function,
            safe_input_buffer_deepcopy=safe_input_buffer_deepcopy,
            cache=cache,
            readonly_inputs=readonly_inputs,
            context=context,
        )
        if gui is None or gui == "headless":
            return headless_pipeline
        else:
            InteractivePipeGui = get_interactive_pipeline_class(gui)
            gui_pipeline = InteractivePipeGui(
                pipeline=headless_pipeline,
                markdown_description=markdown_description,
                name=name if name is not None else pipeline_function.__name__,
                **kwargs_gui,
            )

        @functools.wraps(pipeline_function)
        def inner(*args: Any, **kwargs: Any) -> Any:
            return gui_pipeline.__call__(*args, **kwargs)

        # Attach the GUI pipeline object to the function so users can access methods like graph_representation
        setattr(inner, "pipeline", gui_pipeline)
        setattr(inner, "graph_representation", gui_pipeline.graph_representation)

        return inner

    return wrapper