Run Python ML Models in the Browser with Pyodide: A Complete Guide

This article explains how to use Pyodide—a WebAssembly‑based Python runtime—to run complex machine‑learning model pre‑ and post‑processing directly in the browser, covering memory layout, JavaScript‑Python interoperation, package loading, OpenCV usage, inference engines, persistent storage, and wheel packaging.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Run Python ML Models in the Browser with Pyodide: A Complete Guide

Background

With the growing use of machine learning, many JavaScript frameworks can run inference in the browser. Front‑end developers often want to reuse Python models that normally run on the server, but translating complex pre‑ and post‑processing code to JavaScript is time‑consuming.

Pyodide, a WebAssembly‑based scientific‑computing framework, allows native Python code (including numpy, scipy, etc.) to run directly in the browser, eliminating the need for manual translation.

Principle

Pyodide is a WebAssembly application built from CPython using Emscripten. It bundles many scientific‑computing PyPI packages as WASM modules, enabling Python statements to be executed in the browser. The architecture is shown below.

1 WASM memory layout

This is the linear memory layout of WASM. The data segment starts at 0x400, the function table is also placed there, and the stack and heap have defined start addresses. The key point for developers is the mutual access between JavaScript memory and WASM memory.

2 JavaScript and Python inter‑operation

For security, WASM runs in a sandbox and cannot access JavaScript memory, while JavaScript can access WASM memory (a "one‑way" memory access). Pyodide introduces PyProxy and JsProxy objects that act like pointers, allowing each side to reference objects on the other side.

Creating a PyProxy in JavaScript:

const arr_pyproxy = pyodide.globals.get('arr'); // 'arr' is a global Python object

Creating a JsProxy in Python:

import js
from js import foo   # 'foo' is a global JavaScript object

Type conversion occurs at three levels:

Automatic conversion : simple types (numbers, strings, booleans) are copied directly.

Semi‑automatic conversion : built‑in containers (list, dict, numpy.ndarray) require explicit pyodide.to_js() or to_py() calls.

Manual conversion : custom classes or functions are accessed via proxy objects; operations are performed through simulated syntax (e.g., let a = new XXX() in JS becomes a = XXX.new() in Python).

Practice

1 Initialize Python

Load required JavaScript libraries dynamically (opencv.js, onnxruntime.js, pyodide.js), then initialize Pyodide and install micropip:

function loadJS(url, callback) {
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.onload = function() { callback && callback(); };
  script.src = url;
  document.getElementsByTagName('head')[0].appendChild(script);
}
// Load opencv.js, onnxruntime.js, pyodide.js …
loadJS('https://.../opencv.js', () => console.log('js load ok'));
loadJS('https://.../onnx.min.js', () => console.log('js load ok'));
loadJS('https://.../pyodide.js', () => console.log('js load ok'));
pyodide = await loadPyodide({ indexURL: 'https://.../pyodide/0.18.0/' });
await pyodide.loadPackage(['micropip']);

2 Load PyPI packages

Standard library modules are available by default. Third‑party pure‑Python packages can be installed with micropip.install(). Packages with C extensions require pre‑compiled wheels listed in the official Pyodide package repository; otherwise they must be compiled manually.

3 Using OpenCV

Because the official Pyodide build does not include OpenCV, the article demonstrates calling opencv.js from Python via proxies. An example creates a synthetic 1080p image, resizes it with cv2.resize, and converts the result back to a NumPy array.

await pyodide.runPythonAsync(`
# Create a 1080p image
h, w = 1080, 1920
img = np.arange(h * w * 3, dtype=np.uint8).reshape(h, w, 3)
# Resize using opencv.js
h_small, w_small = 108, 192
mat = cv2.matFromArray(h, w, cv2.CV_8UC3, pyodide.to_js(img.reshape(h * w * 3)))
dst = cv2.Mat.new(h_small, w_small, cv2.CV_8UC3)
cv2.resize(mat, dst, cv2.Size.new(w_small, h_small), 0, 0, cv2.INTER_NEAREST)
small_img = np.asarray(dst.data.to_py()).reshape(h_small, w_small, 3)
`);

4 Inference engine

Load an ONNX model with onnxruntime.js and run inference from Python:

await pyodide.runPythonAsync(`
model_url = "<model_url>"
session = onnxruntime.InferenceSession.new()
session.loadModel(model_url)
session.run(...)
`);

5 Mount persistent filesystem

Pyodide provides a persistent IDBFS filesystem. Create a mount point, write files, and sync to storage so data survives page reloads.

// Create mount point
pyodide.FS.mkdir('/mnt');
// Mount IDBFS
pyodide.FS.mount(pyodide.FS.filesystems.IDBFS, {}, '/mnt');
// Write a file
pyodide.FS.writeFile('/mnt/test.txt', 'hello world');
// Persist changes
pyodide.FS.syncfs(function(err) { console.log(err); });

6 Build a wheel package

When a project consists of multiple Python files, package them into a wheel and install via micropip.install(). The wheel must contain an __init__.py file.

micropip.install("https://foo.com/bar-1.2.3-xxx.whl")
from bar import ...

Existing Limitations

Python runtime initialization can take several seconds depending on network conditions.

Not all PyPI packages are available; C‑extension packages often need manual compilation.

Common libraries such as OpenCV are not pre‑compiled for Pyodide.

Calling JavaScript libraries from Python may incur memory copy overhead, especially for large arrays.

API mismatches between Python and JavaScript libraries may require additional adaptation.

Conclusion

Despite these drawbacks, Pyodide offers a highly efficient way to port complex machine‑learning models to the browser with near‑one‑to‑one code fidelity, making it suitable for many business scenarios and accelerating the adoption of ML technologies on the front‑end.

Useful links:

Test page: https://test-bucket-duplicate.oss-cn-hangzhou.aliyuncs.com/public/pyodide/test.html

Documentation: https://pyodide.org/en/stable/usage/type-conversions.html

Official compiled packages list: https://github.com/pyodide/pyodide/tree/main/packages

Filesystem manual: https://emscripten.org/docs/api_reference/Filesystem-API.html

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontend developmentWebAssemblyPyodidePython in Browser
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.