RISO — Risomorphism-1911 ASCII Pipeline
Package: ascii-art-pipeline (installed at ~/riso/)
CLI: ascii-pipeline
Base grid: 48×24 (Herm avatar default)
Pure-Python ASCII rendering with edge-aware downsampling, four curated presets, and automatic quality verdicts. No external binaries required. See references/preprocessing.md for the session-specific image preprocessing patterns.
Preset Quick Reference
| Preset | Charset | Best for | Quality gate |
|---|---|---|---|
stroke-clarity |
12 glyphs (@$#MHAGXS532;:,) |
High-contrast silhouettes, portraits, safe default | high-contrast |
d30-dense |
68 glyphs (extended D30) | Dense cyber-noir texture, HUD aesthetic | high-contrast (edge-aware at scale≥8) |
braille-detail |
Unicode Braille (U+2800–U+28FF) | Maximum detail, halftone effect | braille-dominant |
eikon-motion |
D30 charset | Video → animated eikon for mirror/avatar | high-contrast per frame |
When to use what
| You want... | Use... |
|---|---|
| Clean readable ASCII from a photo | stroke-clarity — default, fastest, always works |
| Dense moody ASCII (cyber-noir look) | d30-dense — richer texture, slower at high scales |
| Character portrait with legible face | stroke-clarity — the 12-glyph charset produces cleaner silhouettes than d30-dense face detail at scale ≤2 |
| Maximum detail in the cell grid | braille-detail — needs terminal Braille support |
| Animated avatar for mirror | eikon-motion via build-eikon-from-video |
Preprocessing for Clean Backgrounds
CRITICAL: The pipeline maps pure black (0,0,0) to @ (the darkest character), NOT to spaces. The converter at converter.py:_map_intensity maps pixel brightness 0–255 linearly to charset indices with floor(pixels * n). Black → index 0 → @. There is no special handling for black-as-background.
If you send an image with a black background, the output fills with @, $, # — the background looks like solid dense character fill, not empty space.
Fix: set background to white (255) before running through the pipeline. White maps to the lightest characters (. / , / ;) which are visually near-invisible. The autocontrast preprocess then stretches the character's contrast range independently, so the subject stays crisp.
Preprocessing Workflow for Subject Isolation
- Crop tight to the subject — remove background at the image level first
- Analyze brightness histogram — find the valley between background peak and subject peak
- Threshold — create a boolean mask with the threshold set at that valley:
gray = np.mean(arr, axis=2) mask = gray > threshold # True = subject, False = background - Set background to white — replace background pixels with (255,255,255):
result = np.full_like(arr, 255) # all white for c in range(3): result[:,:,c] = np.where(mask, arr[:,:,c], 255) - Median filter — smooth mask boundary artifacts:
Image.fromarray(result).filter(ImageFilter.MedianFilter(3)) - Run through pipeline —
stroke-clarityat scale 2 (96×48) is a good starting point for portraits
Quality Check
After rendering, inspect fill_ratio in the diagnostics JSON:
- 0.65–0.75: clean result — ~25–35% background rendered as space
- ~1.0: background wasn't removed — redo the threshold with a higher value
- Heavy ratio > 0.50 with fill near 1.0: likely
@/$/#background fill, not character
Commands
Render a still image
ascii-pipeline render-image \
--input <image> \
--preset <preset> \
--out <output.txt> \
[--preview-out preview.png] \
[--diagnostics-out metrics.json] \
[--scale N] # default 1, range 1–16
[--fullsize] # alias for --scale 4 (192×96)
Workflow:
- Preprocess image if background needs isolation (see above)
- Run with
--diagnostics-outto get quality verdict - Check verdict:
high-contrast→ ship;low-contrast-garble-risk→ increase scale or switch preset - Use
--preview-outfor side-by-side visual QA
Diagnose an existing file
ascii-pipeline diagnose --input <file.txt> --pretty
Checks dimensions, glyph diversity, fill ratio, heavy/light balance. Verdict is authoritative.
Render preview from text/eikon
ascii-pipeline render-preview --input <file.txt> --out preview.png
Build animated eikon from video (mirror workflow)
ascii-pipeline build-eikon-from-video \
--video <input.mp4> \
--fps 24 \
--states 3 \
--id <eikon-name> \
[--grid 192x96] \
[--charset d30-dense]
Outputs: <id>.eikon + <id>-player.html (offline HTML5 canvas player).
Workflow for mirror deployment:
- Use
build-eikon-from-videowith source video - Output
.eikonfile can be served to the mirror - HTML player is self-contained (base64-encoded, no server needed)
Scaling Guide
| Scale N | Grid (W×H) | Name | When |
|---|---|---|---|
| 1 | 48×24 | avatar | Default, Herm avatar size, chat-friendly |
| 2 | 96×48 | compact | Slightly more detail, good for portraits |
| 4 | 192×96 | fullsize | Showcase, poster, eikon master |
| 8 | 384×192 | large | High-fidelity showcase |
| 16 | 768×384 | max | Archival, print — only with stroke-clarity |
Performance: d30-dense at scale ≥8 is slow and memory-heavy (up to 4GB at N=16). 0.3s at N=16). Prefer stroke-clarity stays fast at all scales (stroke-clarity for quick previews, then switch to d30-dense for the final render.
Quality Gates
| Verdict | Meaning | Action |
|---|---|---|
high-contrast |
Production-safe | Ship |
low-contrast-garble-risk |
Edge washout detected | Increase scale or switch to stroke-clarity |
braille-dominant |
Braille preset, 4× resolution achieved | Accept (Braille mode) |
Always run --diagnostics-out before shipping to mirror or avatar. Don't ship low-contrast-garble-risk.
Integration with Other Skills
ascii-artskill — Userisoinstead ofjp2a/ascii-image-converterwhen you want quality-gated output with preset control. Theascii-artskill handles text banners, cowsay, and borders — this handles high-fidelity image → ASCII.ascii-videoskill —riso'sbuild-eikon-from-videois a lighter alternative to the full ascii-video pipeline when you just need an animated eikon for the mirror (no scene composition, shaders, or audio reactivity needed).animated-eikon-from-baseskill — Upstream pipeline that generates real animated video clips (idle/listening/thinking/speaking/working/error) from a single base image via fal.ai/Kling. Once you have the MP4s, either feed them throughriso'sbuild-eikon-from-videofor ASCII eikon conversion, or install them directly into Herm Studio source at~/.hermes/eikons/<name>/source/for native rendering. riso handles ASCII/braille eikons; animated-eikon-from-base handles real video eikons — complementary, not competing.animated-eikon-from-baseskill — Use this upstream to generate animated video clips from a single base image via fal.ai/Kling (idle/listening/thinking/speaking/working/error). Once you have those MP4s, feed them intoriso'sbuild-eikon-from-videofor ASCII eikon conversion, or install them directly into Herm Studio's source folder (~/.hermes/eikons/<name>/source/) for native rendering.
Pitfalls
- Black background →
@fill, NOT spaces — The core converter maps black (0) → index 0 →@. If the output is@-dense with fill_ratio ~1.0, the background is pure black. Set it to white (255) instead. This is the #1 gotcha. d30-denseat scale ≥8 can memory-spike — intermediate grid goes to 6144×3072 at N=16. Usestroke-clarityat N=16 instead.stroke-claritybeatsd30-densefor portraits — The 12-glyph charset produces cleaner face silhouettes.d30-densewith 68 glyphs adds texture noise that obscures facial features at scale ≤2.- Edge-aware downsampling is automatic for
d30-denseat scale ≥8 — don't try to override it, it's wired into the preset. braille-detailneeds a Braille-capable terminal — standard terminals don't render U+2800+ well. Use for file output / preview PNG, not terminal display.- Eikon videos require source MP4 to be high-contrast, well-lit, minimal motion blur — the ASCII pipeline amplifies noise.
- Clean up temp frames after
build-eikon-from-video:rm -rf tmp/in the working directory.