parallel-matplotlib-animation 0.1.1__tar.gz → 0.1.3__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 (38) hide show
  1. parallel_matplotlib_animation-0.1.3/.github/workflows/docs.yml +40 -0
  2. parallel_matplotlib_animation-0.1.3/.github/workflows/publish.yml +42 -0
  3. parallel_matplotlib_animation-0.1.3/.github/workflows/tests.yml +33 -0
  4. parallel_matplotlib_animation-0.1.3/.gitignore +308 -0
  5. parallel_matplotlib_animation-0.1.1/README.md → parallel_matplotlib_animation-0.1.3/PKG-INFO +54 -12
  6. parallel_matplotlib_animation-0.1.1/PKG-INFO → parallel_matplotlib_animation-0.1.3/README.md +33 -35
  7. parallel_matplotlib_animation-0.1.3/dev/mp4_to_gif.sh +31 -0
  8. parallel_matplotlib_animation-0.1.3/docs/api/animator.md +10 -0
  9. parallel_matplotlib_animation-0.1.3/docs/benchmark.md +41 -0
  10. parallel_matplotlib_animation-0.1.3/docs/developer.md +49 -0
  11. parallel_matplotlib_animation-0.1.3/docs/embed_example_videos.py +40 -0
  12. parallel_matplotlib_animation-0.1.3/docs/examples.md +83 -0
  13. parallel_matplotlib_animation-0.1.3/docs/gen_benchmark.py +23 -0
  14. parallel_matplotlib_animation-0.1.3/docs/index.md +76 -0
  15. parallel_matplotlib_animation-0.1.3/docs/installation.md +43 -0
  16. parallel_matplotlib_animation-0.1.3/docs/usage.md +86 -0
  17. {parallel_matplotlib_animation-0.1.1/src/parallel_animate → parallel_matplotlib_animation-0.1.3}/examples/multi_panel_animation.py +1 -1
  18. parallel_matplotlib_animation-0.1.3/examples/nondeterministic_video_loader.py +46 -0
  19. parallel_matplotlib_animation-0.1.3/examples/run_all_except_benchmarks.sh +47 -0
  20. parallel_matplotlib_animation-0.1.3/examples/scaling_test.py +234 -0
  21. {parallel_matplotlib_animation-0.1.1/src/parallel_animate → parallel_matplotlib_animation-0.1.3}/examples/simple_wave_animation.py +1 -1
  22. {parallel_matplotlib_animation-0.1.1/src/parallel_animate → parallel_matplotlib_animation-0.1.3}/examples/very_complex_animation.py +2 -2
  23. parallel_matplotlib_animation-0.1.3/mkdocs.yml +66 -0
  24. parallel_matplotlib_animation-0.1.3/pyproject.toml +52 -0
  25. parallel_matplotlib_animation-0.1.3/src/parallel_animate/__init__.py +3 -0
  26. parallel_matplotlib_animation-0.1.3/src/parallel_animate/animator.py +587 -0
  27. parallel_matplotlib_animation-0.1.3/src/parallel_animate/util.py +64 -0
  28. parallel_matplotlib_animation-0.1.3/tests/test_animator.py +633 -0
  29. parallel_matplotlib_animation-0.1.3/tests/test_examples.py +78 -0
  30. parallel_matplotlib_animation-0.1.3/tests/test_pvio_options.py +216 -0
  31. parallel_matplotlib_animation-0.1.3/tests/test_util.py +56 -0
  32. parallel_matplotlib_animation-0.1.3/uv.lock +2190 -0
  33. parallel_matplotlib_animation-0.1.1/pyproject.toml +0 -26
  34. parallel_matplotlib_animation-0.1.1/src/parallel_animate/__init__.py +0 -1
  35. parallel_matplotlib_animation-0.1.1/src/parallel_animate/animator.py +0 -387
  36. parallel_matplotlib_animation-0.1.1/src/parallel_animate/examples/scaling_test.py +0 -96
  37. {parallel_matplotlib_animation-0.1.1 → parallel_matplotlib_animation-0.1.3}/LICENSE +0 -0
  38. {parallel_matplotlib_animation-0.1.1/src/parallel_animate/examples → parallel_matplotlib_animation-0.1.3/tests}/__init__.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
@@ -0,0 +1,308 @@
1
+ output/
2
+
3
+ ######################################################################
4
+
5
+ # Created by https://www.toptal.com/developers/gitignore/api/python,linux,macos,windows,git,vim,visualstudiocode
6
+ # Edit at https://www.toptal.com/developers/gitignore?templates=python,linux,macos,windows,git,vim,visualstudiocode
7
+
8
+ ### Git ###
9
+ # Created by git for backups. To disable backups in Git:
10
+ # $ git config --global mergetool.keepBackup false
11
+ *.orig
12
+
13
+ # Created by git when using merge tools for conflicts
14
+ *.BACKUP.*
15
+ *.BASE.*
16
+ *.LOCAL.*
17
+ *.REMOTE.*
18
+ *_BACKUP_*.txt
19
+ *_BASE_*.txt
20
+ *_LOCAL_*.txt
21
+ *_REMOTE_*.txt
22
+
23
+ ### Linux ###
24
+ *~
25
+
26
+ # temporary files which can be created if a process still has a handle open of a deleted file
27
+ .fuse_hidden*
28
+
29
+ # KDE directory preferences
30
+ .directory
31
+
32
+ # Linux trash folder which might appear on any partition or disk
33
+ .Trash-*
34
+
35
+ # .nfs files are created when an open file is removed but is still being accessed
36
+ .nfs*
37
+
38
+ ### macOS ###
39
+ # General
40
+ .DS_Store
41
+ .AppleDouble
42
+ .LSOverride
43
+
44
+ # Icon must end with two \r
45
+ Icon
46
+
47
+
48
+ # Thumbnails
49
+ ._*
50
+
51
+ # Files that might appear in the root of a volume
52
+ .DocumentRevisions-V100
53
+ .fseventsd
54
+ .Spotlight-V100
55
+ .TemporaryItems
56
+ .Trashes
57
+ .VolumeIcon.icns
58
+ .com.apple.timemachine.donotpresent
59
+
60
+ # Directories potentially created on remote AFP share
61
+ .AppleDB
62
+ .AppleDesktop
63
+ Network Trash Folder
64
+ Temporary Items
65
+ .apdisk
66
+
67
+ ### macOS Patch ###
68
+ # iCloud generated files
69
+ *.icloud
70
+
71
+ ### Python ###
72
+ # Byte-compiled / optimized / DLL files
73
+ __pycache__/
74
+ *.py[cod]
75
+ *$py.class
76
+
77
+ # C extensions
78
+ *.so
79
+
80
+ # Distribution / packaging
81
+ .Python
82
+ build/
83
+ develop-eggs/
84
+ dist/
85
+ downloads/
86
+ eggs/
87
+ .eggs/
88
+ lib/
89
+ lib64/
90
+ parts/
91
+ sdist/
92
+ var/
93
+ wheels/
94
+ share/python-wheels/
95
+ *.egg-info/
96
+ .installed.cfg
97
+ *.egg
98
+ MANIFEST
99
+
100
+ # PyInstaller
101
+ # Usually these files are written by a python script from a template
102
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
103
+ *.manifest
104
+ *.spec
105
+
106
+ # Installer logs
107
+ pip-log.txt
108
+ pip-delete-this-directory.txt
109
+
110
+ # Unit test / coverage reports
111
+ htmlcov/
112
+ .tox/
113
+ .nox/
114
+ .coverage
115
+ .coverage.*
116
+ .cache
117
+ nosetests.xml
118
+ coverage.xml
119
+ *.cover
120
+ *.py,cover
121
+ .hypothesis/
122
+ .pytest_cache/
123
+ cover/
124
+
125
+ # Translations
126
+ *.mo
127
+ *.pot
128
+
129
+ # Django stuff:
130
+ *.log
131
+ local_settings.py
132
+ db.sqlite3
133
+ db.sqlite3-journal
134
+
135
+ # Flask stuff:
136
+ instance/
137
+ .webassets-cache
138
+
139
+ # Scrapy stuff:
140
+ .scrapy
141
+
142
+ # Sphinx documentation
143
+ docs/_build/
144
+
145
+ # PyBuilder
146
+ .pybuilder/
147
+ target/
148
+
149
+ # Jupyter Notebook
150
+ .ipynb_checkpoints
151
+
152
+ # IPython
153
+ profile_default/
154
+ ipython_config.py
155
+
156
+ # pyenv
157
+ # For a library or package, you might want to ignore these files since the code is
158
+ # intended to run in multiple environments; otherwise, check them in:
159
+ # .python-version
160
+
161
+ # pipenv
162
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
163
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
164
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
165
+ # install all needed dependencies.
166
+ #Pipfile.lock
167
+
168
+ # poetry
169
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
170
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
171
+ # commonly ignored for libraries.
172
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
173
+ #poetry.lock
174
+
175
+ # pdm
176
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
177
+ #pdm.lock
178
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
179
+ # in version control.
180
+ # https://pdm.fming.dev/#use-with-ide
181
+ .pdm.toml
182
+
183
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
184
+ __pypackages__/
185
+
186
+ # Celery stuff
187
+ celerybeat-schedule
188
+ celerybeat.pid
189
+
190
+ # SageMath parsed files
191
+ *.sage.py
192
+
193
+ # Environments
194
+ .env
195
+ .venv
196
+ env/
197
+ venv/
198
+ ENV/
199
+ env.bak/
200
+ venv.bak/
201
+
202
+ # Spyder project settings
203
+ .spyderproject
204
+ .spyproject
205
+
206
+ # Rope project settings
207
+ .ropeproject
208
+
209
+ # mkdocs documentation
210
+ /site
211
+
212
+ # mypy
213
+ .mypy_cache/
214
+ .dmypy.json
215
+ dmypy.json
216
+
217
+ # Pyre type checker
218
+ .pyre/
219
+
220
+ # pytype static type analyzer
221
+ .pytype/
222
+
223
+ # Cython debug symbols
224
+ cython_debug/
225
+
226
+ # PyCharm
227
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
228
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
229
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
230
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
231
+ #.idea/
232
+
233
+ ### Python Patch ###
234
+ # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
235
+ poetry.toml
236
+
237
+ # ruff
238
+ .ruff_cache/
239
+
240
+ # LSP config files
241
+ pyrightconfig.json
242
+
243
+ ### Vim ###
244
+ # Swap
245
+ [._]*.s[a-v][a-z]
246
+ !*.svg # comment out if you don't need vector files
247
+ [._]*.sw[a-p]
248
+ [._]s[a-rt-v][a-z]
249
+ [._]ss[a-gi-z]
250
+ [._]sw[a-p]
251
+
252
+ # Session
253
+ Session.vim
254
+ Sessionx.vim
255
+
256
+ # Temporary
257
+ .netrwhist
258
+ # Auto-generated tag files
259
+ tags
260
+ # Persistent undo
261
+ [._]*.un~
262
+
263
+ ### VisualStudioCode ###
264
+ .vscode/*
265
+ !.vscode/settings.json
266
+ !.vscode/tasks.json
267
+ !.vscode/launch.json
268
+ !.vscode/extensions.json
269
+ !.vscode/*.code-snippets
270
+
271
+ # Local History for Visual Studio Code
272
+ .history/
273
+
274
+ # Built Visual Studio Code Extensions
275
+ *.vsix
276
+
277
+ ### VisualStudioCode Patch ###
278
+ # Ignore all local history of files
279
+ .history
280
+ .ionide
281
+
282
+ ### Windows ###
283
+ # Windows thumbnail cache files
284
+ Thumbs.db
285
+ Thumbs.db:encryptable
286
+ ehthumbs.db
287
+ ehthumbs_vista.db
288
+
289
+ # Dump file
290
+ *.stackdump
291
+
292
+ # Folder config file
293
+ [Dd]esktop.ini
294
+
295
+ # Recycle Bin used on file shares
296
+ $RECYCLE.BIN/
297
+
298
+ # Windows Installer files
299
+ *.cab
300
+ *.msi
301
+ *.msix
302
+ *.msm
303
+ *.msp
304
+
305
+ # Windows shortcuts
306
+ *.lnk
307
+
308
+ # End of https://www.toptal.com/developers/gitignore/api/python,linux,macos,windows,git,vim,visualstudiocode
@@ -1,3 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: parallel-matplotlib-animation
3
+ Version: 0.1.3
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.8
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
- ![](assets/simple_wave_animation.gif)
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:
@@ -75,25 +97,45 @@ This library has a single class: `parallel_animate.Animator`. To make an animati
75
97
  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:
76
98
 
77
99
  - `output_file` (Path or str): Output video path
78
- - `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
+ - `param_by_frame` (Iterable): Iterable of parameters. Each element is the `params` argument to be given to the `.update` call for the corresponding frame. Can be a list, tuple, generator, or any other iterable. Using generators is particularly useful for large data (e.g., bitmaps) to avoid loading everything into memory at once.
79
101
  - `fps` (int): Frame rate of the output video
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.
80
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.
81
- - 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.
109
+
110
+ ### Special case: frame params arriving out-of-order in `param_by_frame`
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.
82
112
 
113
+ See `examples/nondeterministic_video_loader.py` for details.
83
114
 
84
115
  ## Examples
85
116
 
86
- See [`src/parallel_animate/examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/src/parallel_animate/examples/):
87
- - `simple_wave_animation.py`: The example above
88
- - `multi_panel_animation.py`: 5 subplots with different plot types
89
- - `very_complex_animation.py`: 14 subplots with GridSpec layout
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`.
118
+
119
+ `simple_wave_animation.py`: The example above
120
+
121
+ `multi_panel_animation.py`: 5 subplots with different plot types
122
+
123
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/multi_panel_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
124
+
125
+ `very_complex_animation.py`: 14 subplots with GridSpec layout
126
+
127
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/very_complex_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
128
+
129
+ `nondeterministic_video_loader.py`: handling frames that arrive out of order
130
+
131
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/nondeterministic_video_loader.mp4" width="240" autoplay loop muted playsinline controls></video>
90
132
 
91
133
 
92
134
  ## Performance test
93
135
 
94
- 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:
95
137
 
96
- ![](assets/scaling_graph.png)
138
+ See the [interactive scaling figure](https://sibocw.github.io/parallel-matplotlib-animation/benchmark/) on the documentation site.
97
139
 
98
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.
99
141
 
@@ -1,25 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: parallel-matplotlib-animation
3
- Version: 0.1.1
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
- Project-URL: Repository, https://github.com/sibocw/parallel-matplotlib-animation
21
- Description-Content-Type: text/markdown
22
-
23
1
  # parallel-matplotlib-animation
24
2
 
25
3
  Create matplotlib animations rendered to video in parallel, with efficient resources reuse.
@@ -43,7 +21,7 @@ Renders matplotlib animations by:
43
21
  1. Creating a bunch of worker processes, and creating matplotlib resources (plt.Figure, plt.Axes, artists, etc.) once per worker
44
22
  2. Distributing frames across workers via a dynamic queue
45
23
  3. Rendering the assigned frames from each worker, but updating the data only (without redrawing the whole plot from scratch)
46
- 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)
47
25
 
48
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:
49
27
  - Serial processing: avoids the overhead of recreating complex layouts for every frame
@@ -59,7 +37,7 @@ from parallel_animate import Animator
59
37
  # Step 1: Create a child class of parallel_animate.Animator
60
38
  class WaveAnimation(Animator):
61
39
 
62
- # Step 2: Define how the plot should be setup
40
+ # Step 2: Define how the plot should be set up
63
41
  def setup(self):
64
42
  fig, ax = plt.subplots()
65
43
  self.x = np.linspace(0, 4 * np.pi, 200)
@@ -80,12 +58,12 @@ class WaveAnimation(Animator):
80
58
  # Step 4: Define a list of input parameters, one for each frame
81
59
  params = [{"phase": 2 * np.pi * i / 60} for i in range(60)]
82
60
 
83
- # Step 5: Make video in parallel
61
+ # Step 5: Make the video in parallel
84
62
  anim = WaveAnimation()
85
63
  anim.make_video("wave.mp4", param_by_frame=params, fps=30, num_workers=4)
86
64
  ```
87
65
 
88
- ![](assets/simple_wave_animation.gif)
66
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/simple_wave_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
89
67
 
90
68
  ## Usage
91
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:
@@ -97,25 +75,45 @@ This library has a single class: `parallel_animate.Animator`. To make an animati
97
75
  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:
98
76
 
99
77
  - `output_file` (Path or str): Output video path
100
- - `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.
78
+ - `param_by_frame` (Iterable): Iterable of parameters. Each element is the `params` argument to be given to the `.update` call for the corresponding frame. Can be a list, tuple, generator, or any other iterable. Using generators is particularly useful for large data (e.g., bitmaps) to avoid loading everything into memory at once.
101
79
  - `fps` (int): Frame rate of the output video
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.
102
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.
103
- - 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.
87
+
88
+ ### Special case: frame params arriving out-of-order in `param_by_frame`
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.
104
90
 
91
+ See `examples/nondeterministic_video_loader.py` for details.
105
92
 
106
93
  ## Examples
107
94
 
108
- See [`src/parallel_animate/examples/`](https://github.com/sibocw/parallel-matplotlib-animation/blob/main/src/parallel_animate/examples/):
109
- - `simple_wave_animation.py`: The example above
110
- - `multi_panel_animation.py`: 5 subplots with different plot types
111
- - `very_complex_animation.py`: 14 subplots with GridSpec layout
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`.
96
+
97
+ `simple_wave_animation.py`: The example above
98
+
99
+ `multi_panel_animation.py`: 5 subplots with different plot types
100
+
101
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/multi_panel_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
102
+
103
+ `very_complex_animation.py`: 14 subplots with GridSpec layout
104
+
105
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/very_complex_animation.mp4" width="480" autoplay loop muted playsinline controls></video>
106
+
107
+ `nondeterministic_video_loader.py`: handling frames that arrive out of order
108
+
109
+ <video src="https://sibocw.github.io/parallel-matplotlib-animation/output/nondeterministic_video_loader.mp4" width="240" autoplay loop muted playsinline controls></video>
112
110
 
113
111
 
114
112
  ## Performance test
115
113
 
116
- 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:
117
115
 
118
- ![](assets/scaling_graph.png)
116
+ See the [interactive scaling figure](https://sibocw.github.io/parallel-matplotlib-animation/benchmark/) on the documentation site.
119
117
 
120
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.
121
119
 
@@ -123,4 +121,4 @@ The left-most blue dot indicates serial processing with resources reuse. The bla
123
121
  ## Unit tests
124
122
  ```bash
125
123
  python -m unittest discover -s tests
126
- ```
124
+ ```
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Convert an MP4 video to a GIF with specified scale and optional frame limit.
4
+ # Useful for creating GIFs from rendered videos for documentation.
5
+ # Usage:
6
+ # ./mp4_to_gif.sh input.mp4 output.gif scale max_frames
7
+ # Example:
8
+ # ./mp4_to_gif.sh input.mp4 output.gif 320 100 # limit to 100 frames
9
+ # ./mp4_to_gif.sh input.mp4 output.gif 320 0 # unlimited
10
+
11
+ input_path="$1"
12
+ output_path="$2"
13
+ scale="$3"
14
+ max_frames="$4"
15
+
16
+ # Check arguments
17
+ if [ $# -lt 3 ] || [ $# -gt 4 ]; then
18
+ echo "Usage: $0 <input_path> <output_path> <scale> [max_frames]"
19
+ exit 1
20
+ fi
21
+
22
+ # Set frame limit option
23
+ frame_limit=""
24
+ if [ -n "$max_frames" ] && [ "$max_frames" -gt 0 ]; then
25
+ frame_limit="-vframes $max_frames"
26
+ fi
27
+
28
+ ffmpeg -i "$input_path" $frame_limit \
29
+ -filter_complex "fps=10,scale=${scale}:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=single[p];[s1][p]paletteuse=dither=sierra2_4a" \
30
+ -loop 0 "$output_path"
31
+