napari / napari-animation

A napari plugin for making animations
https://napari.github.io/napari-animation/
Other
74 stars 27 forks source link

Procedural Animations #155

Open kolibril13 opened 1 year ago

kolibril13 commented 1 year ago

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

```python 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 ```

alisterburt commented 1 year ago

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...

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?

kolibril13 commented 1 year ago

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