napari-animation
napari-animation copied to clipboard
Procedural Animations
Procedural animations are animation that are generated automatically via mathematical transformations that occur over time.
E.g. manim can produce procedural animations with the t.add_updater(foo)
function.
I just wrote a manim script that is connected to the napari viewer, and it produces the below animation.
Note that the camera is panning in a sinusoidal path, while other parameters (zoom, opacity, contrast limits) are changing simultaneously.
I think that won't be possible by only using key frames.
It would open up more possible animations if napari-animation would support these procedural animations as well.
https://user-images.githubusercontent.com/44469195/223225956-2a461fbf-68fc-4f14-92d7-c78daedd4bfe.mp4
Click here to see the script
import napari
from skimage import data
import numpy as np
from manim import *
viewer = napari.view_image(data.cells3d(), channel_axis=1, ndisplay=3)
viewer.camera.angles = (0, 0, 90)
class Example(Scene):
def construct(self):
self.camera.background_color = BLUE_A
img = viewer.screenshot(canvas_only=True, flash=False)
t = ImageMobject(img)
t.time = 0
self.add(t)
tr_zoom = ValueTracker(1)
contrast_tracker = ValueTracker(65535) # dark limit
def foo(mob,dt):
mob.time += dt
SECOND = 1
def bar(mob):
viewer.camera.angles = (0, 30*np.sin((mob.time*PI/11*2)), 50)
viewer.camera.zoom = tr_zoom.get_value()
viewer.layers[0].contrast_limits = (0, contrast_tracker.get_value())
img = viewer.screenshot(canvas_only=True, flash=False)
new_mob = ImageMobject(img)
mob.become(new_mob)
t.add_updater(foo)
t.add_updater(bar)
viewer.layers[1].opacity = 1
self.play(tr_zoom.animate.set_value(2), rate_func=smooth, run_time=SECOND)
self.play(contrast_tracker.animate.set_value(19500), rate_func=smooth, run_time=SECOND) #bright limit
self.wait(SECOND)
viewer.layers[1].opacity = 0
self.play(tr_zoom.animate.set_value(4), rate_func=smooth, run_time=SECOND)
self.wait(SECOND)
self.wait(SECOND)
self.play(tr_zoom.animate.set_value(2), rate_func=smooth, run_time=SECOND)
self.wait(SECOND)
viewer.layers[1].opacity = 1
self.wait(SECOND)
self.play(contrast_tracker.animate.set_value( 65535 ), rate_func=smooth, run_time=SECOND) #dark limit
self.play(tr_zoom.animate.set_value(1), rate_func=smooth, run_time=SECOND)
t.remove_updater(foo)
self.wait(SECOND)
%manim -v WARNING -qh --disable_caching --progress_bar None Example
Hey @kolibril13 - this is cool, it feels like an alternative method for generating a FrameSequence
- I'm not exactly sure of the API in manim but you could...
- write functions for each parameter in terms of t
- loop
- capture viewer state
- increment t
- render the frame sequence
If we do end up integrating this Animation
might become KeyFrameAnimation
and this could be a ProceduralAnimation
- the render method might need to be moved onto the framesequence in that case
sound reasonable?
write functions for each parameter in terms of t
I'm not a fan of case discrimination (see in example below in zoom_over_time
), but yes, that would work.
I'm not familiar with the FrameSequence
module, but I think key_frame = viewer.screenshot()
is most likely not the object that should be phrased to it.
Therefore, this below example is only a sketch, and won't actually run.
@alisterburt : If you're interested, feel free to test if you can make my prototype code run :)
def angle_over_time(t):
return (0, 30*np.sin((t*np.pi)), 50)
def zoom_over_time(t):
zoom = 0
start_time = 1
if t < start_time:
zoom = 2
# zoom starts for t threshold
elif start_time < t:
zoom = t -1
return zoom
my_key_frames = []
for t in np.linspace(0,4, 300):
viewer.camera.angles = angle_over_time(t)
viewer.camera.zoom = zoom_over_time(t)
key_frame = viewer.screenshot() # <- this should probably be something else.
my_key_frames.append(key_frame)
fs = FrameSequence(key_frames = my_key_frames)
animation = Animation(frame_sequence=fs, viewer=viewer, fps=30) # <- this should probably be something else as well.
# and here should happend the rendering