Scripting animation confusion

I’m new to ParaView, but have managed to put together some Python scripts to render nice views of my data. One of the scripts has a phase option, and sweeping the phase through 2*pi radians makes a pretty animation. I’d like to be able to generate a movie of the animation. What I have figured out how to do is produce a bunch of screenshots that I can stitch together externally:

    # I'm running this script in the Python Shell.
    # I've already loaded some .py files that read my data and
    # define the stimulus() function referenced below.
    animation=GetAnimationScene()
    animation.StartTime=0.0
    animation.EndTime=1.0
    animation.NumberOfFrames=20
    animation.PlayMode='Sequence'
    # view is the render view that is being updated
    animation.ViewModules=[view]
    animation.GoToFirst()
    i=0
    while True:
      if animation.AnimationTime == animation.EndTime:
        break
      # stimulus is a function that I've written that does some
      # computations for the given phase and re-renders view
      stimulus([1, 0], phase=animation.AnimationTime*2*pi)
      # I can save screenshots like and stitch them together, but
      # it would be nice if I could generate a movie file directly.
      SaveScreenshot("movie%02d.png"%(i), view)
      i+=1
      animation.GoToNext()
    # This doesn't do what I want; the "movie" is just a static image
    # SaveAnimation("movie.avi")

I think I’m looking for something along the lines of a “SaveAnimationFrame” to be called instead of SaveScreenshot that would take the rendered view and squirrel it away so that SaveAnimation would produce an animated file. I tried adding a Python cue to the animation and calling stimulus() in the tick function, but it doesn’t seem to be defined there (it’s like the script in the cue is running in a separate interpreter).

I’d not recommend using SaveAnimation, as the codec we use is very bad, atm.

Using ffmpeg as a post process to create the actual movie and the current correct usage.

Mathieu is correct, until recently. Here is what i know:

  • ParaView is dependent on the codecs we can find that are opensource. Codecs are the magic libraries that we use to create movies. Within the last few years, these have gotten very good. The last one we updated and improved was for Windows, about a year ago. https://gitlab.kitware.com/paraview/paraview/-/issues/18578.
  • There are numerous reasons to write out a flipbook of images, then post process them into movies. One is that movies are quite lossy compressed. If you ever want one frame from the movie, you want to go to the original frames - NOT try to pull an image out of a movie.
  • Another is that post processing tools, such as ffmped, are more flexible and powerful than the ParaView interface.

Having said all that, I now always tell users that creating .avi’s directly is fine. ParaView makes great movies.

I did not know about this windows only encoder. Interesting.

At this point, all of the .avi’s are quite good. Previous to 5.7.0, especially on Windows, um … clear throat … not so much.

Is there some function or something that I’m missing that makes the SaveAnimation do the right thing? I was hoping that GoToNext would do something like save the rendered view somewhere, update the AnimationTime, and then SaveAnimation would write the whole thing to a file. Or that there was some other function I should call that would somehow tell the animation “hey, the view is rendered and ready, do your thing to save the frame”.

Try

Tools/ Start Trace
File/ Save Animation
Tools/ Stop Trace.

That’s my go to sollution for all things Python…

Unfortunately that just shows a bunch of things that my stimulus() function is adjusting interspersed with SaveScreenshot, and then File>Save Animation just shows a call to SaveAnimation (which writes an unchanging movie). Presumably SaveAnimation is doing something internally to re-render each frame (or do what it thinks should re-render the frame), but in this case I need to somehow be able to hook that internal processing up to stimulus(). If there’s a way to get the Python cue thing to be running in the same environment as the Python shell, I think that might work.

@wascott : @bg2b wants to be able to run script between writing movie frames.

@bg2b : I’m afraid this is not possible yet. You either need to be able to setup the animation using the animation view then use SaveAnimation, or use your current method with SaveScreenshot.

Thanks @mwestphal. Am I correct that the Python cue mechanism is not running in the same environment as the regular Python shell, or did I possibly just mess something up when I was trying to get stimulus() to be called from tick?

I missed this part of your message. Not sure to follow here, could you give more details into what you tried to do and what worked or not ?

I was originally trying something like this:

    animation=GetAnimationScene()
    animation.StartTime=0.0
    animation.EndTime=1.0
    animation.NumberOfFrames=5
    animation.PlayMode='Sequence'

    python_cue=PythonAnimationCue()
    python_cue.Script= """
    from paraview.simple import *

    def start_cue(self): pass

    def tick(self):
       animation=GetAnimationScene()
       stimulus([1, 0], phase=animation.AnimationTime*2*pi)

    def end_cue(self): pass
    """

    animation.Cues.append(python_cue)
    animation.Play()

But when the animation attempts to run, I get a bunch of messages (one per frame)

Traceback (most recent call last):
  File "<string>", line 16, in <module>
  File "<string>", line 10, in tick
NameError: name 'stimulus' is not defined

Edit: To be clear, I have this in another .py file, and I’m running it from the same Python shell window where stimulus() works normally.

what if you global it ?

I tried this

def tick(self):
   animation=GetAnimationScene()
   time=animation.AnimationTime
   global stimulus
   stimulus([1, 0], phase=animation.AnimationTime*2*pi)

Same issue, not defined. Also tried sticking “global stimulus” before and after the definition of stimulus.

Edit: Is there some sort of environment or something for the Python shell? In order to have GetAnimationScene() work in the Python cue it’s necessary to import paraview.simple again. Maybe there’s something I can import that will get the definitions in the Python shell?

I tried some other variants of the global idea, like making a variable and sticking the function in that, but those didn’t work. But then I realized that I do have an object whose handle I can get in the Python cue (the animation scene), so I just need to associate the stimulus function with that somehow. Et voilà…

animation=GetAnimationScene()
animation.StartTime=0.0
animation.EndTime=1.0
animation.NumberOfFrames=30
animation.PlayMode='Sequence'
animation.add_attribute('stimfn', stimulus)

python_cue=PythonAnimationCue()
python_cue.Script= """
from paraview.simple import *

def start_cue(self): pass

def tick(self):
   animation=GetAnimationScene()
   time=animation.AnimationTime
   animation.stimfn([1, 0], phase=animation.AnimationTime*2*pi)

def end_cue(self): pass
"""

animation.Cues.append(python_cue)
animation.Play()

Works perfectly.

1 Like