sensus
Sensory perception simulation for images and audio — apply color blindness, low vision, visual field defects, and hearing loss filters to ordinary photos and audio buffers. Built as a Rust crate so it can power accessibility demos, educational tools, and apps like universal-experience.
sensus (Latin) — "sense, perception, feeling".
Status
v0.5.0 — published on crates.io.
All vision filters through Phase 4 (color vision, refraction, visual field,
light/transparency, balance/vertigo, eye fatigue, and more) are implemented,
plus 14 hearing filters (usable from the CLI via --audio). See
docs/roadmap.md for the full phase tracker.
Filters
Vision
| Filter | Status | Phase | Notes |
|---|---|---|---|
protanopia |
✅ implemented | 1 (#2) | L-cone loss (red-blind), Machado 2009 |
deuteranopia |
✅ implemented | 1 (#2) | M-cone loss (green-blind), Machado 2009 |
tritanopia |
✅ implemented | 1 (#2) | S-cone loss (blue-blind), Machado 2009 |
achromatopsia |
✅ implemented | 1 (#2) | Total color blindness, BT.709 luma |
tetrachromacy |
✅ implemented | 1+ (#3) | Four-cone exploration, gamut expansion in LMS space |
myopia |
✅ implemented | 2 (#4) | Disk blur, -6 D max |
hyperopia |
✅ implemented | 2 (#4) | Disk blur, +4 D max |
presbyopia |
✅ implemented | 2 (#4) | Disk blur, +3 D add max |
astigmatism |
✅ implemented | 2 (#4) | 1D directional blur (pure cylindrical lens), configurable axis |
glaucoma |
✅ implemented | 3 (#5) | Radial vignette / peripheral field loss |
macular_degeneration |
✅ implemented | 3 (#5) | Foveal blur and dimming |
hemianopia |
✅ implemented | 3 (#5) | Half-field blanking |
tunnel_vision |
✅ implemented | 3 (#5) | Severe radial vignette |
cataract |
✅ implemented | 3 (#6) | Haze overlay |
floaters |
✅ implemented | 3 (#6) | Translucent blob compositing |
photophobia |
✅ implemented | 3 (#6) | Brightness boost and highlight halation |
night-blindness |
✅ implemented | 3 (#6) | Darkening and desaturation (nyctalopia) |
vertigo |
✅ implemented | 4 (#9) | Rotational displacement / dizziness |
bppv-rotation |
✅ implemented | 4 (#9) | Positional vertigo rotation |
vestibular-neuritis |
✅ implemented | 4 (#9) | Sustained spinning + blur |
diplopia |
✅ implemented | 4 (#29) | Double vision (ghost image offset) |
nystagmus |
✅ implemented | 4 (#29) | Involuntary directional motion blur |
starbursts |
✅ implemented | 4 (#29) | Light starbursts / glare from highlights |
eye-strain |
✅ implemented | 4 (#36) | Contrast loss, vignette, slight blur |
dry-eye |
✅ implemented | 4 (#36) | Tear-film tile distortion |
Additional vision filters (library Filter enum and CLI --filter):
metamorphopsia (#55), contrast-sensitivity (#56), detail-loss (#57),
teichopsia (#58), and flickering-stars (#59). The library function names
use underscores (contrast_sensitivity, detail_loss, flickering_stars);
the CLI flag names and the night-blindness / nyctalopia pair follow the
kebab-case ↔ snake-case mapping clap derives.
Depth-aware refraction is also available CLI-side only via
--filter myopia-depth / hyperopia-depth / depth-of-field combined with
--depth, --mpo, or --portrait (#19).
Hearing (library + CLI)
14 hearing filters operate on audio buffers (#7–#9, #102–#104): hearing_loss,
sudden_hearing_loss, noise_induced_hearing_loss, tinnitus,
hyperacusis, misophonia, paracusis, amusia, dysmelodia,
pitch_shift_semitones, diplacusis, auditory_processing_disorder (APD),
meniere, and labyrinthitis. Each takes a
sensus_core::hearing::AudioBuffer (f32 interleaved PCM) and returns one.
From the CLI, use --audio <in.wav> --hearing <filter> -o <out.wav> (WAV only).
See CLI usage. From Rust, use sensus_core::apply_hearing /
HearingFilter, or chain via AudioPipeline.
Composite experiences (Experience)
Some conditions span both vision and hearing (e.g. Ménière's disease =
spinning vertigo + low-frequency hearing loss + low-pitched tinnitus). Because
sensus is a pure, separate-buffer (image vs audio) library, Experience
canonicalises which vision filter pairs with which hearing filter, so consumers
(e.g. the universal-experience GUI) don't hard-code the combination:
Experience::MENIERE, BPPV, VESTIBULAR_NEURITIS, LABYRINTHITIS. Each
carries an Urgency classification for "see a doctor" prompts.
Each filter ships with a short note on when to see a doctor — sensus is designed both as an empathy / education tool and as an early-warning primer for symptoms worth taking seriously.
Install
This installs the sensus CLI binary. Pre-built binaries for Linux,
macOS (Intel & Apple Silicon), and Windows are also attached to each
GitHub Release.
CLI usage
# Phase 1: color vision deficiency simulation
# Phase 2: focus / refraction (disk blur in linear sRGB)
# astigmatism with explicit cylinder axis (degrees, 0..=180; default 90)
# Phase 3: visual field defects and light conditions
# Pipeline: chain multiple filters in one pass
# Pipe mode: read JPEG frames from stdin, write filtered frames to stdout (ffmpeg integration)
# --output is not required when --pipe is used
| \
| \
# Hearing: apply hearing filters to a WAV file (#105). WAV in / WAV out only.
# Chain hearing filters and use the composite Ménière hearing side
Flags:
| Flag | Description |
|---|---|
-i, --input |
Input image path (PNG / JPEG / WebP, etc.). |
-o, --output |
Output path (image, or WAV with --audio). Format is inferred from the extension. |
-f, --filter |
Vision filter to apply (e.g. deuteranopia, cataract, glaucoma). |
--audio |
Input audio path (WAV). Routes to hearing-filter mode; requires --hearing and -o. Cannot be combined with --filter / --pipe. |
--hearing |
Hearing filter(s) for --audio (e.g. hearing-loss, tinnitus, meniere, labyrinthitis). Repeatable to chain. |
--freq |
Frequency (Hz) for --hearing tinnitus / sudden-hearing-loss / misophonia. Default 4000. |
--semitones |
Semitones for --hearing pitch-shift (negative = lower). Default 0.0. |
-s, --strength |
Strength 0.0 – 1.0. 0.0 keeps the original; 1.0 is full effect. Default 1.0. |
--axis |
Astigmatism cylinder axis in degrees 0.0 – 180.0 (astigmatism only). Default 90.0 (with-the-rule: vertical lines sharp, horizontal blurred). |
--seed |
Random seed for stochastic filters (cataract, floaters, flickering-stars). Default 0. |
--density, --gaze-x, --gaze-y, --size |
floaters: blob density 0–1, gaze position 0–1, and size multiplier 0.1–5.0. |
--side |
hemianopia side: 0.0 = left field lost, 1.0 = right field lost. |
--offset-x, --offset-y, --ghost-strength |
diplopia ghost-image offsets (min(W,H) ratio) and ghost strength. |
--amplitude, --direction-deg |
nystagmus motion amplitude (min(W,H) ratio) and direction in degrees. |
--num-rays, --ray-length, --threshold, --dispersion |
starbursts: ray count, ray length (min(W,H) ratio), brightness threshold, and chromatic dispersion (0 = white, 1 = rainbow). |
--cell-size |
detail-loss pixelation tile size in pixels (>= 1; 1 = no effect). Default 8. |
--meta-freq, --meta-seed |
metamorphopsia distortion spatial frequency and field seed. |
--depth, --mpo, --portrait, --focus |
Depth-map sources and focus depth for the *-depth / depth-of-field filters. |
Per-filter parameter flags only affect their own filter; passing one to an
unrelated single filter prints a warning and is otherwise ignored.
Run sensus --help for the full list of filter names and flags.
Library usage
sensus-core is the pure-logic crate (no I/O). Take a DynamicImage in,
get a DynamicImage out.
# Cargo.toml
[]
= "0.5"
use DynamicImage;
use ;
strength is clamped to [0.0, 1.0]. Filters in Phase 1 simulate color
vision deficiency in linear sRGB space using the
Machado 2009 model;
achromatopsia uses the BT.709 photopic luminance.
(BT.601 luma coefficients are designed for NTSC CRTs and are colorimetrically
wrong for sRGB images.)
Phase 2 (refraction) applies an optically correct disk blur in linear
sRGB — radius derived from 0.5 × pupil_diameter × |D| (Smith–Helmholtz
gives angular diameter, so the radius is half), min(W, H)-relative,
edge-replicated borders. astigmatism is 1D directional blur (pure
cylindrical lens / line spread function), not an elliptical disk — see
docs/overview.md for the full derivation.
The same function signatures can be applied frame-by-frame for video, or
chained together via sensus_core::pipeline.
Hearing filters
Hearing filters live in sensus_core::hearing and are dispatched via
apply_hearing. They operate on audio buffers, not images. From the CLI they
are reachable via --audio <in.wav> --hearing <filter> -o <out.wav> (WAV only);
from Rust use the API below or chain them via AudioPipeline.
use ;
GLSL shaders (host integration)
sensus_core::shaders ships the same vision effects as GLSL fragment
shaders for hosts that render on the GPU (Flutter FragmentProgram, WebGL,
etc.). Some shaders need resolution-dependent uniforms — e.g.
photophobia, eye_strain, dry_eye, teichopsia, glaucoma,
macular_degeneration, tunnel_vision, and the disk-blur refraction
filters consume values such as uRadiusPx and uTexelSize
(vec2(1.0/width, 1.0/height)). When using these shaders in an external
host you must set those uniforms from the matching *_uniforms() helper
(e.g. shaders::photophobia_uniforms(strength, width, height)); passing only
strength will produce incorrect (scale-dependent) results. This is the
contract universal-experience relies on.
Crates
| Crate | Path | Role |
|---|---|---|
sensus-core |
crates/core/ |
Pure rendering / DSP core. No filesystem, no subprocess. |
sensus |
crates/cli/ |
CLI binary. Owns image::open, file writes, etc. |
License
MIT (c) 2026 kako-jun. See LICENSE.