Open balthazarneveu opened 1 year ago
c0ef1d4f9fa564331ac636d58bd1e405d419352c forbid the declaration of multiple sliders using the same name. :warning: Note that using a unique id defined internally instead of a user defined (or autodefined name) would allow general handling (we could warn the user but still get two sliders with the same "pretty name")
:warning: If you remove name="image_index_2"
, you will get an assert
from interactive_pipe import interactive, interactive_pipeline, pipeline, Control, KeyboardControl
from interactive_pipe.helper.decorator import filter_from_function, get_interactive_pipeline_class
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()
def generate_flat_colored_image_list():
'''Generate a constant colorful image
'''
flat_list = []
for color_choice, color_val in COLOR_DICT.items():
flat_list.append (np.array(color_val) * np.ones((64, 64, 3)))
return flat_list
kb1 = KeyboardControl(value_default=0, value_range=[0, 2], keydown="pagedown", keyup="pageup", modulo=True)
@interactive(image_index=kb1)
def switch_image_1(img1, img2, img3, image_index=0):
return [img1, img2, img3][image_index]
kb2 = KeyboardControl(value_default=0, value_range=[0, 2], name="image_index_2", keydown="down", keyup="up", modulo=True)
@interactive(image_index=kb2)
def switch_image_2(img1, img2, img3, image_index=0):
return [img1, img2, img3][image_index]
@interactive_pipeline(gui="qt")
def sample_pipeline_generated_image():
red, green, blue, gray = generate_flat_colored_image_list()
chosen = switch_image_1(red, green, blue)
chosen2 = switch_image_2(red, green, blue)
return chosen, chosen2
sample_pipeline_generated_image()
basically like interact in iPywidget
Solution Use the @interact
decorator
Write a simple "library".
@interact
decorator , line 6 .
def generate_sine_wave(
frequency=1,
phase=0.,
):
# THIS MIMICKS A PURE LIBRARY. generate_sine_wave will stay untouched
x = np.linspace(0., 1., 100)
crv = Curve(
[
[
x,
np.cos(2.*np.pi*frequency*x+np.deg2rad(phase)),
"k-",
f"sinewave {frequency:.1f}Hz\nphase={int(phase):d}°"
],
[x, np.cos(2.*np.pi*frequency*x), "g--", f"sinewave {frequency:.1f}Hz"],
],
xlabel="time [s]",
ylabel="value",
grid=True,
title="Oscillator"
)
return crv
Then in another cell
frequency_slider = (1., [0.1, 10.], "freq [Hz]")
phase_slider = (90, [-180, 180], "phase [°]")
interact(frequency=frequency_slider, phase=phase_slider, gui="nb")(generate_sine_wave)
If you check generate_sine_wave(frequency=5, phase=10).show() , you'll see a regular static plot, meaning that your original function is still there, left untouched!
This is very similar to Interact in ipywidget
This is sort of the dirty way to define things, but it works. But you can't use the oscillator function as is...
@interact
def oscillator(
frequency=(1., [0.1, 10.], "freq [Hz]"),
phase=(90, [-180, 180], "phase [°]")
):
x = np.linspace(0., 1., 100)
crv = Curve(
[
[
x,
np.cos(2.*np.pi*frequency*x+np.deg2rad(phase)),
"k-",
f"sinewave {frequency:.1f}Hz\nphase={int(phase):d}°"
],
[x, np.cos(2.*np.pi*frequency*x), "g--", f"sinewave {frequency:.1f}Hz"],
],
xlabel="time [s]",
ylabel="value",
grid=True,
title="Oscillator"
)
return crv
# you should see a GUI appear now!
Now, you can still run oscillator afterward like oscillator(frequency=5., phase=80.).show()
.
But keep in mind that you can't run oscillator()
just like that..
because the default arguments are tuples or Controls ... which are not supported by the pure function. So you need to pass the keyword args you want.
interact
decorator@interact(
frequency = (1., [0.1, 10.], "freq [Hz]"),
phase = (17, [-180, 180], "phase [°]")
)
def oscillator_func( frequency=4.2, phase=42,):
Now you can even run the function again oscillator_func().show()
(will use frequency=4.2, phase=42)
This is a nice little decorator which allows you performing an almost "unitary" check on each filter definition, without spoiling the code.
You could simply leave the default values in your code for the sliders & comment/uncomment the decorator or use disable=True
You can also use a dedicated file for interactive "testing".
import generate_sine_wave
from interactive_pipe import interact
frequency_slider = (8., [0.1, 10.], "freq [Hz]")
phase_slider = (90, [-180, 180], "phase [°]")
interact(frequency=frequency_slider, phase=phase_slider, gui="nb")(generate_sine_wave)
in another file...
Using the interactive decorator
WARNING
In notebooks, people tend to execute the same cell several times, you can't declare the same interactive_filters several times :-1: The trick is to reset the
registered_controls_names
list.from interactive_pipe.helper import _private
_private.registered_controls_names = []
Preliminary checks
kb = KeyboardControl(value_default=0, value_range=[0, 2], keydown="pagedown", keyup="pageup", modulo=True)
@interactive
willHeadlessPipeline.from_function
, sliders are found and automatically retrieved.MAJOR BUG RELATED #18 There was no
update_parameters_from_controls
method called in __run in the HeadlessPipeline class:exclamation: This whole thing worked thanks to the decorator which keeps all sliders & applies .values before running the function. This is a nice trick to be able to use the decorated function "as-is" but it hides some bugs.
When moving on to using filters, you actually get "non responsive" sliders.
TODOS
@interactive
could use a FilterCore class & use__call__
?