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.
- parallel_matplotlib_animation-0.1.3/.github/workflows/docs.yml +40 -0
- parallel_matplotlib_animation-0.1.3/.github/workflows/publish.yml +42 -0
- parallel_matplotlib_animation-0.1.3/.github/workflows/tests.yml +33 -0
- parallel_matplotlib_animation-0.1.3/.gitignore +308 -0
- parallel_matplotlib_animation-0.1.1/README.md → parallel_matplotlib_animation-0.1.3/PKG-INFO +54 -12
- parallel_matplotlib_animation-0.1.1/PKG-INFO → parallel_matplotlib_animation-0.1.3/README.md +33 -35
- parallel_matplotlib_animation-0.1.3/dev/mp4_to_gif.sh +31 -0
- parallel_matplotlib_animation-0.1.3/docs/api/animator.md +10 -0
- parallel_matplotlib_animation-0.1.3/docs/benchmark.md +41 -0
- parallel_matplotlib_animation-0.1.3/docs/developer.md +49 -0
- parallel_matplotlib_animation-0.1.3/docs/embed_example_videos.py +40 -0
- parallel_matplotlib_animation-0.1.3/docs/examples.md +83 -0
- parallel_matplotlib_animation-0.1.3/docs/gen_benchmark.py +23 -0
- parallel_matplotlib_animation-0.1.3/docs/index.md +76 -0
- parallel_matplotlib_animation-0.1.3/docs/installation.md +43 -0
- parallel_matplotlib_animation-0.1.3/docs/usage.md +86 -0
- {parallel_matplotlib_animation-0.1.1/src/parallel_animate → parallel_matplotlib_animation-0.1.3}/examples/multi_panel_animation.py +1 -1
- parallel_matplotlib_animation-0.1.3/examples/nondeterministic_video_loader.py +46 -0
- parallel_matplotlib_animation-0.1.3/examples/run_all_except_benchmarks.sh +47 -0
- parallel_matplotlib_animation-0.1.3/examples/scaling_test.py +234 -0
- {parallel_matplotlib_animation-0.1.1/src/parallel_animate → parallel_matplotlib_animation-0.1.3}/examples/simple_wave_animation.py +1 -1
- {parallel_matplotlib_animation-0.1.1/src/parallel_animate → parallel_matplotlib_animation-0.1.3}/examples/very_complex_animation.py +2 -2
- parallel_matplotlib_animation-0.1.3/mkdocs.yml +66 -0
- parallel_matplotlib_animation-0.1.3/pyproject.toml +52 -0
- parallel_matplotlib_animation-0.1.3/src/parallel_animate/__init__.py +3 -0
- parallel_matplotlib_animation-0.1.3/src/parallel_animate/animator.py +587 -0
- parallel_matplotlib_animation-0.1.3/src/parallel_animate/util.py +64 -0
- parallel_matplotlib_animation-0.1.3/tests/test_animator.py +633 -0
- parallel_matplotlib_animation-0.1.3/tests/test_examples.py +78 -0
- parallel_matplotlib_animation-0.1.3/tests/test_pvio_options.py +216 -0
- parallel_matplotlib_animation-0.1.3/tests/test_util.py +56 -0
- parallel_matplotlib_animation-0.1.3/uv.lock +2190 -0
- parallel_matplotlib_animation-0.1.1/pyproject.toml +0 -26
- parallel_matplotlib_animation-0.1.1/src/parallel_animate/__init__.py +0 -1
- parallel_matplotlib_animation-0.1.1/src/parallel_animate/animator.py +0 -387
- parallel_matplotlib_animation-0.1.1/src/parallel_animate/examples/scaling_test.py +0 -96
- {parallel_matplotlib_animation-0.1.1 → parallel_matplotlib_animation-0.1.3}/LICENSE +0 -0
- {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
|
parallel_matplotlib_animation-0.1.1/README.md → parallel_matplotlib_animation-0.1.3/PKG-INFO
RENAMED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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` (
|
|
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
|
-
-
|
|
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 [`
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 `
|
|
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
|
-
|
|
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
|
|
parallel_matplotlib_animation-0.1.1/PKG-INFO → parallel_matplotlib_animation-0.1.3/README.md
RENAMED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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` (
|
|
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
|
-
-
|
|
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 [`
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 `
|
|
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
|
-
|
|
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
|
+
|