parallel-matplotlib-animation 0.1.2__tar.gz → 0.1.4__tar.gz

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.
Files changed (46) hide show
  1. parallel_matplotlib_animation-0.1.4/.github/workflows/docs.yml +40 -0
  2. parallel_matplotlib_animation-0.1.4/.github/workflows/publish.yml +42 -0
  3. parallel_matplotlib_animation-0.1.4/.github/workflows/tests.yml +33 -0
  4. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/.gitignore +1 -1
  5. parallel_matplotlib_animation-0.1.2/README.md → parallel_matplotlib_animation-0.1.4/PKG-INFO +38 -12
  6. parallel_matplotlib_animation-0.1.2/PKG-INFO → parallel_matplotlib_animation-0.1.4/README.md +16 -27
  7. parallel_matplotlib_animation-0.1.4/docs/api/animator.md +10 -0
  8. parallel_matplotlib_animation-0.1.4/docs/benchmark.md +41 -0
  9. parallel_matplotlib_animation-0.1.4/docs/developer.md +49 -0
  10. parallel_matplotlib_animation-0.1.4/docs/embed_example_videos.py +40 -0
  11. parallel_matplotlib_animation-0.1.4/docs/examples.md +83 -0
  12. parallel_matplotlib_animation-0.1.4/docs/gen_benchmark.py +23 -0
  13. parallel_matplotlib_animation-0.1.4/docs/index.md +76 -0
  14. parallel_matplotlib_animation-0.1.4/docs/installation.md +43 -0
  15. parallel_matplotlib_animation-0.1.4/docs/usage.md +86 -0
  16. {parallel_matplotlib_animation-0.1.2/src/parallel_animate → parallel_matplotlib_animation-0.1.4}/examples/multi_panel_animation.py +1 -1
  17. {parallel_matplotlib_animation-0.1.2/src/parallel_animate → parallel_matplotlib_animation-0.1.4}/examples/nondeterministic_video_loader.py +1 -1
  18. parallel_matplotlib_animation-0.1.4/examples/run_all_except_benchmarks.sh +47 -0
  19. parallel_matplotlib_animation-0.1.4/examples/scaling_test.py +234 -0
  20. {parallel_matplotlib_animation-0.1.2/src/parallel_animate → parallel_matplotlib_animation-0.1.4}/examples/simple_wave_animation.py +1 -1
  21. {parallel_matplotlib_animation-0.1.2/src/parallel_animate → parallel_matplotlib_animation-0.1.4}/examples/very_complex_animation.py +2 -2
  22. parallel_matplotlib_animation-0.1.4/mkdocs.yml +66 -0
  23. parallel_matplotlib_animation-0.1.4/pyproject.toml +52 -0
  24. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/src/parallel_animate/__init__.py +2 -0
  25. parallel_matplotlib_animation-0.1.4/src/parallel_animate/animator.py +587 -0
  26. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/tests/test_animator.py +112 -0
  27. parallel_matplotlib_animation-0.1.4/tests/test_examples.py +78 -0
  28. parallel_matplotlib_animation-0.1.4/tests/test_pvio_options.py +216 -0
  29. parallel_matplotlib_animation-0.1.4/uv.lock +2190 -0
  30. parallel_matplotlib_animation-0.1.2/.claude/settings.local.json +0 -12
  31. parallel_matplotlib_animation-0.1.2/assets/multi_panel_animation.gif +0 -0
  32. parallel_matplotlib_animation-0.1.2/assets/nondeterministic_video_loader.gif +0 -0
  33. parallel_matplotlib_animation-0.1.2/assets/scaling_graph.png +0 -0
  34. parallel_matplotlib_animation-0.1.2/assets/simple_wave_animation.gif +0 -0
  35. parallel_matplotlib_animation-0.1.2/assets/very_complex_animation.gif +0 -0
  36. parallel_matplotlib_animation-0.1.2/pyproject.toml +0 -26
  37. parallel_matplotlib_animation-0.1.2/src/parallel_animate/animator.py +0 -441
  38. parallel_matplotlib_animation-0.1.2/src/parallel_animate/examples/scaling_test.py +0 -96
  39. parallel_matplotlib_animation-0.1.2/tests/__init__.py +0 -0
  40. parallel_matplotlib_animation-0.1.2/tests/test_examples.py +0 -47
  41. parallel_matplotlib_animation-0.1.2/uv.lock +0 -785
  42. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/LICENSE +0 -0
  43. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/dev/mp4_to_gif.sh +0 -0
  44. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/src/parallel_animate/util.py +0 -0
  45. {parallel_matplotlib_animation-0.1.2/src/parallel_animate/examples → parallel_matplotlib_animation-0.1.4/tests}/__init__.py +0 -0
  46. {parallel_matplotlib_animation-0.1.2 → parallel_matplotlib_animation-0.1.4}/tests/test_util.py +0 -0
@@ -0,0 +1,40 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ release:
7
+ types: [published]
8
+
9
+ permissions:
10
+ contents: write
11
+
12
+ jobs:
13
+ deploy:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Install FFmpeg
22
+ run: |
23
+ sudo apt-get update
24
+ sudo apt-get install -y ffmpeg
25
+
26
+ - name: Set up uv
27
+ uses: astral-sh/setup-uv@v6
28
+ with:
29
+ enable-cache: true
30
+
31
+ - name: Install dependencies
32
+ run: uv sync --frozen --extra dev
33
+
34
+ - name: Render example videos
35
+ # The gallery MP4s are git-ignored, so render them here. The
36
+ # embed_example_videos.py MkDocs hook copies them into the built site.
37
+ run: uv run bash examples/run_all_except_benchmarks.sh
38
+
39
+ - name: Deploy docs to GitHub Pages
40
+ run: uv run mkdocs gh-deploy --force --clean
@@ -0,0 +1,42 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build distribution
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v6
16
+
17
+ - name: Build package
18
+ run: uv build
19
+
20
+ - name: Upload dist artifacts
21
+ uses: actions/upload-artifact@v4
22
+ with:
23
+ name: dist
24
+ path: dist/
25
+
26
+ publish:
27
+ name: Publish to PyPI
28
+ needs: build
29
+ runs-on: ubuntu-latest
30
+ environment: pypi-nely
31
+ permissions:
32
+ id-token: write # required for OIDC trusted publisher
33
+
34
+ steps:
35
+ - name: Download dist artifacts
36
+ uses: actions/download-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ - name: Publish to PyPI
42
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,33 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: ["main", "dev-*"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install FFmpeg
21
+ run: sudo apt-get update && sudo apt-get install -y ffmpeg
22
+
23
+ - name: Set up uv
24
+ uses: astral-sh/setup-uv@v6
25
+ with:
26
+ enable-cache: true
27
+ python-version: ${{ matrix.python-version }}
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --frozen --extra dev
31
+
32
+ - name: Run tests
33
+ run: uv run pytest tests
@@ -1,4 +1,4 @@
1
- example_output/
1
+ output/
2
2
 
3
3
  ######################################################################
4
4
 
@@ -1,3 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: parallel-matplotlib-animation
3
+ Version: 0.1.4
4
+ Summary: Animate matplotlib figures into videos, in parallel but with efficient caching
5
+ Project-URL: Repository, https://github.com/nely-epfl/parallel-matplotlib-animation
6
+ Author-email: Sibo Wang-Chen <sibo.wang@epfl.ch>
7
+ License-File: LICENSE
8
+ Requires-Python: <3.15,>=3.10
9
+ Requires-Dist: matplotlib>=3.10.0
10
+ Requires-Dist: numpy<3,>=2
11
+ Requires-Dist: parallel-video-io==0.1.9
12
+ Requires-Dist: tqdm>=4.67
13
+ Provides-Extra: benchmark
14
+ Requires-Dist: pandas<3.0,>=2.0; extra == 'benchmark'
15
+ Requires-Dist: plotly<7.0,>=6.8; extra == 'benchmark'
16
+ Provides-Extra: dev
17
+ Requires-Dist: mkdocs-material<10.0,>=9.5; extra == 'dev'
18
+ Requires-Dist: mkdocstrings[python]>=0.29; extra == 'dev'
19
+ Requires-Dist: pytest<10.0,>=9.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.15; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
1
23
  # parallel-matplotlib-animation
2
24
 
3
25
  Create matplotlib animations rendered to video in parallel, with efficient resources reuse.
@@ -21,7 +43,7 @@ Renders matplotlib animations by:
21
43
  1. Creating a bunch of worker processes, and creating matplotlib resources (plt.Figure, plt.Axes, artists, etc.) once per worker
22
44
  2. Distributing frames across workers via a dynamic queue
23
45
  3. Rendering the assigned frames from each worker, but updating the data only (without redrawing the whole plot from scratch)
24
- 4. Encoding frames to video with PyAV (very efficient FFmpeg under the hood)
46
+ 4. Encoding frames to video with [parallel-video-io](https://github.com/sibocw/parallel-video-io) (FFmpeg under the hood, with automatic GPU/NVENC acceleration when available)
25
47
 
26
48
  **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:
27
49
  - Serial processing: avoids the overhead of recreating complex layouts for every frame
@@ -37,7 +59,7 @@ from parallel_animate import Animator
37
59
  # Step 1: Create a child class of parallel_animate.Animator
38
60
  class WaveAnimation(Animator):
39
61
 
40
- # Step 2: Define how the plot should be setup
62
+ # Step 2: Define how the plot should be set up
41
63
  def setup(self):
42
64
  fig, ax = plt.subplots()
43
65
  self.x = np.linspace(0, 4 * np.pi, 200)
@@ -58,12 +80,12 @@ class WaveAnimation(Animator):
58
80
  # Step 4: Define a list of input parameters, one for each frame
59
81
  params = [{"phase": 2 * np.pi * i / 60} for i in range(60)]
60
82
 
61
- # Step 5: Make video in parallel
83
+ # Step 5: Make the video in parallel
62
84
  anim = WaveAnimation()
63
85
  anim.make_video("wave.mp4", param_by_frame=params, fps=30, num_workers=4)
64
86
  ```
65
87
 
66
- <img src="assets/simple_wave_animation.gif" width="480"/>
88
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/simple_wave_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
67
89
 
68
90
  ## Usage
69
91
  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:
@@ -79,37 +101,41 @@ Once you have defined your animator class, there is a single method that you nee
79
101
  - `fps` (int): Frame rate of the output video
80
102
  - `n_frames` (int or None): Number of frames to render. If None, use the length of `param_by_frame`. If param_by_frame does not have `__len__` implemented and `n_frames` is None, the progress bar won't show completion percentage.
81
103
  - `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.
82
- - See the docstring for `parallel_animate.animator` directly for less commonly used, optional parameters. These control logging, rendering quality, etc.
104
+ - `video_mode` (str): Encoder selection passed to parallel-video-io: `"auto"` (default) uses the GPU encoder (FFmpeg/NVENC) when a CUDA device is available and falls back to CPU (libx264) otherwise; `"gpu"` forces NVENC and `"cpu"` forces libx264. Output is always an H.264 MP4.
105
+ - `video_quality` (int or None), `video_preset` (str or None), `video_extra_ffmpeg_params` (list of str or None): Optional encoding-quality controls forwarded to parallel-video-io. Leave as `None` to use its sensible defaults.
106
+ - See the docstring for `parallel_animate.animator` directly for the remaining less commonly used, optional parameters. These control logging, figure reuse, prefetching, etc.
107
+
108
+ > **Note:** parallel-video-io is currently Linux-only.
83
109
 
84
110
  ### Special case: frame params arriving out-of-order in `param_by_frame`
85
111
  In some cases, frames in `param_by_frame` might be out of order. We can handle these scenarios by populating `param_by_frame` with a special `parallel_animate.IndexedFrameParams` dataclass, which specifies the frame index that overrides the ordering in `param_by_frame`. This can be useful when, for example, the animator needs to draw frames that are decoded from a video, and the dataloader for that video might return frames in nondeterministic order because it's parallelized.
86
112
 
87
- See `src/parallel_animate/examples/nondeterministic_video_loader.py` for details.
113
+ See `examples/nondeterministic_video_loader.py` for details.
88
114
 
89
115
  ## Examples
90
116
 
91
- See [`src/parallel_animate/examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/src/parallel_animate/examples/):
117
+ See [`examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/examples/). Run all of them (except the benchmark) with `./examples/run_all.sh`.
92
118
 
93
119
  `simple_wave_animation.py`: The example above
94
120
 
95
121
  `multi_panel_animation.py`: 5 subplots with different plot types
96
122
 
97
- <img src="assets/multi_panel_animation.gif" width="480"/>
123
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/multi_panel_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
98
124
 
99
125
  `very_complex_animation.py`: 14 subplots with GridSpec layout
100
126
 
101
- <img src="assets/very_complex_animation.gif" width="480"/>
127
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/very_complex_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
102
128
 
103
129
  `nondeterministic_video_loader.py`: handling frames that arrive out of order
104
130
 
105
- <img src="assets/nondeterministic_video_loader.gif" width="240"/>
131
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/nondeterministic_video_loader.mp4" width="240" autoplay loop muted playsinline controls></video>
106
132
 
107
133
 
108
134
  ## Performance test
109
135
 
110
- 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:
136
+ A [strong scaling test](https://hpc-wiki.info/hpc/Scaling_tests#Strong_Scaling) is implemented in `examples/scaling_test.py`. Here's the result on my 8-core (16-thread) Intel Core i9-11900K Processor:
111
137
 
112
- ![](assets/scaling_graph.png)
138
+ See the [interactive scaling figure](https://sibocw.github.io/parallel-matplotlib-animation/benchmark/) on the documentation site.
113
139
 
114
140
  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.
115
141
 
@@ -1,18 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: parallel-matplotlib-animation
3
- Version: 0.1.2
4
- Summary: Animate matplotlib figures into videos, in parallel but with efficient caching
5
- Project-URL: Repository, https://github.com/sibocw/parallel-matplotlib-animation
6
- Author-email: Sibo Wang-Chen <sibo.wang@epfl.ch>
7
- License-File: LICENSE
8
- Requires-Python: >=3.10
9
- Requires-Dist: av>=14.0
10
- Requires-Dist: matplotlib>=3.10.0
11
- Requires-Dist: numpy<3,>=2
12
- Requires-Dist: pillow>=11.0
13
- Requires-Dist: tqdm>=4.67
14
- Description-Content-Type: text/markdown
15
-
16
1
  # parallel-matplotlib-animation
17
2
 
18
3
  Create matplotlib animations rendered to video in parallel, with efficient resources reuse.
@@ -36,7 +21,7 @@ Renders matplotlib animations by:
36
21
  1. Creating a bunch of worker processes, and creating matplotlib resources (plt.Figure, plt.Axes, artists, etc.) once per worker
37
22
  2. Distributing frames across workers via a dynamic queue
38
23
  3. Rendering the assigned frames from each worker, but updating the data only (without redrawing the whole plot from scratch)
39
- 4. Encoding frames to video with PyAV (very efficient FFmpeg under the hood)
24
+ 4. Encoding frames to video with [parallel-video-io](https://github.com/sibocw/parallel-video-io) (FFmpeg under the hood, with automatic GPU/NVENC acceleration when available)
40
25
 
41
26
  **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:
42
27
  - Serial processing: avoids the overhead of recreating complex layouts for every frame
@@ -52,7 +37,7 @@ from parallel_animate import Animator
52
37
  # Step 1: Create a child class of parallel_animate.Animator
53
38
  class WaveAnimation(Animator):
54
39
 
55
- # Step 2: Define how the plot should be setup
40
+ # Step 2: Define how the plot should be set up
56
41
  def setup(self):
57
42
  fig, ax = plt.subplots()
58
43
  self.x = np.linspace(0, 4 * np.pi, 200)
@@ -73,12 +58,12 @@ class WaveAnimation(Animator):
73
58
  # Step 4: Define a list of input parameters, one for each frame
74
59
  params = [{"phase": 2 * np.pi * i / 60} for i in range(60)]
75
60
 
76
- # Step 5: Make video in parallel
61
+ # Step 5: Make the video in parallel
77
62
  anim = WaveAnimation()
78
63
  anim.make_video("wave.mp4", param_by_frame=params, fps=30, num_workers=4)
79
64
  ```
80
65
 
81
- <img src="assets/simple_wave_animation.gif" width="480"/>
66
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/simple_wave_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
82
67
 
83
68
  ## Usage
84
69
  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:
@@ -94,37 +79,41 @@ Once you have defined your animator class, there is a single method that you nee
94
79
  - `fps` (int): Frame rate of the output video
95
80
  - `n_frames` (int or None): Number of frames to render. If None, use the length of `param_by_frame`. If param_by_frame does not have `__len__` implemented and `n_frames` is None, the progress bar won't show completion percentage.
96
81
  - `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.
97
- - See the docstring for `parallel_animate.animator` directly for less commonly used, optional parameters. These control logging, rendering quality, etc.
82
+ - `video_mode` (str): Encoder selection passed to parallel-video-io: `"auto"` (default) uses the GPU encoder (FFmpeg/NVENC) when a CUDA device is available and falls back to CPU (libx264) otherwise; `"gpu"` forces NVENC and `"cpu"` forces libx264. Output is always an H.264 MP4.
83
+ - `video_quality` (int or None), `video_preset` (str or None), `video_extra_ffmpeg_params` (list of str or None): Optional encoding-quality controls forwarded to parallel-video-io. Leave as `None` to use its sensible defaults.
84
+ - See the docstring for `parallel_animate.animator` directly for the remaining less commonly used, optional parameters. These control logging, figure reuse, prefetching, etc.
85
+
86
+ > **Note:** parallel-video-io is currently Linux-only.
98
87
 
99
88
  ### Special case: frame params arriving out-of-order in `param_by_frame`
100
89
  In some cases, frames in `param_by_frame` might be out of order. We can handle these scenarios by populating `param_by_frame` with a special `parallel_animate.IndexedFrameParams` dataclass, which specifies the frame index that overrides the ordering in `param_by_frame`. This can be useful when, for example, the animator needs to draw frames that are decoded from a video, and the dataloader for that video might return frames in nondeterministic order because it's parallelized.
101
90
 
102
- See `src/parallel_animate/examples/nondeterministic_video_loader.py` for details.
91
+ See `examples/nondeterministic_video_loader.py` for details.
103
92
 
104
93
  ## Examples
105
94
 
106
- See [`src/parallel_animate/examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/src/parallel_animate/examples/):
95
+ See [`examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/examples/). Run all of them (except the benchmark) with `./examples/run_all.sh`.
107
96
 
108
97
  `simple_wave_animation.py`: The example above
109
98
 
110
99
  `multi_panel_animation.py`: 5 subplots with different plot types
111
100
 
112
- <img src="assets/multi_panel_animation.gif" width="480"/>
101
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/multi_panel_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
113
102
 
114
103
  `very_complex_animation.py`: 14 subplots with GridSpec layout
115
104
 
116
- <img src="assets/very_complex_animation.gif" width="480"/>
105
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/very_complex_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
117
106
 
118
107
  `nondeterministic_video_loader.py`: handling frames that arrive out of order
119
108
 
120
- <img src="assets/nondeterministic_video_loader.gif" width="240"/>
109
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/nondeterministic_video_loader.mp4" width="240" autoplay loop muted playsinline controls></video>
121
110
 
122
111
 
123
112
  ## Performance test
124
113
 
125
- 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:
114
+ A [strong scaling test](https://hpc-wiki.info/hpc/Scaling_tests#Strong_Scaling) is implemented in `examples/scaling_test.py`. Here's the result on my 8-core (16-thread) Intel Core i9-11900K Processor:
126
115
 
127
- ![](assets/scaling_graph.png)
116
+ See the [interactive scaling figure](https://sibocw.github.io/parallel-matplotlib-animation/benchmark/) on the documentation site.
128
117
 
129
118
  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.
130
119
 
@@ -0,0 +1,10 @@
1
+ # `parallel_animate` — API Reference
2
+
3
+ The public API consists of the `Animator` base class and the
4
+ `IndexedFrameParams` helper, both importable directly from `parallel_animate`.
5
+
6
+ ::: parallel_animate.animator
7
+ options:
8
+ members:
9
+ - Animator
10
+ - IndexedFrameParams
@@ -0,0 +1,41 @@
1
+ # Benchmark
2
+
3
+ A [strong-scaling test](https://hpc-wiki.info/hpc/Scaling_tests#Strong_Scaling)
4
+ measures how render-to-video throughput scales with the number of worker
5
+ processes. It sweeps:
6
+
7
+ - **worker count** — 1, 2, 4, 8, 16;
8
+ - **figure reuse** — with vs without (`reuse_figure_object`); and
9
+ - **encoding backend** — parallel-video-io's CPU (libx264) and GPU (NVENC)
10
+ encoders. The GPU backend is benchmarked only when a CUDA device is present.
11
+
12
+ The animation rendered is
13
+ [`very_complex_animation.py`](examples.md#very_complex_animationpy), a 14-subplot
14
+ `GridSpec` figure that is expensive to build — exactly the case where the
15
+ setup-once / update-many design pays off.
16
+
17
+ ## How to run
18
+
19
+ ```bash
20
+ pip install -e '.[benchmark]'
21
+ python examples/scaling_test.py # full sweep
22
+ python examples/scaling_test.py --quick # small, fast smoke run
23
+ ```
24
+
25
+ Results are written to `examples/output/scaling_test/` (`results.csv`,
26
+ `results.json`, and the interactive figure embedded below as
27
+ `scaling_graph.html`).
28
+
29
+ ## Result
30
+
31
+ Speedup is normalised to the serial (one worker), no-reuse, CPU-encode baseline,
32
+ so the curves capture both the parallel speedup and the extra gains from figure
33
+ reuse and GPU encoding. The dashed black line is ideal (zero-overhead) linear
34
+ scaling.
35
+
36
+ --8<-- "examples/output/scaling_test/scaling_graph.html"
37
+
38
+ The left-most dark-blue point is serial processing with figure reuse. Points at
39
+ 2+ workers show the parallel speedup; reuse curves (darker) sit above the
40
+ no-reuse curves (lighter) because the expensive layout is built once per worker
41
+ instead of once per frame.
@@ -0,0 +1,49 @@
1
+ # Developer Info
2
+
3
+ Clone the repository and install the development extra:
4
+
5
+ ```bash
6
+ git clone https://github.com/sibocw/parallel-matplotlib-animation.git
7
+ cd parallel-matplotlib-animation
8
+ pip install -e '.[dev]'
9
+ ```
10
+
11
+ The `dev` extra installs everything needed for testing, formatting, and building
12
+ the documentation site.
13
+
14
+ ## Testing
15
+
16
+ The test suite uses pytest. Run it from the repository root:
17
+
18
+ ```bash
19
+ pytest
20
+ ```
21
+
22
+ A few tests write small MP4 files via parallel-video-io / FFmpeg, so make sure
23
+ `ffmpeg` is on your `$PATH`.
24
+
25
+ ## Documentation
26
+
27
+ The docs are built with [MkDocs](https://www.mkdocs.org/) and the Material theme,
28
+ with API pages generated from docstrings by
29
+ [mkdocstrings](https://mkdocstrings.github.io/).
30
+
31
+ ```bash
32
+ mkdocs serve # live-preview at http://127.0.0.1:8000
33
+ mkdocs build # build the static site into site/
34
+ mkdocs gh-deploy # build and push to the gh-pages branch
35
+ ```
36
+
37
+ The [benchmark page](benchmark.md) embeds
38
+ `examples/output/scaling_test/scaling_graph.html`, produced
39
+ by `python examples/scaling_test.py`. If that file is missing at build time, a
40
+ placeholder is substituted by the `docs/gen_benchmark.py` hook so the build still
41
+ succeeds.
42
+
43
+ ## Code style
44
+
45
+ - Follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html).
46
+ - Use ASCII-only characters in `.py` docstrings and comments.
47
+ - Format with [ruff](https://docs.astral.sh/ruff/).
48
+ - On a new GitHub release, the package is published to PyPI and the docs are
49
+ deployed to GitHub Pages automatically.
@@ -0,0 +1,40 @@
1
+ """MkDocs hook: publish the gallery example videos into the built site.
2
+
3
+ The example scripts render their animations to ``examples/output/`` (git-ignored,
4
+ so the MP4s are not committed to ``main``/``dev`` branches). The docs embed them
5
+ via ``<video>`` tags pointing at ``output/<name>.mp4``. This hook copies the
6
+ gallery videos straight from ``examples/output/`` into the built site so they are
7
+ published to the ``gh-pages`` branch -- and only there -- by ``mkdocs gh-deploy``.
8
+
9
+ Only the named gallery videos are copied (not, e.g., the large benchmark
10
+ artifacts that ``scaling_test.py`` writes alongside them). Missing videos are
11
+ skipped with a warning so the build still succeeds on a fresh checkout where the
12
+ examples have not been rendered yet.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import shutil
19
+ from pathlib import Path
20
+
21
+ log = logging.getLogger("mkdocs.hooks.embed_example_videos")
22
+
23
+ _SOURCE_DIR = Path("examples/output")
24
+ _GALLERY_VIDEOS = (
25
+ "simple_wave_animation.mp4",
26
+ "multi_panel_animation.mp4",
27
+ "very_complex_animation.mp4",
28
+ "nondeterministic_video_loader.mp4",
29
+ )
30
+
31
+
32
+ def on_post_build(config): # noqa: ARG001
33
+ dest_dir = Path(config["site_dir"]) / "output"
34
+ dest_dir.mkdir(parents=True, exist_ok=True)
35
+ for name in _GALLERY_VIDEOS:
36
+ src = _SOURCE_DIR / name
37
+ if src.exists():
38
+ shutil.copy2(src, dest_dir / name)
39
+ else:
40
+ log.warning("Example video missing, not published: %s", src)
@@ -0,0 +1,83 @@
1
+ # Examples
2
+
3
+ Runnable example scripts live in [`examples/`](https://github.com/sibocw/parallel-matplotlib-animation/tree/main/examples).
4
+
5
+ ---
6
+
7
+ ## `simple_wave_animation.py`
8
+
9
+ A single oscillating cosine wave — the [quick example](index.md#quick-example).
10
+
11
+ <div style="text-align:center;margin:1em 0;">
12
+ <a href="https://github.com/NeLy-EPFL/parallel-matplotlib-animation/blob/main/examples/simple_wave_animation.py" target="_blank" rel="noopener"
13
+ style="display:inline-block;padding:0.25em 0.9em;font-size:0.8em;font-weight:400;background:var(--md-primary-fg-color);color:var(--md-primary-bg-color);border-radius:6px;text-decoration:none;">
14
+ See source code ↗</a>
15
+ </div>
16
+
17
+ <div style="text-align:center;margin:1em 0;">
18
+ <video src="../output/simple_wave_animation.mp4" autoplay loop muted playsinline controls
19
+ style="width:100%;border-radius:6px;">
20
+ Your browser does not support the video tag.
21
+ </video>
22
+ </div>
23
+
24
+ ---
25
+
26
+ ## `multi_panel_animation.py`
27
+
28
+ Five subplots with different plot types updating together.
29
+
30
+ <div style="text-align:center;margin:1em 0;">
31
+ <a href="https://github.com/NeLy-EPFL/parallel-matplotlib-animation/blob/main/examples/http://multi_panel_animation.py" target="_blank" rel="noopener"
32
+ style="display:inline-block;padding:0.25em 0.9em;font-size:0.8em;font-weight:400;background:var(--md-primary-fg-color);color:var(--md-primary-bg-color);border-radius:6px;text-decoration:none;">
33
+ See source code ↗</a>
34
+ </div>
35
+
36
+ <div style="text-align:center;margin:1em 0;">
37
+ <video src="../output/multi_panel_animation.mp4" autoplay loop muted playsinline controls
38
+ style="width:100%;border-radius:6px;">
39
+ Your browser does not support the video tag.
40
+ </video>
41
+ </div>
42
+
43
+ ---
44
+
45
+ ## `very_complex_animation.py`
46
+
47
+ A 14-subplot `GridSpec` layout — the figure used by the
48
+ [benchmark](benchmark.md). It showcases how much the setup-once / update-many
49
+ design saves when the layout is expensive to build.
50
+
51
+ <div style="text-align:center;margin:1em 0;">
52
+ <a href="https://github.com/NeLy-EPFL/parallel-matplotlib-animation/blob/main/examples/very_complex_animation.py" target="_blank" rel="noopener"
53
+ style="display:inline-block;padding:0.25em 0.9em;font-size:0.8em;font-weight:400;background:var(--md-primary-fg-color);color:var(--md-primary-bg-color);border-radius:6px;text-decoration:none;">
54
+ See source code ↗</a>
55
+ </div>
56
+
57
+ <div style="text-align:center;margin:1em 0;">
58
+ <video src="../output/very_complex_animation.mp4" autoplay loop muted playsinline controls
59
+ style="width:100%;border-radius:6px;">
60
+ Your browser does not support the video tag.
61
+ </video>
62
+ </div>
63
+
64
+ ---
65
+
66
+ ## `nondeterministic_video_loader.py`
67
+
68
+ Handling frames whose parameters arrive out of order, using
69
+ [`IndexedFrameParams`](api/animator.md). Useful when frames are decoded by a
70
+ parallel dataloader that returns them non-deterministically.
71
+
72
+ <div style="text-align:center;margin:1em 0;">
73
+ <a href="https://github.com/NeLy-EPFL/parallel-matplotlib-animation/blob/main/examples/nondeterministic_video_loader.py" target="_blank" rel="noopener"
74
+ style="display:inline-block;padding:0.25em 0.9em;font-size:0.8em;font-weight:400;background:var(--md-primary-fg-color);color:var(--md-primary-bg-color);border-radius:6px;text-decoration:none;">
75
+ See source code ↗</a>
76
+ </div>
77
+
78
+ <div style="text-align:center;margin:1em 0;">
79
+ <video src="../output/nondeterministic_video_loader.mp4" autoplay loop muted playsinline controls
80
+ style="width:100%;border-radius:6px;">
81
+ Your browser does not support the video tag.
82
+ </video>
83
+ </div>
@@ -0,0 +1,23 @@
1
+ """MkDocs hook: ensure the benchmark figure exists before the docs build.
2
+
3
+ The strong-scaling figure is generated by ``examples/scaling_test.py`` and
4
+ written to ``examples/output/scaling_test/scaling_graph.html``, then embedded
5
+ into ``benchmark.md`` via
6
+ pymdownx.snippets. When the benchmark has not been run (e.g. on a fresh CI
7
+ checkout) the file is missing and the build would fail, so this hook writes a
8
+ placeholder in its place.
9
+ """
10
+
11
+ from pathlib import Path
12
+
13
+ _FIGURE = Path("examples/output/scaling_test/scaling_graph.html")
14
+ _PLACEHOLDER = (
15
+ "<p><em>Run <code>python examples/scaling_test.py</code> to generate this "
16
+ "figure.</em></p>\n"
17
+ )
18
+
19
+
20
+ def on_pre_build(config): # noqa: ARG001
21
+ if not _FIGURE.exists():
22
+ _FIGURE.parent.mkdir(parents=True, exist_ok=True)
23
+ _FIGURE.write_text(_PLACEHOLDER)