parallel-matplotlib-animation 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parallel_animate/__init__.py +1 -0
- parallel_animate/animator.py +387 -0
- parallel_animate/examples/__init__.py +0 -0
- parallel_animate/examples/multi_panel_animation.py +144 -0
- parallel_animate/examples/scaling_test.py +96 -0
- parallel_animate/examples/simple_wave_animation.py +37 -0
- parallel_animate/examples/very_complex_animation.py +634 -0
- parallel_matplotlib_animation-0.1.0.dist-info/METADATA +125 -0
- parallel_matplotlib_animation-0.1.0.dist-info/RECORD +11 -0
- parallel_matplotlib_animation-0.1.0.dist-info/WHEEL +4 -0
- parallel_matplotlib_animation-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parallel-matplotlib-animation
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Animate matplotlib figures into videos, in parallel but with efficient caching
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Sibo Wang-Chen
|
|
7
|
+
Author-email: sibo.wang@epfl.ch
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: Pillow (>=11.0)
|
|
16
|
+
Requires-Dist: av (>=14.0)
|
|
17
|
+
Requires-Dist: matplotlib (>=3.10.0)
|
|
18
|
+
Requires-Dist: numpy (>=2,<3)
|
|
19
|
+
Requires-Dist: tqdm (>=4.67)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# parallel-matplotlib-animation
|
|
23
|
+
|
|
24
|
+
Create matplotlib animations rendered to video in parallel, with efficient resources reuse.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install parallel-matplotlib-animation
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
or from a local copy:
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/sibocw/parallel-matplotlib-animation.git
|
|
35
|
+
cd parallel-matplotlib-animation
|
|
36
|
+
pip install -e . --config-settings editable_mode=compat
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## What it does
|
|
40
|
+
|
|
41
|
+
Renders matplotlib animations by:
|
|
42
|
+
1. Creating a bunch of worker processes, and creating matplotlib resources (plt.Figure, plt.Axes, artists, etc.) once per worker
|
|
43
|
+
2. Distributing frames across workers via a dynamic queue
|
|
44
|
+
3. Rendering the assigned frames from each worker, but updating the data only (without redrawing the whole plot from scratch)
|
|
45
|
+
4. Encoding frames to video with PyAV (very efficient FFmpeg under the hood)
|
|
46
|
+
|
|
47
|
+
**Key design: Figure reuse.** In each worker process, `setup()` runs once to create the figure, then `update()` modifies it repeatedly. This brings the best of:
|
|
48
|
+
- Serial processing: avoids the overhead of recreating complex layouts for every frame
|
|
49
|
+
- Parallel processing: accomplishes speedup by using multiple CPU cores
|
|
50
|
+
|
|
51
|
+
## Quick example
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import numpy as np
|
|
55
|
+
import matplotlib.pyplot as plt
|
|
56
|
+
from parallel_animate import Animator
|
|
57
|
+
|
|
58
|
+
# Step 1: Create a child class of parallel_animate.Animator
|
|
59
|
+
class WaveAnimation(Animator):
|
|
60
|
+
|
|
61
|
+
# Step 2: Define how the plot should be setup
|
|
62
|
+
def setup(self):
|
|
63
|
+
fig, ax = plt.subplots()
|
|
64
|
+
self.x = np.linspace(0, 4 * np.pi, 200)
|
|
65
|
+
(self.line,) = ax.plot(self.x, np.cos(self.x))
|
|
66
|
+
ax.set_xlim(0, 4 * np.pi)
|
|
67
|
+
ax.set_ylim(-1.5, 1.5)
|
|
68
|
+
ax.set_xlabel("x")
|
|
69
|
+
ax.set_ylabel("y")
|
|
70
|
+
ax.set_title("Cosine Wave")
|
|
71
|
+
return fig # <- return a plt.Figure object
|
|
72
|
+
|
|
73
|
+
# Step 3: Define how plot elements should be updated for each frame
|
|
74
|
+
# (given parameters that you define later)
|
|
75
|
+
def update(self, frame_idx, params):
|
|
76
|
+
phase = params["phase"]
|
|
77
|
+
self.line.set_ydata(np.cos(self.x + phase))
|
|
78
|
+
|
|
79
|
+
# Step 4: Define a list of input parameters, one for each frame
|
|
80
|
+
params = [{"phase": 2 * np.pi * i / 60} for i in range(60)]
|
|
81
|
+
|
|
82
|
+
# Step 5: Make video in parallel
|
|
83
|
+
anim = WaveAnimation()
|
|
84
|
+
anim.make_video("wave.mp4", param_by_frame=params, fps=30, num_workers=4)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
This library has a single class: `parallel_animate.Animator`. To make an animation, you must create your own class inheriting from it and define the following methods:
|
|
91
|
+
|
|
92
|
+
- **`.setup(self)`**: No input argument except `self`. In this method, you can setup your figure however you like. Just make sure you return the figure you created (i.e. the `plt.Figure` object). You might want to save the things you created as attributes—axes, return values of plotting calls like `plt.plot`, etc. This way, you can access and modify them in the update method.
|
|
93
|
+
- **`.update(self, frame_idx, params)`**: Given the frame index and some input parameters, update the plot elements. `params` is typically a dictionary of variables, but really it can be any Python object (tuple, a single value, etc.) as long as it's picklable. In this method, you want to call methods like `.set_data` on the plot elements that you created in setup and saved as attributes.
|
|
94
|
+
- (Optional) **`__init__(self, ...)`**: You can add any custom logic here. It's handy if you want to create many animation instances using the same custom class, but with different parameters. For example, if you make `__init__` accept an input data path, you can do things like `anim = RecordingAnimator(dataset_path=...)` and animate many datasets in a loop.
|
|
95
|
+
|
|
96
|
+
Once you have defined your animator class, there is a single method that you need to call that makes the video: **`.make_video(...)`**. It accepts the following arguments:
|
|
97
|
+
|
|
98
|
+
- `output_file` (Path or str): Output video path
|
|
99
|
+
- `param_by_frame` (list): List of parameters. Each element in the list is the `params` argument to be given to the `.update` call for the corresponding frame.
|
|
100
|
+
- `fps` (int): Frame rate of the output video
|
|
101
|
+
- `num_workers` (int): Number of worker processes to be spawned. If -1, use all CPU cores. If -2, use all but one CPU cores, etc. If 1, no child process is created and the video is made in the main process itself. Default is -1.
|
|
102
|
+
- See the docstring for `parallel_animate.animator` directly for less commonly used, optional parameters. These control logging, rendering quality, etc.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## Examples
|
|
106
|
+
|
|
107
|
+
See [`src/parallel_animate/examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/src/parallel_animate/examples/):
|
|
108
|
+
- `simple_wave_animation.py`: The example above
|
|
109
|
+
- `multi_panel_animation.py`: 5 subplots with different plot types
|
|
110
|
+
- `very_complex_animation.py`: 14 subplots with GridSpec layout
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## Performance test
|
|
114
|
+
|
|
115
|
+
A [strong scaling test](https://hpc-wiki.info/hpc/Scaling_tests#Strong_Scaling) is implemented in `src/parallel_animate/examples/scaling_test.py`. Here's the result on my 8-core (16-thread) Intel Core i9-11900K Processor:
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+
|
|
119
|
+
The left-most blue dot indicates serial processing with resources reuse. The black line indicates ideal scaling (zero overhead) if all frames are rendered completely independently in parallel (as is the case in all parallel matplotlib animation libraries I found). Blue dots at 1+ workers are what's implemented in this library.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## Unit tests
|
|
123
|
+
```bash
|
|
124
|
+
python -m unittest discover -s tests
|
|
125
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
parallel_animate/__init__.py,sha256=LhvDAwx0dS6zhxit2hZ7C47l7S2t4hj-p9262RxTjtY,31
|
|
2
|
+
parallel_animate/animator.py,sha256=EqzV70jqk_d1wIsrKa90FnMDWG32PcD4GiMRxQnA8Mw,13585
|
|
3
|
+
parallel_animate/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
parallel_animate/examples/multi_panel_animation.py,sha256=stUoAQn0h4Hgg0BBJQtRmXuW9sj5j-FQBjUmqYA-5mI,5127
|
|
5
|
+
parallel_animate/examples/scaling_test.py,sha256=aHvKjlzsUPGAqCWdmysllesdhJccHlcMa0cap9Z836Q,3396
|
|
6
|
+
parallel_animate/examples/simple_wave_animation.py,sha256=O_SyXltGJ5iovquwGnu4KicTucUgBZY78oDzTW-31Gc,1043
|
|
7
|
+
parallel_animate/examples/very_complex_animation.py,sha256=X37-vDlbxUGVApvRJuO9nhGpzAvf3WjCOAEKbDolXeA,24101
|
|
8
|
+
parallel_matplotlib_animation-0.1.0.dist-info/METADATA,sha256=yaUGS13TYDYFwDuWdnj4YtFZT9z5SqngfsQxhLafhPg,6211
|
|
9
|
+
parallel_matplotlib_animation-0.1.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
10
|
+
parallel_matplotlib_animation-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
11
|
+
parallel_matplotlib_animation-0.1.0.dist-info/RECORD,,
|