docs/ had 26 developer documents flat beside the manual, and the two audiences are very differently sized: most readers want the manual and the gesture reference, a few want the register, the designs and the measurements. The manual and gestures.md stay at the top; everything for someone changing the code moves to docs/dev/, and the two documents that name their own successors — the v0.1 milestone and the UI-refinement plan — go to docs/dev/archive/ rather than being deleted, since both are still cited. docs/README.md is the index, users first. Every reference follows: code comments, Cargo manifests, the workflows, the pre-commit hook, the bench and traceability tools (which locate the repo root by docs/dev/requirements.md now), packaging, the Docker READMEs, CLAUDE.md, CONTRIBUTING.md and the README. The matrix links one level deeper and is regenerated. Links out of the moved documents into the tree gain a level; a link checker over every Markdown file finds none broken.
120 lines
4.7 KiB
Bash
Executable File
120 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Freeze the input dimensions of the face models so tract can parse them.
|
|
#
|
|
# ./tools/fix-face-model-shapes.sh IN.onnx OUT.onnx --input NAME=1,3,640,640
|
|
# ./tools/fix-face-model-shapes.sh IN.onnx OUT.onnx --dim NAME=1
|
|
#
|
|
# The two the face pipeline needs, verified 2026-08-26 (docs/dev/faces.md §12 M1):
|
|
#
|
|
# ... det_500m.onnx scrfd_500m_640.onnx --input input.1=1,3,640,640
|
|
# ... det_2.5g.onnx scrfd_2.5g_640.onnx --input input.1=1,3,640,640
|
|
# ... det_10g.onnx scrfd_10g_640.onnx --input input.1=1,3,640,640
|
|
# ... w600k_mbf.onnx arcface_mbf_b1.onnx --dim None=1
|
|
#
|
|
# And the three eye-state models, verified 2026-09-19 (docs/dev/faces.md §17).
|
|
# The landmark model's batch is the literal "None" like the embedder's; the
|
|
# two classifiers' is a *named* dim_param "batch":
|
|
#
|
|
# ... 2d106det.onnx 2d106det_b1.onnx --dim None=1
|
|
# ... ocec_s.onnx ocec_s_b1.onnx --dim batch=1
|
|
# ... sgc_is_l_48x48.onnx sgc_l_48_b1.onnx --dim batch=1
|
|
#
|
|
# ## Why this exists
|
|
#
|
|
# The InsightFace exports declare dynamic input dimensions — SCRFD's H and W,
|
|
# ArcFace's batch N. **tract cannot parse either graph in that form**, failing
|
|
# at the input node and at the first Conv respectively:
|
|
#
|
|
# scrfd_500m_bnkps.onnx Translating node #0 "input.1" Source ToTypedTranslator
|
|
# arcface_w600k_mbf.onnx Failed analyse for node #139 "Conv_0" ConvHir
|
|
#
|
|
# Both load cleanly once the dims are pinned. This is the same wall dr-segment
|
|
# hit, which is why `tools/export-seg-model.sh` passes `dynamic=False`; here we
|
|
# cannot re-export from PyTorch, because the weights are InsightFace's and the
|
|
# training code is not in the loop, so the dims are rewritten in the ONNX file
|
|
# instead.
|
|
#
|
|
# `make_dynamic_shape_fixed` only edits the declared dimension; it does not
|
|
# retrain, requantise, or change a single weight. The output is numerically the
|
|
# same graph with one shape pinned. SCRFD's *outputs* were already static — the
|
|
# export was made at 640 and only its input forgot to say so — which is why 640
|
|
# is not a free choice here.
|
|
#
|
|
# ## Why it is a script and not a build step
|
|
#
|
|
# Same reason as the segmentation export: the model is not a build input
|
|
# (docs/dev/faces.md §2.2 — the weights are never committed, because InsightFace's
|
|
# grant is non-commercial). This runs once, wherever the user's model lives,
|
|
# and the app loads the result. It exists so the transformation is reproducible
|
|
# rather than a binary someone once produced and nobody can regenerate.
|
|
#
|
|
# Requires `uv`. Everything else is fetched into a throwaway venv, in /var/tmp
|
|
# rather than /tmp — /tmp here is a tmpfs, and onnxruntime is not small.
|
|
set -euo pipefail
|
|
|
|
if [ "$#" -lt 4 ]; then
|
|
sed -n '2,10p' "$0" >&2
|
|
exit 2
|
|
fi
|
|
|
|
IN="$1"; shift
|
|
OUT="$1"; shift
|
|
|
|
[ -f "$IN" ] || { echo "no such model: $IN" >&2; exit 1; }
|
|
|
|
WORK="$(mktemp -d -p /var/tmp fix-face-shapes.XXXXXX)"
|
|
trap 'rm -rf "${WORK}"' EXIT
|
|
|
|
echo "==> venv in ${WORK}"
|
|
uv venv --python 3.12 "${WORK}/venv" >/dev/null
|
|
VIRTUAL_ENV="${WORK}/venv" uv pip install --quiet onnx onnxruntime
|
|
|
|
# Two forms, because the two models need different ones — and which one a graph
|
|
# needs is not a matter of taste:
|
|
#
|
|
# --dim NAME=VALUE for a *named* symbolic dimension.
|
|
# --input NAME=D,D,D,D for a dimension that is dynamic but unnamed.
|
|
#
|
|
# ArcFace declares its batch as the literal dim_param "None", so `--dim` binds
|
|
# it. SCRFD's H and W carry no dim_param at all, so there is no name to bind
|
|
# and the whole input shape has to be restated. Reaching for `--dim` first and
|
|
# getting a silent no-op is the half-hour worth skipping.
|
|
CUR="$IN"
|
|
STEP=0
|
|
while [ "$#" -gt 0 ]; do
|
|
FLAG="$1"; shift
|
|
PAIR="${1:-}"; shift || true
|
|
NAME="${PAIR%%=*}"
|
|
VAL="${PAIR#*=}"
|
|
STEP=$((STEP + 1))
|
|
NEXT="${WORK}/step${STEP}.onnx"
|
|
case "$FLAG" in
|
|
--dim)
|
|
echo "==> dim_param ${NAME} := ${VAL}"
|
|
"${WORK}/venv/bin/python" -m onnxruntime.tools.make_dynamic_shape_fixed \
|
|
--dim_param "${NAME}" --dim_value "${VAL}" "${CUR}" "${NEXT}"
|
|
;;
|
|
--input)
|
|
echo "==> input ${NAME} := ${VAL}"
|
|
"${WORK}/venv/bin/python" -m onnxruntime.tools.make_dynamic_shape_fixed \
|
|
--input_name "${NAME}" --input_shape "${VAL}" "${CUR}" "${NEXT}"
|
|
;;
|
|
*)
|
|
echo "unknown flag ${FLAG} (want --dim or --input)" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
CUR="${NEXT}"
|
|
done
|
|
|
|
cp "${CUR}" "${OUT}"
|
|
echo "==> wrote ${OUT}"
|
|
"${WORK}/venv/bin/python" - "$OUT" <<'PY'
|
|
import sys, onnx
|
|
m = onnx.load(sys.argv[1])
|
|
for vi in list(m.graph.input) + list(m.graph.output):
|
|
dims = [d.dim_value if d.HasField("dim_value") else (d.dim_param or "?")
|
|
for d in vi.type.tensor_type.shape.dim]
|
|
print(f" {vi.name:<24} {dims}")
|
|
PY
|