--- title: SphinxEyes — Hieroglyph Decoder emoji: 🔆 colorFrom: yellow colorTo: indigo sdk: gradio sdk_version: 5.49.1 app_file: app.py pinned: false license: agpl-3.0 short_description: Detect, order and read Middle Egyptian hieroglyphs --- # SphinxEyes End-to-end reading of Middle Egyptian hieroglyphs from a photograph: ``` photo -> YOLO11l detection (150 Gardiner classes) -> quadrat clustering + reading order -> Viterbi correction (dictionary trie + bigram LM + confusion priors) -> cartouche matching (Needleman-Wunsch vs 41 Beckerath-validated kings) -> [optional] GPT-4o transliteration + English gloss ``` Everything up to the last step runs locally in the Space and is free and unlimited. Only the GPT stage is metered. ## Setup **Hardware:** set the Space to **ZeroGPU**. The detector runs a raw PyTorch forward inside `@spaces.GPU`; on CPU it still works but takes ~3–4 s. **Secret:** add `OPENAI_API_KEY` under *Settings → Secrets* to enable transliteration. Without it the app runs normally and the toggle reports that the stage is unavailable — it never errors out. Optional environment overrides: | Variable | Default | Meaning | |---|---|---| | `OPENAI_MODEL` | `gpt-4o` | Transliteration model | | `TRANSLIT_MAX_SIGNS` | `20` | Signs per LLM chunk | | `TRANSLIT_TEMPERATURE` | `0.1` | Sampling temperature | | `SPHINX_PER_IP_DAILY` | `3` | Transliterations per visitor per UTC day | | `SPHINX_GLOBAL_DAILY` | `100` | Space-wide daily ceiling (key-spend guard) | | `SPHINX_GPU_SECONDS` | `60` | ZeroGPU duration budget per forward | | `SPHINX_WEIGHTS` | `src/artifacts/best_modelv10.pt` | Detector checkpoint | | `SPHINX_ONNX` | *(unset)* | Local dev only — use an ONNX file on CPU instead of torch | ## Using it **Layout is required** and cannot be auto-detected reliably — the geometric voter misvotes on real walls, and a wrong choice scrambles the reading order. Pick `columns` for vertical text, `rows` for horizontal. **Reading direction** is geometrically undecidable. Right-to-left is the default; signs normally face the *start* of the line, so if the figures look left, choose `ltr`. The archaeological-context panel is optional but materially improves the GPT reading — a temple wall favours royal formulae, pyramid texts favour Old Kingdom spellings, and a known reign anchors the titulary. Fields left as `unknown` are explicitly flagged to the model as unsupplied so it does not invent them. ## Rate limiting The `3/day` cap is **best-effort cost control, not security**: state is in-memory (a Space restart or sleep resets it) and identity is the hashed client IP, which is shared behind NAT and rotatable. `SPHINX_GLOBAL_DAILY` is the backstop that actually bounds API spend. A failed or unavailable LLM call does not consume quota. ## Model `best_modelv10.pt` — YOLO11l, 150 classes (148 Gardiner + `cartouche` + `unknown`), imgsz 1024, trained with Ultralytics 8.4.102. Class ordering is `Aa1=0, a1=1 … Aa15=149` and is identical to v9, including the three transmuted indices (30 `m4`, 61 `f34`, 70 `o29`). **Known gap:** the substitution priors ship as `confusion_matrix_v9_normalized.csv` — no v10 confusion matrix exists yet. It feeds only the Viterbi substitution cost, and since v10 shares v9's exact class ordering the priors stay meaningful, but they are one generation stale. There are also no published v10 validation metrics. ### Why a raw forward, not `model.predict()` The spatial layer needs the **top-3** class candidates per detection to feed the Viterbi corrector. `predict()` returns top-1 after its own NMS, which would gut the correction layer. So `src/infer_torch.py` runs `DetectionModel.forward` to get the raw `[1, 154, 21504]` tensor — the same contract as an ONNX export with `nms=False` — and hands it to the unchanged `spatial_logic.postprocess_onnx`. ## Layout ``` app.py Gradio UI + two-stage orchestration src/ flat, NO __init__.py (see below) pipeline.py SphinxPipeline; detection backend is injected infer_torch.py ZeroGPU backend (v10 .pt) llm.py GPT-4o transliteration (env-var config) ratelimit.py per-IP + global daily cap spatial_logic.py detection postprocess, NMS, quadrats, reading order sphinx_corrector.py Viterbi + bigram LM + linguistic priors sphinx_trie.py lexicon trie (must stay importable as `sphinx_trie`) cartouche_matcher.py Needleman-Wunsch vs royal_names.json layout_detector.py fallback layout voter enhance_img.py CLAHE/denoise presets (off by default) royal_names.json determinatives.json initial_particles.json artifacts/ weights, class map, trie, corpus, confusion matrix ``` `src/` has no `__init__.py` on purpose: `sphinx_trie_v4.pkl` has the module path `sphinx_trie` baked into the pickle, so importing it as `src.sphinx_trie` makes `pickle.load` raise `ModuleNotFoundError`. `app.py` puts `src/` on `sys.path` and imports flat. Image enhancement is off by default and should stay that way: A/B testing showed it deletes real signs and hallucinates others on clean photographs. ## Local development `onnxruntime` is not in `requirements.txt`, but the ONNX backend lets you verify the pipeline without installing torch: ```bash pip install onnxruntime python src/pipeline.py /path/to/Unas1c.jpg columns # SPHINX_ONNX or repo default SPHINX_ONNX=/path/to/best_model_v9.onnx python app.py ``` Both backends letterbox identically and end in `postprocess_onnx`, so a result that holds under ONNX holds under torch. ## License The Space is **AGPL-3.0**: it loads a YOLO checkpoint via [Ultralytics](https://github.com/ultralytics/ultralytics), which is AGPL-3.0 licensed, and that obligation extends to this deployed service.