Module specrel.graphics.companim
Composite graphics that glue together one or more simple spacetime animations in special relativity.
Source code
"""Composite graphics that glue together one or more simple spacetime
animations in special relativity.
"""
from abc import abstractmethod
import copy
import os
import subprocess
import matplotlib.pyplot as plt
from specrel.graphics import graphrc
import specrel.graphics.basegraph as bgraph
import specrel.graphics.simpanim as sanim
class MultiAnimator(bgraph.FigureCreator, bgraph.BaseAnimator):
"""Runs multiple animations simultaneously on different subplots.
Args:
n_animations (int): Number of animations to run simultaneously.
fig (matplotlib.figure.Figure, optional): Figure window. If `None`, a
new figure window is created.
axs (list, optional): List of axes in row major format. If `None`, a
new set of axes is created under a new figure window.
others: See `specrel.graphics.basegraph.BaseAnimator`.
Attributes:
axs (list): List of axes to draw on in row major format.
"""
def __init__(self,
n_animations,
fig=graphrc['fig'],
axs=graphrc['axs'],
stepsize=None,
fps=graphrc['anim.fps'],
display_current=graphrc['anim.display_current'],
display_current_decimals=
graphrc['anim.display_current_decimals'],
title=graphrc['title'],
frame_lim=(None, None)):
bgraph.FigureCreator.__init__(self)
# Make a new figure if necessary
if fig is None and axs is None:
# Default to a (1 x n) subplot
fig, axs = plt.subplots(1, n_animations)
elif fig is None or axs is None:
raise ValueError('Must give both figure and axes, or neither.')
self.axs = axs
bgraph.BaseAnimator.__init__(self, fig, stepsize, fps, display_current,
display_current_decimals, title, frame_lim)
# Make sure there's at least one animation
if n_animations == 0:
raise ValueError('Number of animations must be nonzero.')
# Make sure animations and axes match
if n_animations != len(self.axs):
raise ValueError('Number of animations and axes must match.')
# Set up animators
self._animators = []
# Text in the suptitle
self._current_title_text = None
if self.title:
self._current_title_text = fig.suptitle(self.title)
elif self.display_current:
self._current_title_text = fig.suptitle('')
def clear(self):
for anim in self._animators:
anim.clear()
def init_func(self):
return [anim.init_func() for anim in self._animators]
@abstractmethod
def _val_text(self, val):
"""Return formatted display text for the given frame value."""
return ''
def _update_current_text(self, val):
"""Update the current title text for a given frame value."""
new_text = ''
if self.title:
new_text += self.title + '\n'
new_text += self._val_text(val)
self._current_title_text.set_text(new_text)
def update(self, frame):
artists = []
if self.display_current:
self._update_current_text(self.calc_frame_val(frame))
artists.append(self._current_title_text)
else:
# Still update title every frame in case it changes
self._current_title_text.set_text(self.title)
return artists + [anim.update(frame) for anim in self._animators]
def get_frame_lim(self):
# Expand the frame lim enough to be able to encompass the frame limits
# of every animator
start_frame = None
frame_lim = super().get_frame_lim()
if frame_lim[0] is not None:
start_frame = frame_lim[0]
else:
start_frame = min(
[anim.get_frame_lim()[0] for anim in self._animators])
end_frame = None
if frame_lim[1] is not None:
end_frame = frame_lim[1]
else:
end_frame = max(
[anim.get_frame_lim()[1] for anim in self._animators])
return (start_frame, end_frame)
class MultiTimeAnimator(MultiAnimator, sanim.TimeAnimator):
"""Runs multiple time-evolution animations simultaneously on different
subplots.
Args:
animations_params (list): List of dictionaries containing parameters for
animators in each subplot. Each individual dictionary has the
following items:
- **animator**: `type`, initializer for the
`specrel.graphics.simpanim.STAnimator` to use.
- **transformable**: `specrel.geom.LorentzTransformable` object to
animate.
- **animator_options**: optional, dictionary of certain keyword
arguments for `animator`, *except* for the following:
- `fig`
- `ax`
- `ct_per_sec`
- `instant_pause_time`
- `fps`
- `display_current_time`
- `display_current_time_decimals`
- **draw_options**: optional, dictionary of keyword arguments to
`specrel.geom.LorentzTransformable.draw`, except for the
`plotter` argument.
fig (matplotlib.figure.Figure, optional): See
`specrel.graphics.companim.MultiAnimator`.
axs (list, optional): See `specrel.graphics.companim.MultiAnimator`.
tlim (tuple, optional): Start and end time of the animation.
others: See `specrel.graphics.simpanim.TimeAnimator`.
"""
def __init__(self,
animations_params,
fig=graphrc['fig'],
axs=graphrc['axs'],
tlim=(None, None),
ct_per_sec=graphrc['anim.time.ct_per_sec'],
instant_pause_time=graphrc['anim.time.instant_pause_time'],
fps=graphrc['anim.fps'],
display_current_time=graphrc['anim.display_current'],
display_current_time_decimals=
graphrc['anim.display_current_decimals'],
title=graphrc['title']):
sanim.TimeAnimator.__init__(self, fig, ct_per_sec, instant_pause_time,
fps, display_current_time, display_current_time_decimals)
frame_lim = (
None if tlim[0] is None else self.calc_frame_idx(tlim[0]),
None if tlim[1] is None else self.calc_frame_idx(tlim[1])
)
MultiAnimator.__init__(self, len(animations_params), fig, axs,
self.stepsize, fps, display_current_time,
display_current_time_decimals, title, frame_lim)
# Set up animators and draw the objects
for params, ax in zip(animations_params, self.axs):
# Construct animator
anim_kwargs = {}
if 'animator_options' in params:
anim_kwargs = dict(params['animator_options'])
for omit in ['fig', 'ax', 'ct_per_sec', 'instant_pause_time',
'fps', 'display_current_time',
'display_current_time_decimals']:
anim_kwargs.pop(omit, None)
anim = params['animator'](
fig=self.fig, ax=ax,
ct_per_sec=ct_per_sec,
instant_pause_time=instant_pause_time,
fps=fps,
display_current_time=False,
**anim_kwargs)
# Draw object with animator
draw_kwargs = {}
if 'draw_options' in params:
draw_kwargs = dict(params['draw_options'])
draw_kwargs.pop('plotter', None)
params['transformable'].draw(plotter=anim, **draw_kwargs)
# Add animator to the master list
self._animators.append(anim)
# Broaden the frame lists across all animators
broadened_flim = self.get_frame_lim()
broadened_tlim = (self.calc_frame_val(broadened_flim[0]),
self.calc_frame_val(broadened_flim[1]))
for anim in self._animators:
anim.resize(broadened_tlim)
def _val_text(self, val):
return f'Time = {{:.{self.display_current_decimals}f}} s'.format(val)
"""Get dict with keys corresponding to frame indexes, and boolean values
for whether or not any animator has flagged the frame for pause"""
def _get_frame_pause_flags(self):
frame_pause_flags = {}
for anim in self._animators:
for frame, flag in anim.frame_pause_flags.items():
if frame not in frame_pause_flags:
frame_pause_flags[frame] = False
# Pause if any animator pauses at a specific frame
frame_pause_flags[frame] = max(frame_pause_flags[frame], flag)
return frame_pause_flags
def _get_frame_list(self):
# Insert pause frames where necessary
return [f for f in MultiAnimator._get_frame_list(self)
for repeat in range(
self._pause_frames if self._get_frame_pause_flags()[f] else 1)]
class MultiTransformAnimator(MultiAnimator):
"""Runs multiple Lorentz transform animations simultaneously on different
subplots.
Args:
animation_params (list): List of dictionaries containing parameters for
animators in each subplot. Each individual dictionary has the
following items:
- **transformable**: `specrel.geom.LorentzTransformable` object to
animate.
- **animator_options**: optional, dictionary of certain keyword
arguments for `specrel.graphics.simpanim.TransformAnimator`
initialization, from the following list:
- `origin`
- `stanimator`
- `stanimator_options`
- `tlim`
- `xlim`
- `time`
- `title`
fig (matplotlib.figure.Figure, optional): See
`specrel.graphics.companim.MultiAnimator`.
axs (list, optional): See `specrel.graphics.companim.MultiAnimator`.
others: See `specrel.graphics.simpanim.TransformAnimator`.
"""
def __init__(self,
animations_params,
velocity,
fig=graphrc['fig'],
axs=graphrc['axs'],
transition_duration=graphrc['anim.transform.transition_duration'],
fps=graphrc['anim.fps'],
display_current_velocity=graphrc['anim.display_current'],
display_current_velocity_decimals=
graphrc['anim.display_current_decimals'],
title=graphrc['title']):
nsteps = round(transition_duration * fps)
super().__init__(len(animations_params), fig, axs, velocity / nsteps,
fps, display_current_velocity, display_current_velocity_decimals,
title, (None, None))
# Set up animators
for params, ax in zip(animations_params, self.axs):
# Construct animator
anim_kwargs = {}
if 'animator_options' in params:
anim_kwargs = dict(params['animator_options'])
for omit in ['lorentz_transformable', 'velocity', 'fig', 'ax',
'transition_duration', 'fps', 'display_current_velocity',
'display_current_velocity_decimals']:
anim_kwargs.pop(omit, None)
# Add animator to the master list
self._animators.append(
sanim.TransformAnimator(
copy.deepcopy(params['transformable']),
velocity,
fig=self.fig,
ax=ax,
transition_duration=transition_duration,
fps=fps,
display_current_velocity=False,
**anim_kwargs
)
)
def _val_text(self, val):
return f'$v = {{:.{self.display_current_decimals}f}}c$'.format(val)
class Rewinder(bgraph.BaseAnimator):
"""Animates the "rewinding" of an animator's animation.
Args:
animator (specrel.graphics.basegraph.BaseAnimator): Animator to rewind.
rewind_rate (int, optional): See `rewind_rate` attribute.
rewind_title (str, optional): Rewind animation title; overrides title of
the original animation.
end_pause (float, optional): Pause length in seconds to insert at the
beginning and end of the rewind animation.
Attributes:
animator (specrel.graphics.basegraph.BaseAnimator): Animator to rewind.
end_pause_frames (int): Number of pause frame at the beginning and end
of the rewind animation.
rewind_rate (int): Speedup factor of the rewind animation relative to
the original.
"""
def __init__(self, animator, rewind_rate=2,
rewind_title='Rewinding \u25C0 \u25C0', end_pause=1):
super().__init__(animator.fig, animator.stepsize, animator.fps,
animator.display_current, animator.display_current_decimals,
rewind_title, animator.get_frame_lim())
self.animator = animator
self.rewind_rate = rewind_rate
self.end_pause_frames = round(end_pause * self.fps)
def clear(self):
self._cached_anim = None
def init_func(self):
return self.animator.init_func()
def update(self, frame):
old_title = self.animator.title
# Temporarily change the title of the animator if actually rewinding
frame_lim = self.get_frame_lim()
if frame != frame_lim[0] and frame != frame_lim[1]:
self.animator.title = self.title
artist = self.animator.update(frame)
self.animator.title = old_title
return artist
def _get_frame_list(self):
# Run through frames backwards, at rewind_rate times the rate of the
# original
frame_lim = self.get_frame_lim()
frame_list = list(range(
frame_lim[1], frame_lim[0]-1, -self.rewind_rate))
# Explicitly make sure the starting frame is included
if frame_lim[0] not in frame_list:
frame_list.append(frame_lim[0])
# Pause at the start and end
# if self.end_pause_frames is 0, -1*[entry] will add an empty list
frame_list = (self.end_pause_frames - 1)*frame_list[:1] \
+ frame_list + (self.end_pause_frames - 1)*frame_list[-1:]
return frame_list
def concat_demuxer(input_files, output_file):
"""Concatenate video files.
Args:
input_files (list): List of input file names.
output_file (str): Output file name.
"""
# Write temporary file list as input for FFmpeg
tempfilename = 'concat_demuxer_temp_input_file.txt'
with open(tempfilename, 'w+') as tempfile:
for i in input_files:
tempfile.write(f"file '{i}'\n")
# Concatenate the video files with FFmpeg's concat demuxer
subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', tempfilename,
'-c', 'copy', output_file])
# Delete temporary file list
try:
os.remove(tempfilename)
except OSError as e:
print(f"[Error] {e.strerror}: '{tempfilename}'")
Functions
def concat_demuxer(input_files, output_file)-
Concatenate video files.
Args
input_files:list- List of input file names.
output_file:str- Output file name.
Source code
def concat_demuxer(input_files, output_file): """Concatenate video files. Args: input_files (list): List of input file names. output_file (str): Output file name. """ # Write temporary file list as input for FFmpeg tempfilename = 'concat_demuxer_temp_input_file.txt' with open(tempfilename, 'w+') as tempfile: for i in input_files: tempfile.write(f"file '{i}'\n") # Concatenate the video files with FFmpeg's concat demuxer subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', tempfilename, '-c', 'copy', output_file]) # Delete temporary file list try: os.remove(tempfilename) except OSError as e: print(f"[Error] {e.strerror}: '{tempfilename}'")
Classes
class MultiAnimator (n_animations, fig=None, axs=None, stepsize=None, fps=50, display_current=True, display_current_decimals=3, title=None, frame_lim=(None, None))-
Runs multiple animations simultaneously on different subplots.
Args
n_animations:int- Number of animations to run simultaneously.
fig:matplotlib.figure.Figure, optional- Figure window. If
None, a new figure window is created. axs:list, optional- List of axes in row major format. If
None, a new set of axes is created under a new figure window. others- See
BaseAnimator.
Attributes
axs:list- List of axes to draw on in row major format.
Source code
class MultiAnimator(bgraph.FigureCreator, bgraph.BaseAnimator): """Runs multiple animations simultaneously on different subplots. Args: n_animations (int): Number of animations to run simultaneously. fig (matplotlib.figure.Figure, optional): Figure window. If `None`, a new figure window is created. axs (list, optional): List of axes in row major format. If `None`, a new set of axes is created under a new figure window. others: See `specrel.graphics.basegraph.BaseAnimator`. Attributes: axs (list): List of axes to draw on in row major format. """ def __init__(self, n_animations, fig=graphrc['fig'], axs=graphrc['axs'], stepsize=None, fps=graphrc['anim.fps'], display_current=graphrc['anim.display_current'], display_current_decimals= graphrc['anim.display_current_decimals'], title=graphrc['title'], frame_lim=(None, None)): bgraph.FigureCreator.__init__(self) # Make a new figure if necessary if fig is None and axs is None: # Default to a (1 x n) subplot fig, axs = plt.subplots(1, n_animations) elif fig is None or axs is None: raise ValueError('Must give both figure and axes, or neither.') self.axs = axs bgraph.BaseAnimator.__init__(self, fig, stepsize, fps, display_current, display_current_decimals, title, frame_lim) # Make sure there's at least one animation if n_animations == 0: raise ValueError('Number of animations must be nonzero.') # Make sure animations and axes match if n_animations != len(self.axs): raise ValueError('Number of animations and axes must match.') # Set up animators self._animators = [] # Text in the suptitle self._current_title_text = None if self.title: self._current_title_text = fig.suptitle(self.title) elif self.display_current: self._current_title_text = fig.suptitle('') def clear(self): for anim in self._animators: anim.clear() def init_func(self): return [anim.init_func() for anim in self._animators] @abstractmethod def _val_text(self, val): """Return formatted display text for the given frame value.""" return '' def _update_current_text(self, val): """Update the current title text for a given frame value.""" new_text = '' if self.title: new_text += self.title + '\n' new_text += self._val_text(val) self._current_title_text.set_text(new_text) def update(self, frame): artists = [] if self.display_current: self._update_current_text(self.calc_frame_val(frame)) artists.append(self._current_title_text) else: # Still update title every frame in case it changes self._current_title_text.set_text(self.title) return artists + [anim.update(frame) for anim in self._animators] def get_frame_lim(self): # Expand the frame lim enough to be able to encompass the frame limits # of every animator start_frame = None frame_lim = super().get_frame_lim() if frame_lim[0] is not None: start_frame = frame_lim[0] else: start_frame = min( [anim.get_frame_lim()[0] for anim in self._animators]) end_frame = None if frame_lim[1] is not None: end_frame = frame_lim[1] else: end_frame = max( [anim.get_frame_lim()[1] for anim in self._animators]) return (start_frame, end_frame)Ancestors
- FigureCreator
- BaseAnimator
- abc.ABC
Subclasses
Inherited members
class MultiTimeAnimator (animations_params, fig=None, axs=None, tlim=(None, None), ct_per_sec=1, instant_pause_time=1, fps=50, display_current_time=True, display_current_time_decimals=3, title=None)-
Runs multiple time-evolution animations simultaneously on different subplots.
Args
animations_params:list-
List of dictionaries containing parameters for animators in each subplot. Each individual dictionary has the following items:
- animator:
type, initializer for theSTAnimatorto use. - transformable:
LorentzTransformableobject to animate. - animator_options: optional, dictionary of certain keyword
arguments for
animator, except for the following:figaxct_per_secinstant_pause_timefpsdisplay_current_timedisplay_current_time_decimals
- draw_options: optional, dictionary of keyword arguments to
LorentzTransformable.draw(), except for theplotterargument.
- animator:
fig:matplotlib.figure.Figure, optional- See
MultiAnimator. axs:list, optional- See
MultiAnimator. tlim:tuple, optional- Start and end time of the animation.
others- See
TimeAnimator.
Source code
class MultiTimeAnimator(MultiAnimator, sanim.TimeAnimator): """Runs multiple time-evolution animations simultaneously on different subplots. Args: animations_params (list): List of dictionaries containing parameters for animators in each subplot. Each individual dictionary has the following items: - **animator**: `type`, initializer for the `specrel.graphics.simpanim.STAnimator` to use. - **transformable**: `specrel.geom.LorentzTransformable` object to animate. - **animator_options**: optional, dictionary of certain keyword arguments for `animator`, *except* for the following: - `fig` - `ax` - `ct_per_sec` - `instant_pause_time` - `fps` - `display_current_time` - `display_current_time_decimals` - **draw_options**: optional, dictionary of keyword arguments to `specrel.geom.LorentzTransformable.draw`, except for the `plotter` argument. fig (matplotlib.figure.Figure, optional): See `specrel.graphics.companim.MultiAnimator`. axs (list, optional): See `specrel.graphics.companim.MultiAnimator`. tlim (tuple, optional): Start and end time of the animation. others: See `specrel.graphics.simpanim.TimeAnimator`. """ def __init__(self, animations_params, fig=graphrc['fig'], axs=graphrc['axs'], tlim=(None, None), ct_per_sec=graphrc['anim.time.ct_per_sec'], instant_pause_time=graphrc['anim.time.instant_pause_time'], fps=graphrc['anim.fps'], display_current_time=graphrc['anim.display_current'], display_current_time_decimals= graphrc['anim.display_current_decimals'], title=graphrc['title']): sanim.TimeAnimator.__init__(self, fig, ct_per_sec, instant_pause_time, fps, display_current_time, display_current_time_decimals) frame_lim = ( None if tlim[0] is None else self.calc_frame_idx(tlim[0]), None if tlim[1] is None else self.calc_frame_idx(tlim[1]) ) MultiAnimator.__init__(self, len(animations_params), fig, axs, self.stepsize, fps, display_current_time, display_current_time_decimals, title, frame_lim) # Set up animators and draw the objects for params, ax in zip(animations_params, self.axs): # Construct animator anim_kwargs = {} if 'animator_options' in params: anim_kwargs = dict(params['animator_options']) for omit in ['fig', 'ax', 'ct_per_sec', 'instant_pause_time', 'fps', 'display_current_time', 'display_current_time_decimals']: anim_kwargs.pop(omit, None) anim = params['animator']( fig=self.fig, ax=ax, ct_per_sec=ct_per_sec, instant_pause_time=instant_pause_time, fps=fps, display_current_time=False, **anim_kwargs) # Draw object with animator draw_kwargs = {} if 'draw_options' in params: draw_kwargs = dict(params['draw_options']) draw_kwargs.pop('plotter', None) params['transformable'].draw(plotter=anim, **draw_kwargs) # Add animator to the master list self._animators.append(anim) # Broaden the frame lists across all animators broadened_flim = self.get_frame_lim() broadened_tlim = (self.calc_frame_val(broadened_flim[0]), self.calc_frame_val(broadened_flim[1])) for anim in self._animators: anim.resize(broadened_tlim) def _val_text(self, val): return f'Time = {{:.{self.display_current_decimals}f}} s'.format(val) """Get dict with keys corresponding to frame indexes, and boolean values for whether or not any animator has flagged the frame for pause""" def _get_frame_pause_flags(self): frame_pause_flags = {} for anim in self._animators: for frame, flag in anim.frame_pause_flags.items(): if frame not in frame_pause_flags: frame_pause_flags[frame] = False # Pause if any animator pauses at a specific frame frame_pause_flags[frame] = max(frame_pause_flags[frame], flag) return frame_pause_flags def _get_frame_list(self): # Insert pause frames where necessary return [f for f in MultiAnimator._get_frame_list(self) for repeat in range( self._pause_frames if self._get_frame_pause_flags()[f] else 1)]Ancestors
Inherited members
class MultiTransformAnimator (animations_params, velocity, fig=None, axs=None, transition_duration=2, fps=50, display_current_velocity=True, display_current_velocity_decimals=3, title=None)-
Runs multiple Lorentz transform animations simultaneously on different subplots.
Args
animation_params:list-
List of dictionaries containing parameters for animators in each subplot. Each individual dictionary has the following items:
- transformable:
LorentzTransformableobject to animate. - animator_options: optional, dictionary of certain keyword
arguments for
TransformAnimatorinitialization, from the following list:originstanimatorstanimator_optionstlimxlimtimetitle
- transformable:
fig:matplotlib.figure.Figure, optional- See
MultiAnimator. axs:list, optional- See
MultiAnimator. others- See
TransformAnimator.
Source code
class MultiTransformAnimator(MultiAnimator): """Runs multiple Lorentz transform animations simultaneously on different subplots. Args: animation_params (list): List of dictionaries containing parameters for animators in each subplot. Each individual dictionary has the following items: - **transformable**: `specrel.geom.LorentzTransformable` object to animate. - **animator_options**: optional, dictionary of certain keyword arguments for `specrel.graphics.simpanim.TransformAnimator` initialization, from the following list: - `origin` - `stanimator` - `stanimator_options` - `tlim` - `xlim` - `time` - `title` fig (matplotlib.figure.Figure, optional): See `specrel.graphics.companim.MultiAnimator`. axs (list, optional): See `specrel.graphics.companim.MultiAnimator`. others: See `specrel.graphics.simpanim.TransformAnimator`. """ def __init__(self, animations_params, velocity, fig=graphrc['fig'], axs=graphrc['axs'], transition_duration=graphrc['anim.transform.transition_duration'], fps=graphrc['anim.fps'], display_current_velocity=graphrc['anim.display_current'], display_current_velocity_decimals= graphrc['anim.display_current_decimals'], title=graphrc['title']): nsteps = round(transition_duration * fps) super().__init__(len(animations_params), fig, axs, velocity / nsteps, fps, display_current_velocity, display_current_velocity_decimals, title, (None, None)) # Set up animators for params, ax in zip(animations_params, self.axs): # Construct animator anim_kwargs = {} if 'animator_options' in params: anim_kwargs = dict(params['animator_options']) for omit in ['lorentz_transformable', 'velocity', 'fig', 'ax', 'transition_duration', 'fps', 'display_current_velocity', 'display_current_velocity_decimals']: anim_kwargs.pop(omit, None) # Add animator to the master list self._animators.append( sanim.TransformAnimator( copy.deepcopy(params['transformable']), velocity, fig=self.fig, ax=ax, transition_duration=transition_duration, fps=fps, display_current_velocity=False, **anim_kwargs ) ) def _val_text(self, val): return f'$v = {{:.{self.display_current_decimals}f}}c$'.format(val)Ancestors
- MultiAnimator
- FigureCreator
- BaseAnimator
- abc.ABC
Inherited members
class Rewinder (animator, rewind_rate=2, rewind_title='Rewinding ◀ ◀', end_pause=1)-
Animates the "rewinding" of an animator's animation.
Args
animator:BaseAnimator- Animator to rewind.
rewind_rate:int, optional- See
rewind_rateattribute. rewind_title:str, optional- Rewind animation title; overrides title of the original animation.
end_pause:float, optional- Pause length in seconds to insert at the beginning and end of the rewind animation.
Attributes
animator:BaseAnimator- Animator to rewind.
end_pause_frames:int- Number of pause frame at the beginning and end of the rewind animation.
rewind_rate:int- Speedup factor of the rewind animation relative to the original.
Source code
class Rewinder(bgraph.BaseAnimator): """Animates the "rewinding" of an animator's animation. Args: animator (specrel.graphics.basegraph.BaseAnimator): Animator to rewind. rewind_rate (int, optional): See `rewind_rate` attribute. rewind_title (str, optional): Rewind animation title; overrides title of the original animation. end_pause (float, optional): Pause length in seconds to insert at the beginning and end of the rewind animation. Attributes: animator (specrel.graphics.basegraph.BaseAnimator): Animator to rewind. end_pause_frames (int): Number of pause frame at the beginning and end of the rewind animation. rewind_rate (int): Speedup factor of the rewind animation relative to the original. """ def __init__(self, animator, rewind_rate=2, rewind_title='Rewinding \u25C0 \u25C0', end_pause=1): super().__init__(animator.fig, animator.stepsize, animator.fps, animator.display_current, animator.display_current_decimals, rewind_title, animator.get_frame_lim()) self.animator = animator self.rewind_rate = rewind_rate self.end_pause_frames = round(end_pause * self.fps) def clear(self): self._cached_anim = None def init_func(self): return self.animator.init_func() def update(self, frame): old_title = self.animator.title # Temporarily change the title of the animator if actually rewinding frame_lim = self.get_frame_lim() if frame != frame_lim[0] and frame != frame_lim[1]: self.animator.title = self.title artist = self.animator.update(frame) self.animator.title = old_title return artist def _get_frame_list(self): # Run through frames backwards, at rewind_rate times the rate of the # original frame_lim = self.get_frame_lim() frame_list = list(range( frame_lim[1], frame_lim[0]-1, -self.rewind_rate)) # Explicitly make sure the starting frame is included if frame_lim[0] not in frame_list: frame_list.append(frame_lim[0]) # Pause at the start and end # if self.end_pause_frames is 0, -1*[entry] will add an empty list frame_list = (self.end_pause_frames - 1)*frame_list[:1] \ + frame_list + (self.end_pause_frames - 1)*frame_list[-1:] return frame_listAncestors
- BaseAnimator
- abc.ABC
Inherited members