Skip to content

fix(ai): resolve the ONNX wasm directory against the app, not the route - #2863

Closed
wayfarer3130 wants to merge 2 commits into
cornerstonejs:mainfrom
wayfarer3130:fix/onnx-wasm-path-app-root
Closed

fix(ai): resolve the ONNX wasm directory against the app, not the route#2863
wayfarer3130 wants to merge 2 commits into
cornerstonejs:mainfrom
wayfarer3130:fix/onnx-wasm-path-app-root

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Context

ONNXSegmentationController.getConfig() points ONNX Runtime at its WebAssembly binaries with

ort.env.wasm.wasmPaths = 'ort/';

That prefix is document-relative. The browser resolves it against the current route rather than against the application, so it only lands on the copy of onnxruntime-web/dist when the page sits exactly one segment deep — which is why it works for the examples and for viewer.ohif.org/segmentation.

Problem

A viewer served from a deeper route (/viewer/dicomweb, <PUBLIC_URL>viewer/<dataSource>, …) requests <route>/ort/ort-wasm-*.wasm. Nothing is served there: the SPA fallback answers with index.html, and compiling that as WebAssembly dies with

expected magic word 00 61 73 6d, found 3c 21 64 6f   // "<!do"

ONNX then reports no available backend found, the SAM controller never finishes loading, and the next viewport render throws on the labelmap it never got. Downstream (OHIF/Manta) this reaches the user as a generic "Something went wrong" on the labelmap assist tool, and every consumer whose viewer is not at a single-segment route has to patch it in application code.

The second half of the problem is that the assignment runs from every ONNXSegmentationController construction, so an application that sets ort.env.wasm.wasmPaths itself (a CDN, a versioned path) has its value clobbered — patching it from the app requires trapping the write with an accessor rather than simply assigning.

Change

  • New packages/ai/src/utils/getOrtWasmPaths.ts resolves the prefix to an absolute URL against the base the bundler already uses for the assets it emits — webpack/rspack's public path (__webpack_public_path__), falling back to document.baseURI, and to the bare 'ort/' outside a browser.
  • getConfig() only sets wasmPaths when the application has not already set it.

Why not new URL(..., import.meta.url)

Every other wasm binary here is located the way the codecs and workers do it — new URL('@cornerstonejs/codec-charls/decodewasm', import.meta.url) — letting the bundler resolve, emit and hash the file. That is not available for ONNX at onnxruntime-web@1.17: its exports map publishes only the JavaScript entry points, so

"./dist/ort-wasm-simd.jsep.wasm" is not exported ... from package onnxruntime-web

Applications therefore copy onnxruntime-web/dist next to their bundle (this repo's utils/ExampleRunner/template-config.js copies it to <example>/ort) and point the runtime at the copy. Anchoring that copy to the bundle's public path is the closest equivalent, and it is the same base the emitted asset URLs carry. When onnxruntime-web is eventually bumped to ≥ 1.21, its *.bundle.min.mjs builds resolve their own .wasm through import.meta.url and this whole assignment can be deleted.

Impact

context before after
examples (publicPath: 'auto', ort copied to <example>/ort) <example>/ort/ <example>/ort/ — unchanged
viewer at a one-segment route <root>/ort/ <root>/ort/ — unchanged
viewer at a deeper route <route>/ort/404 / index.html <root>/ort/
app-configured wasmPaths overwritten respected

No new work at load time: one URL resolution, once, inside getConfig(). No change to what is fetched or when.

Testing

  • Resolution verified across the deployment shapes above (root public path + deep route, PUBLIC_URL=/pacs/ + deep route, publicPath: 'auto' as the example runner uses, no bundler with and without a <base> tag, and a non-browser context).
  • Examples resolve to the identical URL they do today, since the example runner leaves publicPath at 'auto' and copies onnxruntime-web/dist beside the bundle.

Summary by CodeRabbit

  • Bug Fixes
    • Improved loading of ONNX Runtime WebAssembly resources across different deployment and bundling configurations.
    • Preserves existing custom resource paths while automatically resolving paths when needed.

`getConfig` set `ort.env.wasm.wasmPaths = 'ort/'`. That prefix is
document-relative, so the browser resolves it against the current route
rather than against the application. It only finds the copy of
`onnxruntime-web/dist` when the page sits exactly one segment deep, which
is why it works for the examples and for `viewer.ohif.org/segmentation`.

A viewer served from a deeper route — `/viewer/dicomweb`, say — requests
`/viewer/ort/ort-wasm-*.wasm`, gets the SPA fallback's `index.html`, and
compiling that as WebAssembly fails with `expected magic word 00 61 73 6d,
found 3c 21 64 6f`. ONNX then reports "no available backend found", the
SAM controller never finishes loading, and the failure surfaces to the
user as a broken labelmap tool.

Resolve the prefix against the base the bundler already uses for the
assets it emits — webpack/rspack's public path, falling back to
`document.baseURI` — which is the directory applications copy
`onnxruntime-web/dist` into. The example runner copies it to
`<example>/ort` and is served with `publicPath: 'auto'`, so examples
resolve to the same URL they do today.

Also stop overwriting a location the application configured: apps serving
the binaries from a CDN or a versioned path had their setting clobbered
from every `ONNXSegmentationController` construction.

Locating the binaries with `new URL(<specifier>, import.meta.url)`, the
way the codec and worker assets are located, is not available here:
`onnxruntime-web@1.17` publishes only its JavaScript entry points through
`exports`, so `onnxruntime-web/dist/ort-wasm-simd.jsep.wasm` does not
resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9df11306-d68d-49e1-a7bc-e4ddbaa8b10f

📥 Commits

Reviewing files that changed from the base of the PR and between e08ad12 and 4f8f44e.

📒 Files selected for processing (1)
  • packages/ai/src/utils/getOrtWasmPaths.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ai/src/utils/getOrtWasmPaths.ts

📝 Walkthrough

Walkthrough

The change adds browser-aware ONNX Runtime WebAssembly path resolution. The segmentation controller preserves an existing configured path and resolves a fallback path through the new utility.

Changes

ONNX Runtime WebAssembly paths

Layer / File(s) Summary
Path resolution and controller wiring
packages/ai/src/utils/getOrtWasmPaths.ts, packages/ai/src/ONNXSegmentationController.ts
The utility resolves the ort/ directory against the bundler public path or document URL. It falls back to the provided directory when URL context is unavailable or invalid. The controller preserves an existing ort.env.wasm.wasmPaths value and otherwise calls getOrtWasmPaths().

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 4f8f4

This localized change anchors ONNX WebAssembly assets to the application base while preserving application-configured paths; no actionable merge-blocking risk remains beyond normal checks.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the ONNX WebAssembly path resolution fix and follows the semantic-release format.
Description check ✅ Passed The description clearly covers context, changes, impact, and testing, although it omits the template checklist section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Resolve a relative public path against `document.baseURI` (falling back to
`location.href`, for workers) rather than against `location.href` alone.
That is the definition webpack and rspack generate for
`__webpack_require__.b`, which is the base `new URL(<specifier>,
import.meta.url)` compiles down to — so the ONNX binaries now resolve
against exactly the same base as the codec wasm.

Only observable with a relative public path and a `<base>` tag; identical
everywhere else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wayfarer3130

Copy link
Copy Markdown
Collaborator Author

Superseded by #2864 — same commits, from a branch on the repo rather than a fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant