Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Procedural Animations #155

Open
kolibril13 opened this issue Mar 6, 2023 · 2 comments
Open

Procedural Animations #155

kolibril13 opened this issue Mar 6, 2023 · 2 comments
Labels
enhancement New feature or request

Comments

@kolibril13
Copy link
Contributor

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.

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

@alisterburt
Copy link
Collaborator

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
    1. capture viewer state
    2. 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?

@kolibril13
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants