Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6da116518f |
No files matched your search
@@ -7,14 +7,14 @@ The usual way to run it is one Linux AppImage. That file contains the window, th
|
|||||||
## Run the AppImage
|
## Run the AppImage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x Recorder-Tab-Converter-0.1.0.AppImage
|
chmod +x Recorder-Tab-Converter-0.1.1.AppImage
|
||||||
./Recorder-Tab-Converter-0.1.0.AppImage
|
./Recorder-Tab-Converter-0.1.1.AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
If the file will not start because FUSE is missing:
|
If the file will not start because FUSE is missing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
APPIMAGE_EXTRACT_AND_RUN=1 ./Recorder-Tab-Converter-0.1.0.AppImage
|
APPIMAGE_EXTRACT_AND_RUN=1 ./Recorder-Tab-Converter-0.1.1.AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
Open **Twinkle, Twinkle, Little Star** or **Ode to Joy** to see tabs immediately. Or drop a PNG, JPG, or WEBP of a page. The note list under the tab is editable. Fix a bad pitch or duration and the fingerings update. If a photo cannot be read, the app says so and does not invent notes.
|
Open **Twinkle, Twinkle, Little Star** or **Ode to Joy** to see tabs immediately. Or drop a PNG, JPG, or WEBP of a page. The note list under the tab is editable. Fix a bad pitch or duration and the fingerings update. If a photo cannot be read, the app says so and does not invent notes.
|
||||||
@@ -30,9 +30,11 @@ From a checkout, one command:
|
|||||||
That installs Node packages, downloads a relocatable Python plus oemer and its ONNX models, builds the app, and writes:
|
That installs Node packages, downloads a relocatable Python plus oemer and its ONNX models, builds the app, and writes:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
dist/Recorder-Tab-Converter-0.1.0.AppImage
|
dist/Recorder-Tab-Converter-0.1.1.AppImage
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The U-Net checkpoint from oemer 0.1.8 has ConvTranspose pads `[0, 0, -1, -1]`. The build rewrites those nodes to pads `[0, 0, 0, 0]` and `output_padding` `[1, 1]` before packaging. That is the same output size, and it is what current ONNX Runtime will load. The rewrite does not change fingerings.
|
||||||
|
|
||||||
The build needs network access the first time. The AppImage is not committed; keep it under 600MB.
|
The build needs network access the first time. The AppImage is not committed; keep it under 600MB.
|
||||||
|
|
||||||
## Develop in a browser
|
## Develop in a browser
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "recorder-tab-converter",
|
"name": "recorder-tab-converter",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "recorder-tab-converter",
|
"name": "recorder-tab-converter",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.8.0",
|
"@base-ui/react": "^1.8.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "recorder-tab-converter",
|
"name": "recorder-tab-converter",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Turn a sheet-music image into soprano recorder fingering tabs, with rhythm kept.",
|
"description": "Turn a sheet-music image into soprano recorder fingering tabs, with rhythm kept.",
|
||||||
"main": "electron/main.cjs",
|
"main": "electron/main.cjs",
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ ROOT="$(pwd)"
|
|||||||
echo "== Install Node packages =="
|
echo "== Install Node packages =="
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
PY="$ROOT/build/python/bin/python3"
|
|
||||||
if ! "$PY" -c "import oemer.ete, onnxruntime" >/dev/null 2>&1; then
|
|
||||||
echo "== Download a relocatable Python and oemer =="
|
|
||||||
mkdir -p "$ROOT/build/cache"
|
mkdir -p "$ROOT/build/cache"
|
||||||
TARBALL="$ROOT/build/cache/cpython.tar.gz"
|
TARBALL="$ROOT/build/cache/cpython.tar.gz"
|
||||||
URL="https://github.com/astral-sh/python-build-standalone/releases/download/20261003/cpython-3.12.15%2B20261003-x86_64-unknown-linux-gnu-install_only.tar.gz"
|
URL="https://github.com/astral-sh/python-build-standalone/releases/download/20261003/cpython-3.12.15%2B20261003-x86_64-unknown-linux-gnu-install_only.tar.gz"
|
||||||
|
|
||||||
|
PY="$ROOT/build/python/bin/python3"
|
||||||
|
if ! "$PY" -c "import oemer.ete, onnxruntime" >/dev/null 2>&1; then
|
||||||
|
echo "== Download a relocatable Python and oemer =="
|
||||||
if [[ ! -f "$TARBALL" ]]; then
|
if [[ ! -f "$TARBALL" ]]; then
|
||||||
curl -fL --retry 3 -o "$TARBALL" "$URL"
|
curl -fL --retry 3 -o "$TARBALL" "$URL"
|
||||||
fi
|
fi
|
||||||
@@ -36,6 +37,38 @@ if [[ ! -f "$SITE/checkpoints/seg_net/model.onnx" ]]; then
|
|||||||
curl -fL --retry 3 -o "$SITE/checkpoints/seg_net/model.onnx" \
|
curl -fL --retry 3 -o "$SITE/checkpoints/seg_net/model.onnx" \
|
||||||
"https://github.com/BreezeWhite/oemer/releases/download/checkpoints/2nd_model.onnx"
|
"https://github.com/BreezeWhite/oemer/releases/download/checkpoints/2nd_model.onnx"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "== Rewrite U-Net ConvTranspose pads =="
|
||||||
|
# oemer 0.1.8's unet has ConvTranspose pads [0, 0, -1, -1]. ONNX Runtime
|
||||||
|
# 1.30+ rejects those. The rewrite keeps the same output size. onnx itself
|
||||||
|
# is not packaged.
|
||||||
|
if ! "$PY" -m pip --version >/dev/null 2>&1; then
|
||||||
|
if [[ ! -f "$TARBALL" ]]; then
|
||||||
|
echo "pip was removed and the Python tarball is missing, so the U-Net cannot be rewritten." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PIP_RESTORE="$ROOT/build/cache/pip-restore"
|
||||||
|
rm -rf "$PIP_RESTORE"
|
||||||
|
mkdir -p "$PIP_RESTORE"
|
||||||
|
tar -xzf "$TARBALL" -C "$PIP_RESTORE" --wildcards \
|
||||||
|
'python/lib/python3.12/site-packages/pip' \
|
||||||
|
'python/lib/python3.12/site-packages/pip-*.dist-info'
|
||||||
|
rm -rf "$ROOT/build/python/lib/python3.12/site-packages/pip" \
|
||||||
|
"$ROOT/build/python/lib/python3.12/site-packages/pip-"*.dist-info
|
||||||
|
cp -a "$PIP_RESTORE/python/lib/python3.12/site-packages/pip" \
|
||||||
|
"$ROOT/build/python/lib/python3.12/site-packages/"
|
||||||
|
cp -a "$PIP_RESTORE"/python/lib/python3.12/site-packages/pip-*.dist-info \
|
||||||
|
"$ROOT/build/python/lib/python3.12/site-packages/"
|
||||||
|
rm -rf "$PIP_RESTORE"
|
||||||
|
fi
|
||||||
|
PATCH_PY="$ROOT/build/cache/onnx-patch"
|
||||||
|
rm -rf "$PATCH_PY"
|
||||||
|
"$PY" -m pip install --disable-pip-version-check --target "$PATCH_PY" 'onnx>=1.16,<2'
|
||||||
|
PYTHONPATH="$PATCH_PY${PYTHONPATH:+:$PYTHONPATH}" \
|
||||||
|
"$PY" "$ROOT/scripts/patch_oemer_unet.py" \
|
||||||
|
"$SITE/checkpoints/unet_big/model.onnx"
|
||||||
|
rm -rf "$PATCH_PY"
|
||||||
|
|
||||||
MPLBACKEND=Agg "$PY" -c "import oemer.ete, cv2; from cv2.typing import RotatedRect; print('oemer', cv2.__version__)"
|
MPLBACKEND=Agg "$PY" -c "import oemer.ete, cv2; from cv2.typing import RotatedRect; print('oemer', cv2.__version__)"
|
||||||
|
|
||||||
echo "== Trim the Python prefix =="
|
echo "== Trim the Python prefix =="
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Rewrite oemer U-Net ConvTranspose pads so ONNX Runtime can load the model.
|
||||||
|
|
||||||
|
oemer 0.1.8's 1st_model.onnx ships ConvTranspose nodes with pads
|
||||||
|
[0, 0, -1, -1]. ONNX Runtime 1.30 and newer reject those during shape
|
||||||
|
inference. pads [0, 0, 0, 0] with output_padding [1, 1] is the same
|
||||||
|
output size for these stride-2, kernel-1 nodes. Weights are left as they are.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import onnx
|
||||||
|
from onnx import helper
|
||||||
|
|
||||||
|
BAD_PADS = [0, 0, -1, -1]
|
||||||
|
FIXED_PADS = [0, 0, 0, 0]
|
||||||
|
FIXED_OUTPUT_PADDING = [1, 1]
|
||||||
|
CRASH_NODE = "model/conv2d_transpose_1/conv2d_transpose"
|
||||||
|
|
||||||
|
|
||||||
|
def _attr(node: onnx.NodeProto, name: str) -> onnx.AttributeProto | None:
|
||||||
|
for attr in node.attribute:
|
||||||
|
if attr.name == name:
|
||||||
|
return attr
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _ints(node: onnx.NodeProto, name: str) -> list[int] | None:
|
||||||
|
attr = _attr(node, name)
|
||||||
|
if attr is None:
|
||||||
|
return None
|
||||||
|
return list(attr.ints)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_ints(attr: onnx.AttributeProto, values: list[int]) -> None:
|
||||||
|
del attr.ints[:]
|
||||||
|
attr.ints.extend(values)
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite(model_path: Path) -> int:
|
||||||
|
model = onnx.load(model_path)
|
||||||
|
changed = 0
|
||||||
|
for node in model.graph.node:
|
||||||
|
if node.op_type != "ConvTranspose":
|
||||||
|
continue
|
||||||
|
pads = _ints(node, "pads")
|
||||||
|
if pads is None or pads == FIXED_PADS:
|
||||||
|
continue
|
||||||
|
if pads != BAD_PADS:
|
||||||
|
if any(value < 0 for value in pads):
|
||||||
|
raise SystemExit(
|
||||||
|
f"{model_path}: {node.name} has unexpected negative pads {pads}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
pads_attr = _attr(node, "pads")
|
||||||
|
if pads_attr is None:
|
||||||
|
raise SystemExit(f"{model_path}: {node.name} is missing pads")
|
||||||
|
_set_ints(pads_attr, FIXED_PADS)
|
||||||
|
output_padding = _attr(node, "output_padding")
|
||||||
|
if output_padding is None:
|
||||||
|
node.attribute.append(
|
||||||
|
helper.make_attribute("output_padding", FIXED_OUTPUT_PADDING)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_set_ints(output_padding, FIXED_OUTPUT_PADDING)
|
||||||
|
changed += 1
|
||||||
|
print(
|
||||||
|
f"rewrote {node.name}: pads {FIXED_PADS} output_padding {FIXED_OUTPUT_PADDING}"
|
||||||
|
)
|
||||||
|
if changed:
|
||||||
|
onnx.save(model, model_path)
|
||||||
|
verify(model_path)
|
||||||
|
print(f"rewrote {changed} ConvTranspose node(s) in {model_path}")
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
def verify(model_path: Path) -> None:
|
||||||
|
model = onnx.load(model_path)
|
||||||
|
found = False
|
||||||
|
for node in model.graph.node:
|
||||||
|
if node.op_type != "ConvTranspose":
|
||||||
|
continue
|
||||||
|
pads = _ints(node, "pads") or []
|
||||||
|
if any(value < 0 for value in pads):
|
||||||
|
raise SystemExit(f"{model_path}: {node.name} still has negative pads {pads}")
|
||||||
|
if node.name != CRASH_NODE:
|
||||||
|
continue
|
||||||
|
found = True
|
||||||
|
output_padding = _ints(node, "output_padding")
|
||||||
|
if pads != FIXED_PADS or output_padding != FIXED_OUTPUT_PADDING:
|
||||||
|
raise SystemExit(
|
||||||
|
f"{model_path}: {node.name} is {pads} / {output_padding}, "
|
||||||
|
f"expected {FIXED_PADS} / {FIXED_OUTPUT_PADDING}"
|
||||||
|
)
|
||||||
|
if not found:
|
||||||
|
raise SystemExit(f"{model_path}: missing {CRASH_NODE}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("usage: patch_oemer_unet.py MODEL.onnx", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
if not path.is_file():
|
||||||
|
print(f"missing model: {path}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
rewrite(path)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in new issue
Block a user