#!/usr/bin/env python3 """Read one sheet-music image with oemer and print the MusicXML path. oemer asks ONNX Runtime for CUDA first. This wrapper forces the CPU provider so the AppImage does not need a GPU. """ from __future__ import annotations import os import sys import traceback def force_cpu() -> None: import onnxruntime as rt original = rt.InferenceSession def cpu_session(model, providers=None, **kwargs): # type: ignore[no-untyped-def] kwargs.pop("providers", None) return original(model, providers=["CPUExecutionProvider"], **kwargs) rt.InferenceSession = cpu_session # type: ignore[method-assign] def prepare_png(src: str, dest: str) -> None: from PIL import Image, ImageOps with Image.open(src) as image: rgb = ImageOps.exif_transpose(image).convert("RGB") if min(rgb.size) < 80: raise ValueError("This image is too small to read as sheet music.") rgb.thumbnail((2200, 2200)) rgb.save(dest, "PNG") def recognize(png: str, out_dir: str) -> str: from argparse import Namespace from oemer.ete import clear_data, extract def once(deskew: bool) -> str: clear_data() args = Namespace( img_path=png, output_path=out_dir, use_tf=False, save_cache=False, without_deskew=not deskew, ) return extract(args) try: return once(True) except (Exception, SystemExit): traceback.print_exc() return once(False) def main() -> int: if len(sys.argv) != 3: print("usage: run_oemer.py IMAGE OUT_DIR", file=sys.stderr) return 2 src, out_dir = sys.argv[1], sys.argv[2] os.makedirs(out_dir, exist_ok=True) png = os.path.join(out_dir, "page.png") try: prepare_png(src, png) force_cpu() musicxml = recognize(png, out_dir) except Exception as exc: # noqa: BLE001 — surface a single readable failure print(f"{exc.__class__.__name__}: {exc}", file=sys.stderr) traceback.print_exc() return 1 if not musicxml or not os.path.isfile(musicxml): print("oemer did not write MusicXML", file=sys.stderr) return 1 print(f"MUSICXML={musicxml}") return 0 if __name__ == "__main__": raise SystemExit(main())