vipsim 0.1.0__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.
- vipsim-0.1.0/.gitignore +218 -0
- vipsim-0.1.0/LICENSE +21 -0
- vipsim-0.1.0/PKG-INFO +355 -0
- vipsim-0.1.0/pyproject.toml +61 -0
- vipsim-0.1.0/readme.md +319 -0
- vipsim-0.1.0/vipsim/__init__.py +84 -0
- vipsim-0.1.0/vipsim/__main__.py +4 -0
- vipsim-0.1.0/vipsim/assets/macular-degeneration.jpg +0 -0
- vipsim-0.1.0/vipsim/filters.py +2522 -0
- vipsim-0.1.0/vipsim/gpu.py +2538 -0
vipsim-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
vipsim-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Max Rädler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
vipsim-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vipsim
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Visual-impairment simulation filters: a NumPy/PyTorch port of the VIP-Sim Unity shaders
|
|
5
|
+
Project-URL: Homepage, https://github.com/Max-Raed/VIP-Sim_Port
|
|
6
|
+
Project-URL: Upstream, https://github.com/Max-Raed/VIP-Sim
|
|
7
|
+
Project-URL: Issues, https://github.com/Max-Raed/VIP-Sim_Port/issues
|
|
8
|
+
Author: Max Rädler, Omid Rajabi, Mark Colley
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: VIP-Sim,accessibility,image filters,low vision,simulation,visual impairment
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Requires-Dist: numpy>=2.3
|
|
23
|
+
Requires-Dist: pillow>=12.0
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: gymnasium>=1.0.0; extra == 'all'
|
|
26
|
+
Requires-Dist: scikit-image>=0.24; extra == 'all'
|
|
27
|
+
Requires-Dist: torch>=2.11; extra == 'all'
|
|
28
|
+
Provides-Extra: gpu
|
|
29
|
+
Requires-Dist: torch>=2.11; extra == 'gpu'
|
|
30
|
+
Provides-Extra: gym
|
|
31
|
+
Requires-Dist: gymnasium>=1.0.0; extra == 'gym'
|
|
32
|
+
Requires-Dist: torch>=2.11; extra == 'gym'
|
|
33
|
+
Provides-Extra: teichopsia
|
|
34
|
+
Requires-Dist: scikit-image>=0.24; extra == 'teichopsia'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
<div align="center">
|
|
38
|
+
|
|
39
|
+
# 👁️ VipSim
|
|
40
|
+
|
|
41
|
+
### Visual-impairment simulation filters for accessibility research
|
|
42
|
+
|
|
43
|
+
*A Python (NumPy/PIL) and GPU (PyTorch) reimplementation of the [VIP-Sim](https://github.com/Max-Raed/VIP-Sim) Unity shaders — proven equivalent to the originals and built for VLM accessibility analysis and embodied-AI training.*
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+

|
|
47
|
+

|
|
48
|
+

|
|
49
|
+

|
|
50
|
+

|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## What this is
|
|
58
|
+
|
|
59
|
+
VipSim takes an ordinary image and renders it as someone with a given visual impairment would experience it — color-blindness, cataracts, macular degeneration, double vision, migraine aura, and more. It exists to let downstream systems (vision-language model audits, accessibility evaluations, RL agents with simulated low vision) operate on perceptually faithful inputs.
|
|
60
|
+
|
|
61
|
+
The original VIP-Sim runs as a stack of HLSL shaders inside Unity. This repository re-implements every filter in plain Python so it can run server-side, and again in PyTorch so it can run batched on a GPU inside training loops. Crucially, the Python port is **validated against the Unity original** rather than merely "looking right" — see [Validation](#-validation).
|
|
62
|
+
|
|
63
|
+
## ✨ Highlights
|
|
64
|
+
|
|
65
|
+
- **20 visual-impairment filters** spanning color, optical, field-loss, distortion, and time-varying effects.
|
|
66
|
+
- **24 clinical presets** that map named conditions (e.g. *macular degeneration*, *diplopia*, *scintillating scotoma*) to ready-made filter chains.
|
|
67
|
+
- **Two backends** — a NumPy/PIL reference (`vipsim.filters`) and a batched PyTorch port (`vipsim.gpu`). The GPU port covers every filter, preset and profile, with the CPU's keyword arguments, and reproduces the CPU output bit for bit wherever it has been tested (see [Under the hood](#-under-the-hood-gpu-port)).
|
|
68
|
+
- **Checked against Unity** — 17 of 19 tracked filters pass the repo's SSIM_blur criterion against matched Unity captures; read the [caveats](#-validation) before citing this.
|
|
69
|
+
- **Drop-in Gym/MyoSuite wrapper** for applying impairment chains to RL observations on-device.
|
|
70
|
+
- **`vipsim` command** for one-off image processing, installed with the package.
|
|
71
|
+
|
|
72
|
+
## 📦 Repository contents
|
|
73
|
+
|
|
74
|
+
Only the `vipsim/` package goes into the PyPI release; everything else is for development and validation.
|
|
75
|
+
|
|
76
|
+
| File | Role |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `vipsim/filters.py` | CPU reference implementation — 20 filters, 24 presets, and the CLI. The source of truth for behavior. |
|
|
79
|
+
| `vipsim/gpu.py` | PyTorch port of every filter, operating on batched, channels-first tensors `[N, 3, H, W]` and matching the CPU output. Runs presets and profiles; includes a Gym/MyoSuite observation wrapper. |
|
|
80
|
+
| `vipsim/__init__.py`, `vipsim/__main__.py` | Public API (`from vipsim import apply_preset`, …), `__version__`, and `python -m vipsim`. |
|
|
81
|
+
| `vipsim/assets/` | The macular-degeneration overlay used by `apply_field_loss` (shipped in the package). |
|
|
82
|
+
| `vipsim_filters.py`, `vipsim_gpu.py` | Aliases so older commands (`python vipsim_filters.py …`) keep working; not part of the installed package. |
|
|
83
|
+
| `pyproject.toml` | Package metadata and build config (hatchling; distribution name `vipsim`). |
|
|
84
|
+
| `.github/workflows/publish.yml` | Builds and publishes to PyPI via Trusted Publishing when a GitHub release is published. |
|
|
85
|
+
| `tests/` | Unit tests (CPU filters, CLI, packaging, GPU-vs-CPU parity). |
|
|
86
|
+
| `vipsim_equivalence_report.md` | The validation document: how the Python port is shown to reproduce the Unity shaders, with methods, metrics, and results. |
|
|
87
|
+
| `validate.sh`, `validation_scripts/` | One-command Unity-equivalence validation and the scripts behind it. |
|
|
88
|
+
| `vipsim_assets/`, `abs_diff_out/` | Input scene, Unity reference captures, and validation outputs. |
|
|
89
|
+
| `vipsim_profiles_v2/` | The 7 validated VIP-Sim participant profiles as filter stacks (`p1-p7.json`), calibrated against the paper's Figure-6 panels, with side-by-side renders. |
|
|
90
|
+
| `validation_scripts/profile_tuner.py` | Interactive browser tuner (localhost:8765): live sliders for every parameter, similarity scoring vs a reference, auto-tune, and a generated `/docs` page. |
|
|
91
|
+
| `validation_scripts/render_profiles_v2.py` | Regenerates the paper-vs-ours comparison renders for the profiles. |
|
|
92
|
+
| `unity_helpers/` | Unity capture scripts used to produce the reference renders. |
|
|
93
|
+
| `filter_documentation.html` | Plain-language documentation of every filter and parameter (static export of the tuner's `/docs` page). |
|
|
94
|
+
|
|
95
|
+
## 🚀 Quick start
|
|
96
|
+
|
|
97
|
+
### Install
|
|
98
|
+
|
|
99
|
+
Requires Python 3.12 or newer.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install vipsim # CPU reference (numpy, pillow)
|
|
103
|
+
pip install "vipsim[teichopsia]" # + scikit-image, needed by apply_teichopsia
|
|
104
|
+
pip install "vipsim[gpu]" # + torch, for vipsim.gpu / VipSimGPU
|
|
105
|
+
pip install "vipsim[gym]" # + torch and gymnasium, for VisualImpairmentWrapper
|
|
106
|
+
pip install "vipsim[all]" # everything
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
From a checkout of this repository: `pip install -e ".[all]"`.
|
|
110
|
+
|
|
111
|
+
### Command line
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Single filter (`python -m vipsim ...` and `python vipsim_filters.py ...` work too)
|
|
115
|
+
vipsim input.png output.png --filter blur --severity 0.5
|
|
116
|
+
vipsim input.png output.png --filter cvd --type deuteranomaly --severity 0.8
|
|
117
|
+
vipsim input.png output.png --filter cataracts --severity 0.5
|
|
118
|
+
|
|
119
|
+
# Brightness / contrast / gamma
|
|
120
|
+
vipsim input.png output.png --filter bcg --brightness 0.8 --contrast 0.7 --gamma 1.5
|
|
121
|
+
|
|
122
|
+
# Named preset (a chain of filters)
|
|
123
|
+
vipsim input.png output.png --preset moderate_low_vision
|
|
124
|
+
|
|
125
|
+
# See everything available
|
|
126
|
+
vipsim --list-presets
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Python API
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from PIL import Image
|
|
133
|
+
from vipsim import apply_cvd, apply_preset
|
|
134
|
+
|
|
135
|
+
img = Image.open("scene.png").convert("RGB")
|
|
136
|
+
|
|
137
|
+
# Apply one filter
|
|
138
|
+
out = apply_cvd(img, cvd_type="deuteranomaly", severity=0.6)
|
|
139
|
+
|
|
140
|
+
# Apply a clinical preset (chained filters)
|
|
141
|
+
out = apply_preset(img, "macular_degeneration")
|
|
142
|
+
|
|
143
|
+
out.save("scene_impaired.png")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or as a module: `import vipsim as vip`, then `vip.apply_blur(img, 0.5)`, `vip.PRESETS`, `vip.__version__`. Every filter is exported as `apply_<name>`.
|
|
147
|
+
|
|
148
|
+
### GPU / batched
|
|
149
|
+
|
|
150
|
+
The PyTorch port works on a whole batch of frames at once and keeps everything on-device. Every filter is a `VipSimGPU` method with the CPU name and keyword arguments (`sim.cvd(x, cvd_type=..., severity=...)` is `apply_cvd(img, cvd_type=..., severity=...)` on a batch). Image-independent data (warp fields, index maps, masks, lookup tables) is computed once per parameter set and frame size by the CPU's own NumPy code and kept in a bounded on-device cache.
|
|
151
|
+
|
|
152
|
+
Needs `pip install "vipsim[gpu]"`. `VipSimGPU` and `VisualImpairmentWrapper` can also be imported straight from `vipsim`; torch is only loaded when you first touch them, so a plain `import vipsim` stays fast and works without torch.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import json
|
|
156
|
+
import torch
|
|
157
|
+
from vipsim import VipSimGPU
|
|
158
|
+
from vipsim.gpu import frames_to_gpu, gpu_to_frames
|
|
159
|
+
|
|
160
|
+
sim = VipSimGPU(device="cuda")
|
|
161
|
+
x = frames_to_gpu(frames, sim.device) # frames: [N, H, W, 3] uint8 -> [N, 3, H, W] in [0,1]
|
|
162
|
+
|
|
163
|
+
x = sim.cvd(x, cvd_type="deuteranomaly", severity=0.8)
|
|
164
|
+
x = sim.blur(x, severity=0.4)
|
|
165
|
+
x = sim.apply_preset(x, "macular_degeneration")
|
|
166
|
+
x = sim.apply_profile(x, json.load(open("vipsim_profiles_v2/p3.json")))
|
|
167
|
+
out = gpu_to_frames(x) # back to uint8, same pixels as the CPU chain
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Inside an RL / MyoSuite environment
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from vipsim import VisualImpairmentWrapper # needs vipsim[gym]
|
|
174
|
+
|
|
175
|
+
env = VisualImpairmentWrapper(
|
|
176
|
+
env,
|
|
177
|
+
chain=[("cvd", {"severity": 0.8}), ("blur", {"severity": 0.4})],
|
|
178
|
+
device="cuda",
|
|
179
|
+
image_key=None, # set this if the observation is a dict
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`chain` takes any filter with its CPU keyword arguments; `vipsim.PRESETS[name]["filters"]` is a ready chain, and `vipsim.gpu.profile_chain(profile)` turns a `vipsim_profiles_v2` profile into one.
|
|
184
|
+
|
|
185
|
+
## 🎛️ Filter catalogue
|
|
186
|
+
|
|
187
|
+
Every filter takes a `severity` in `[0, 1]` unless noted. The **Simulates** column is the clinical condition the matching preset is named for.
|
|
188
|
+
|
|
189
|
+
| Filter | Simulates | Unity equivalence |
|
|
190
|
+
|---|---|:---:|
|
|
191
|
+
| `cvd` | Color-vision deficiency (prot / deuter / tritanomaly, monochrome) | ✅ proof |
|
|
192
|
+
| `bcg` | Brightness / contrast / gamma shift | ✅ proof |
|
|
193
|
+
| `field_loss` | Macular degeneration (central) / tunnel vision (peripheral) | ✅ proof |
|
|
194
|
+
| `blur` | Low-vision acuity loss | ✅ |
|
|
195
|
+
| `bloom` | Photophobia / glare from bright regions | ✅ |
|
|
196
|
+
| `cataracts` | Cataracts (blur + yellowing + halo scatter) | ✅ |
|
|
197
|
+
| `distortion` | Metamorphopsia (wavy lines) | ✅ |
|
|
198
|
+
| `double_vision` | Diplopia | ✅ |
|
|
199
|
+
| `pixelation` | Very low acuity / low resolution | ✅ |
|
|
200
|
+
| `vortex` | Local swirling distortion | ✅ |
|
|
201
|
+
| `noise` | Visual snow / interference | ✅ |
|
|
202
|
+
| `teichopsia` | Scintillating scotoma (migraine aura) | ✅ |
|
|
203
|
+
| `foveal_darkness` | Central scotoma | ✅ |
|
|
204
|
+
| `floaters` | Vitreous floaters | ✅ |
|
|
205
|
+
| `flickering_stars` | Random bright dots (time-varying) | ✅ burst |
|
|
206
|
+
| `wiggle` | Sinusoidal warp (time-varying) | ✅ burst |
|
|
207
|
+
| `nystagmus` | Involuntary eye-oscillation motion (time-varying) | ✅ burst |
|
|
208
|
+
| `glitch` | RGB channel-shift glitch | ⚠️ dropped |
|
|
209
|
+
| `led` | LED-board cellular dot pattern | ⚠️ marginal |
|
|
210
|
+
| `detail_loss` | Contrast-sensitivity loss (posterization) | — not yet tracked |
|
|
211
|
+
|
|
212
|
+
`✅ proof` = closed by source-level algorithmic equivalence · `✅` / `✅ burst` = passes the measured-fidelity threshold (single-frame or multi-frame burst) · `⚠️` = below threshold, see report.
|
|
213
|
+
|
|
214
|
+
## 🧩 Clinical presets
|
|
215
|
+
|
|
216
|
+
Presets bundle one or more filters under a recognizable name. A selection:
|
|
217
|
+
|
|
218
|
+
| Preset | What it models |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `mild_low_vision` / `moderate_low_vision` / `severe_low_vision` | Graded blur + contrast loss |
|
|
221
|
+
| `deuteranomaly_moderate` / `protanomaly_moderate` | Red-green color blindness |
|
|
222
|
+
| `cataracts_moderate` / `elderly_vision` | Cataracts, with optional age-related CVD |
|
|
223
|
+
| `macular_degeneration` / `tunnel_vision` | Central vs. peripheral field loss |
|
|
224
|
+
| `metamorphopsia` / `diplopia` | Wavy distortion / double vision |
|
|
225
|
+
| `scintillating_scotoma` / `central_scotoma` | Migraine aura / central dark spot |
|
|
226
|
+
| `photophobia` | Glare sensitivity |
|
|
227
|
+
| `nystagmus_motion` / `floaters` / `visual_noise` | Motion blur, floaters, visual snow |
|
|
228
|
+
|
|
229
|
+
Run `vipsim --list-presets` for the full set of 24 with descriptions.
|
|
230
|
+
|
|
231
|
+
## 🧑🤝🧑 Participant profiles (`vipsim_profiles_v2/`)
|
|
232
|
+
|
|
233
|
+
The VIP-Sim paper validated its shader stacks with 7 low-vision participants
|
|
234
|
+
(Table 4) and shows the same street scene through each participant's eyes
|
|
235
|
+
(Figure 6). This repo carries recreations of those 7 profiles as filter stacks:
|
|
236
|
+
one filter per validated shader, calibrated against clean extractions of the
|
|
237
|
+
Figure-6 panels. Each `pN.json` documents its Table-4 mapping and carries the
|
|
238
|
+
stack itself; `renders/` holds paper-vs-ours comparisons. Similarity vs the
|
|
239
|
+
paper panels (mean of SSIM, pixel, and CIEDE2000 color): 81 to 98 percent.
|
|
240
|
+
|
|
241
|
+
Tune or inspect them interactively:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pip install scipy scikit-image # tuner extras
|
|
245
|
+
python validation_scripts/profile_tuner.py # then open http://localhost:8765
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The tuner shows the reference next to a live render, scores the similarity as
|
|
249
|
+
you drag sliders, and can auto-tune all numeric values (coordinate descent).
|
|
250
|
+
Every filter and parameter is explained in plain language at `/docs` (also
|
|
251
|
+
exported as `filter_documentation.html`). Steps may carry `"_disabled": true`
|
|
252
|
+
to switch them off without deleting them; all scripts in this repo honor that.
|
|
253
|
+
|
|
254
|
+
## 🔬 Under the hood (GPU port)
|
|
255
|
+
|
|
256
|
+
All 21 filters of `vipsim.filters` (the catalogue above plus `vignette`) are ported. The CPU filters are the reference: each GPU twin reproduces today's CPU output, quirks included (nearest-index truncation, 8-bit intermediates, Pillow's integer arithmetic), and has been **bit-exact** with it in every comparison so far, on CUDA and on CPU torch, with Pillow 12.2 and 13. The CPU only ever sees a uint8 image, so each twin reads its input as 8-bit levels and returns `level / 255`: a chain of GPU filters sees exactly the CPU chain's images. One exception concerns batches: a stochastic filter seeded with a shared `numpy.random.Generator` (or `None`) draws one field per frame, in batch order, so a batched chain of two such filters consumes the draws filter by filter (it equals running each CPU filter over the whole batch in turn, not the CPU chain frame by frame). Fixed integer seeds, one Generator per filter, or one frame per call (as in the wrapper) avoid this.
|
|
257
|
+
|
|
258
|
+
There are no custom kernels. The backend is built from a few primitives:
|
|
259
|
+
|
|
260
|
+
1. **Per-level tables and channel mixes.** Every step that maps one channel value to another (sRGB ↔ linear, `pow`, brightness/contrast/gamma, posterize, the floaters' blend) is the CPU's own NumPy code, evaluated once on the 256 levels and looked up on the device; torch's `pow` is about 1 ulp off NumPy's, which is enough to flip 8-bit levels. Three-channel mixes (the `cvd` matrices, luminance) add their products in the order NumPy's BLAS does.
|
|
261
|
+
2. **Pillow-exact blur and resize.** `pil_gaussian_blur` replays `ImageFilter.GaussianBlur` (three extended-box passes per axis in 24-bit fixed point, each rounded to 8 bits) with cumulative sums in integers, so its cost does not grow with the radius. `VipSimGPU.pil_resize` replays Pillow's BILINEAR and NEAREST resamplers, including the installed Pillow's pass order (12.2 and 13 differ). Neither uses `conv2d`, which runs in TF32 by default on Ampere and later GPUs. They serve `blur`, `bloom`, the `field_loss` pyramid and `pixelation`.
|
|
262
|
+
3. **Pixel gathers.** `gather_nearest` and `gather_bilinear` replace NumPy fancy indexing for `distortion`, `wiggle`, `vortex`, the cataract frost and the `led` cells; slices and rolls cover `double_vision`, `nystagmus` and `glitch`.
|
|
263
|
+
|
|
264
|
+
Anything that doesn't depend on the image content (index maps, masks, noise fields, tables) is computed **once** per parameter set and frame size by the CPU's NumPy code, seeded RNG included, and kept in a bounded LRU cache on the device, so per-frame work is a few batched tensor operations. Cache keys are exact and typed: under NumPy 2, `np.float64(0.5)` and `0.5` can give different CPU results, so they never share an entry.
|
|
265
|
+
|
|
266
|
+
**What is checked.**
|
|
267
|
+
|
|
268
|
+
- `tests/test_gpu_*.py` compare every GPU filter with its CPU filter pixel for pixel. They use random and natural frames at several sizes (odd, portrait, 1 pixel wide and very tall), severities near 0 and 1, every keyword argument (NumPy scalar types included), batches of different frames, other input dtypes, cache reuse, and the CPU's exceptions for invalid input.
|
|
269
|
+
- `tests/test_gpu_primitives.py` checks the blur and resize against Pillow itself and the gathers against NumPy indexing.
|
|
270
|
+
- `tests/test_gpu_profiles.py` renders all 7 profiles (as `render_profiles_v2.py` does) on both backends step by step and end to end, and all 24 presets end to end.
|
|
271
|
+
- `validation_scripts/validate_gpu_vs_numpy.py` runs every ported filter on the 2048×1024 scene and exits with an error if one exceeds its documented tolerance (`vipsim.gpu.TWIN_TOLERANCE`; results in `abs_diff_out/gpu_vs_numpy_summary.*`) or is skipped for a missing optional dependency (`--allow-skip` accepts that). The tolerance is 0 levels for every filter except `cvd`, `bcg` and `foveal_darkness`, which may differ by 1 level on another BLAS build, because it can sum their channel mix in a different order. The tolerance is per filter, so `foveal_darkness` gets it in every mode, although only `mode="desat"` mixes channels.
|
|
272
|
+
|
|
273
|
+
Because the GPU port equals the NumPy port, it inherits the NumPy port's validation against Unity (below).
|
|
274
|
+
|
|
275
|
+
**Speed.** In the port's verification runs on an RTX 4050 Laptop GPU, one 2048×1024 frame took 3–30 ms per filter (0.1–0.2 s for `field_loss`), against 0.01–2.5 s for the NumPy filter. A batch of 64 frames of 84×84 cost about as much as one frame for most filters; a single small frame is launch-bound and no faster than NumPy. `pixelation` is the exception: like the CPU, it resamples to a grid of cells that does not shrink with the frame (about 510 cells across at the default severity, 800 at severity 0.2), so a batch of 64 frames of 84×84 takes about 0.1–0.2 s and up to 2 GB of GPU memory. The first call with new parameters or a new frame size runs the CPU code to build the cached fields: up to about 1.5 s at 2048×1024 for `vortex`. The cache is bounded by entries, not bytes, so a sweep over many parameter sets at full resolution can hold several GB of fields.
|
|
276
|
+
|
|
277
|
+
## ✅ Validation
|
|
278
|
+
|
|
279
|
+
The Python port isn't just visually plausible — it's checked against the Unity original. Two complementary methods are used, documented in full in [`vipsim_equivalence_report.md`](https://github.com/Max-Raed/VIP-Sim_Port/blob/main/vipsim_equivalence_report.md):
|
|
280
|
+
|
|
281
|
+
- **Method A — algorithmic proof.** For pure deterministic arithmetic filters (`bcg`, `cvd`), the HLSL fragment math and the NumPy code are lined up and shown to compute the same function. Fed Unity's own unfiltered capture, both match the Unity render to within 1 level (MAE 0.14 for bcg, 0.07 for cvd).
|
|
282
|
+
- **Method B — measured fidelity.** For everything else (noise, kernels, halo scatter, UV warps, gaze-contingent and time-varying effects), matched 2048×1024 captures are compared through a stack of published metrics: **MAE, SSIM, SSIM_blur, ΔE2000, Wasserstein-1, LPIPS**.
|
|
283
|
+
|
|
284
|
+
A filter passes when its best sweep point reaches **SSIM_blur ≥ 0.90** against the matched Unity reference. SSIM_blur is SSIM after a σ = 4 px Gaussian pre-blur of both images (`validation_scripts/metric_constants.py`), which discounts detail finer than about 10 px.
|
|
285
|
+
|
|
286
|
+
For the three inherently time-varying filters (`flickering_stars`, `wiggle`, `nystagmus`) a **burst-frame protocol** captures 30 matched frames over a fixed window and aggregates the metric across them.
|
|
287
|
+
|
|
288
|
+
**Status: 17 of 19 tracked filters pass.** `glitch` sits just below threshold (SSIM_blur 0.890, non-deterministic) and `led` is marginal pending cell-phase tuning.
|
|
289
|
+
|
|
290
|
+
**Caveats (audit, 2026-10-29).** A pass is weaker evidence than it looks:
|
|
291
|
+
|
|
292
|
+
- The unfiltered image already scores SSIM_blur ≥ 0.90 against 12 of the 17 passing Unity references, so for those filters the criterion does not separate the port from doing nothing. Of the passing filters only `bcg`, `cataracts` and `double_vision` are clearly separated; `cvd` rests on its Method A proof instead.
|
|
293
|
+
- The best sweep point is chosen on the same reference it is scored against; nothing is held out.
|
|
294
|
+
- The Unity references were rendered from a block-compressed (DXT/BC1) copy of the input, so Unity and Python start from images that differ by MAE ≈ 2.6. That offset, not fp16 rounding, is the ~3 MAE "floor" in the results table.
|
|
295
|
+
- The `flickering_stars` and `nystagmus` burst validators test their own time-aware models, not `apply_flickering_stars` / `apply_nystagmus`.
|
|
296
|
+
- `field_loss` has no source-level proof and is now listed under Method B.
|
|
297
|
+
|
|
298
|
+
### Reproduce the sweep
|
|
299
|
+
|
|
300
|
+
This needs a clone of the repository (the Unity captures and scripts are not in the PyPI package). `./validate.sh` does all of it, including the virtualenv; by hand:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
source .venv/bin/activate
|
|
304
|
+
python validation_scripts/validate_all_filters.py --no-strips
|
|
305
|
+
|
|
306
|
+
# burst-frame validators for the time-varying filters
|
|
307
|
+
python validation_scripts/validate_flickering_stars_burst.py
|
|
308
|
+
python validation_scripts/validate_wiggle_burst.py
|
|
309
|
+
python validation_scripts/validate_nystagmus_burst.py
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Tests
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
python -m unittest discover -s tests -v
|
|
316
|
+
python validation_scripts/validate_gpu_vs_numpy.py # GPU vs NumPy on the 2048x1024 scene
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The GPU tests run on CUDA when it is available, else on CPU torch, and are skipped without torch.
|
|
320
|
+
|
|
321
|
+
### Releasing to PyPI
|
|
322
|
+
|
|
323
|
+
The distribution name is `vipsim`; the version lives in `vipsim/__init__.py` (`__version__`), which the build reads.
|
|
324
|
+
|
|
325
|
+
Releases are published by [`.github/workflows/publish.yml`](https://github.com/Max-Raed/VIP-Sim_Port/blob/main/.github/workflows/publish.yml) through PyPI Trusted Publishing, so no API token is involved:
|
|
326
|
+
|
|
327
|
+
1. Bump `__version__` in `vipsim/__init__.py` and commit.
|
|
328
|
+
2. Create a GitHub release with a matching tag (e.g. `v0.1.0`). Publishing the release builds the sdist and wheel, checks them with `twine check`, and uploads them to PyPI.
|
|
329
|
+
|
|
330
|
+
To check a build locally before releasing:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
python -m pip install build twine
|
|
334
|
+
python -m build # writes dist/vipsim-<version>.tar.gz and .whl
|
|
335
|
+
python -m twine check dist/*
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## 📚 References
|
|
339
|
+
|
|
340
|
+
- Wang, Bovik, Sheikh & Simoncelli (2004). *Image quality assessment: from error visibility to structural similarity.* IEEE TIP 13(4).
|
|
341
|
+
- Sharma, Wu & Dalal (2005). *The CIEDE2000 color-difference formula.* Color Research & Application 30(1).
|
|
342
|
+
- Zhang, Isola, Efros, Shechtman & Wang (2018). *The Unreasonable Effectiveness of Deep Features as a Perceptual Metric.* CVPR.
|
|
343
|
+
- Kantorovich (1942) / Villani (2008) — optimal transport (Wasserstein distance).
|
|
344
|
+
|
|
345
|
+
## 📄 License & attribution
|
|
346
|
+
|
|
347
|
+
VipSim is released under the **MIT License** (see [`LICENSE`](https://github.com/Max-Raed/VIP-Sim_Port/blob/main/LICENSE)).
|
|
348
|
+
|
|
349
|
+
It reimplements shader code from **[VIP-Sim](https://github.com/Max-Raed/VIP-Sim)** by Max Raed, which is licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); please keep that attribution when you use or redistribute VipSim.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
<div align="center">
|
|
354
|
+
<sub>Built for accessibility research and embodied-AI evaluation.</sub>
|
|
355
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# 1.27 is the first hatchling with PEP 639 (license expression + license-files).
|
|
3
|
+
requires = ["hatchling>=1.27"]
|
|
4
|
+
build-backend = "hatchling.build"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "vipsim"
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
description = "Visual-impairment simulation filters: a NumPy/PyTorch port of the VIP-Sim Unity shaders"
|
|
10
|
+
readme = "readme.md"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Max Rädler" },
|
|
16
|
+
{ name = "Omid Rajabi" },
|
|
17
|
+
{ name = "Mark Colley" },
|
|
18
|
+
]
|
|
19
|
+
keywords = ["visual impairment", "low vision", "accessibility", "simulation", "image filters", "VIP-Sim"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Science/Research",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"numpy>=2.3",
|
|
33
|
+
"pillow>=12.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
# apply_teichopsia needs scikit-image (HSV conversion, polygon raster).
|
|
38
|
+
teichopsia = ["scikit-image>=0.24"]
|
|
39
|
+
gpu = ["torch>=2.11"]
|
|
40
|
+
gym = ["vipsim[gpu]", "gymnasium>=1.0.0"]
|
|
41
|
+
all = ["vipsim[teichopsia,gpu,gym]"]
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
vipsim = "vipsim.filters:main"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/Max-Raed/VIP-Sim_Port"
|
|
48
|
+
Upstream = "https://github.com/Max-Raed/VIP-Sim"
|
|
49
|
+
Issues = "https://github.com/Max-Raed/VIP-Sim_Port/issues"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.version]
|
|
52
|
+
# Single source of truth: __version__ in vipsim/__init__.py (currently 0.1.0).
|
|
53
|
+
path = "vipsim/__init__.py"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
# Only the package; vipsim/assets/ (the field-loss overlay) ships inside it.
|
|
57
|
+
packages = ["vipsim"]
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.sdist]
|
|
60
|
+
# No tests, validation scripts, Unity captures, renders or abs_diff_out.
|
|
61
|
+
only-include = ["vipsim", "readme.md", "LICENSE"]
|