viobench 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. viobench/__init__.py +26 -0
  2. viobench/__main__.py +5 -0
  3. viobench/solution/examples/native/cpp_minimal/CMakeLists.txt +12 -0
  4. viobench/solution/examples/native/cpp_minimal/README.md +10 -0
  5. viobench/solution/examples/native/cpp_minimal/solution.cpp +97 -0
  6. viobench/solution/examples/python/minimal/solution.py +29 -0
  7. viobench/solution/runtime/native/README.md +22 -0
  8. viobench/solution/runtime/native/build.sh +18 -0
  9. viobench/solution/runtime/native/viobench_abi.h +51 -0
  10. viobench/solution/runtime/native/viobench_debug_main.c +136 -0
  11. viobench/solution/runtime/native/viobench_reactor.cmake +24 -0
  12. viobench/solution/runtime/python/README.md +55 -0
  13. viobench/solution/runtime/python/build.sh +9 -0
  14. viobench/solution/runtime/python/bundler/__init__.py +10 -0
  15. viobench/solution/runtime/python/bundler/bundle.py +159 -0
  16. viobench/solution/runtime/python/bundler/embed.py +75 -0
  17. viobench/solution/runtime/python/cpython/build-wasm.sh +137 -0
  18. viobench/solution/runtime/python/cpython/patches/numpy.patch +99 -0
  19. viobench/solution/runtime/python/cpython/patches/opencv.patch +54 -0
  20. viobench/solution/runtime/python/cpython/python.sh +5 -0
  21. viobench/solution/runtime/python/cpython/scripts/collect_numpy.py +33 -0
  22. viobench/solution/runtime/python/cpython/scripts/freeze.py +38 -0
  23. viobench/solution/runtime/python/cpython/scripts/link.py +69 -0
  24. viobench/solution/runtime/python/cpython/smoke_vo.py +44 -0
  25. viobench/solution/runtime/python/cpython/test.sh +39 -0
  26. viobench/solution/runtime/python/guest/viobench_guest.py +167 -0
  27. viobench/solution/runtime/python/reactor.c +160 -0
  28. viobench/solution/toolchain/download_dependencies.sh +113 -0
  29. viobench/solution/workspace.py +86 -0
  30. viobench/tools/__init__.py +12 -0
  31. viobench/tools/abi/__init__.py +39 -0
  32. viobench/tools/abi/config.py +73 -0
  33. viobench/tools/abi/module.py +50 -0
  34. viobench/tools/abi/wasm_binary.py +224 -0
  35. viobench/tools/cli.py +334 -0
  36. viobench/tools/config.json +15 -0
  37. viobench/tools/datasets/__init__.py +11 -0
  38. viobench/tools/datasets/episode.py +220 -0
  39. viobench/tools/datasets/feed.py +34 -0
  40. viobench/tools/datasets/generate/__init__.py +1 -0
  41. viobench/tools/datasets/generate/hyperdrone_world.h.in +50 -0
  42. viobench/tools/datasets/generate/simulation.py +396 -0
  43. viobench/tools/datasets/regularize.py +87 -0
  44. viobench/tools/datasets/storage.py +288 -0
  45. viobench/tools/geometry.py +105 -0
  46. viobench/tools/runner/__init__.py +9 -0
  47. viobench/tools/runner/check.py +52 -0
  48. viobench/tools/runner/direct.py +157 -0
  49. viobench/tools/runner/engine.py +62 -0
  50. viobench/tools/runner/leaderboard.py +94 -0
  51. viobench/tools/runner/metrics.py +136 -0
  52. viobench/tools/runner/run.py +188 -0
  53. viobench/tools/runner/sandbox.py +263 -0
  54. viobench/tools/runner/score.py +59 -0
  55. viobench/tools/runner/video.py +104 -0
  56. viobench/tools/server/__init__.py +1 -0
  57. viobench/tools/server/app.py +128 -0
  58. viobench/tools/server/config.py +45 -0
  59. viobench/tools/server/store.py +159 -0
  60. viobench/tools/server/teams.py +30 -0
  61. viobench/tools/server/ui/app.js +461 -0
  62. viobench/tools/server/ui/index.html +76 -0
  63. viobench/tools/server/ui/style.css +101 -0
  64. viobench/tools/server/worker.py +86 -0
  65. viobench-0.0.1.dist-info/METADATA +58 -0
  66. viobench-0.0.1.dist-info/RECORD +69 -0
  67. viobench-0.0.1.dist-info/WHEEL +5 -0
  68. viobench-0.0.1.dist-info/entry_points.txt +2 -0
  69. viobench-0.0.1.dist-info/top_level.txt +1 -0
viobench/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """viobench: compute-aware relative state estimation challenge.
2
+
3
+ Two halves, split at the WebAssembly boundary:
4
+ - `viobench.tools` host side — runs, scores, serves: `abi` (the contract), `datasets`, `runner`, `server`, `cli`.
5
+ - `viobench.solution` guest side — how a solution .wasm is built: toolchain, runtime/{native,python}, examples.
6
+ Mostly files, not modules; the Python runtime's bundler is the one importable piece.
7
+
8
+ `import viobench` gives the host-side essentials: Engine, Sandbox, Limits, StepResult, Episode, read_episode, evaluate.
9
+ """
10
+ from .tools import abi
11
+ from .tools.runner.engine import Engine
12
+ from .tools.runner.sandbox import Limits, Sandbox, SandboxError, SandboxTrap, StepResult
13
+
14
+ __all__ = ["abi", "Engine", "Limits", "Sandbox", "SandboxError", "SandboxTrap", "StepResult", "Episode", "read_episode", "evaluate"]
15
+
16
+
17
+ def __getattr__(name): # heavier optional dependencies (cv2, yaml) load on first use
18
+ if name in ("Episode", "read_episode"):
19
+ from .tools import datasets
20
+
21
+ return getattr(datasets, name)
22
+ if name == "evaluate":
23
+ from .tools.runner.run import evaluate
24
+
25
+ return evaluate
26
+ raise AttributeError(name)
viobench/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .tools.cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,12 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(solution C CXX)
3
+ # wasm (the submission): viobench bundle <this directory> -o solution.wasm
4
+ # host debug build: cmake -S . -B build-host && cmake --build build-host && build-host/debug <feed dir> (docs/DEBUGGING.md)
5
+ set(CMAKE_CXX_STANDARD 17)
6
+ if(NOT CMAKE_BUILD_TYPE)
7
+ set(CMAKE_BUILD_TYPE Release)
8
+ endif()
9
+ set(VIOBENCH_NATIVE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../runtime/native" CACHE PATH "directory with viobench_abi.h and viobench_reactor.cmake")
10
+ include(${VIOBENCH_NATIVE_DIR}/viobench_reactor.cmake)
11
+ viobench_solution(solution solution.cpp)
12
+ target_compile_options(solution PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>)
@@ -0,0 +1,10 @@
1
+ # cpp_minimal
2
+
3
+ The ABI in C++ without the Python runtime: about 40 KB, a few thousand fuel units of overhead per step.
4
+
5
+ ```bash
6
+ viobench bundle <this directory> -o solution.wasm
7
+ viobench check solution.wasm
8
+ ```
9
+
10
+ Building, debugging on the host, OpenCV and Rust: `runtime/native/README.md` in the workspace.
@@ -0,0 +1,97 @@
1
+ // Minimal native solution: a wasm32-wasip1 reactor implementing the viobench ABI (runtime/native/viobench_abi.h) without
2
+ // Python. It reports the identity pose and a zero twist and shows where to put state and how to read the config.
3
+ // Build: viobench bundle <this directory> -o solution.wasm
4
+ #include "viobench_abi.h"
5
+
6
+ #include <cmath>
7
+ #include <cstdint>
8
+ #include <cstdio>
9
+ #include <cstdlib>
10
+ #include <cstring>
11
+ #include <string>
12
+ #include <vector>
13
+
14
+ namespace {
15
+
16
+
17
+ struct Config {
18
+ int width = 0, height = 0;
19
+ double fx = 0, fy = 0, cx = 0, cy = 0;
20
+ };
21
+
22
+ // A deliberately tiny JSON number lookup: finds `"key": <number>`. Good enough for the flat config's scalars; use a
23
+ // real JSON parser (e.g. nlohmann/json, header-only, works with wasi-sdk) for T_cam_imu.
24
+ bool find_number(const std::string& json, const char* key, double& out) {
25
+ std::string needle = std::string("\"") + key + "\"";
26
+ size_t at = json.find(needle);
27
+ if (at == std::string::npos) return false;
28
+ at = json.find(':', at + needle.size());
29
+ if (at == std::string::npos) return false;
30
+ out = std::strtod(json.c_str() + at + 1, nullptr);
31
+ return true;
32
+ }
33
+
34
+ struct State {
35
+ Config config;
36
+ long frames = 0;
37
+ std::vector<uint8_t> previous_frame; // keep the previous frame: the host reuses its buffer every step
38
+ double position[3] = {0, 0, 0};
39
+ double quaternion[4] = {0, 0, 0, 1}; // x, y, z, w
40
+ double velocity[3] = {0, 0, 0}; // of the camera centre, in camera axes
41
+ double angular_velocity[3] = {0, 0, 0}; // in camera axes
42
+ };
43
+
44
+ State state;
45
+
46
+ } // namespace
47
+
48
+ int32_t abi_version() { return VIOBENCH_ABI_VERSION; }
49
+
50
+ void* alloc(uint32_t size) { return std::malloc(size ? size : 1); }
51
+
52
+ // Optional (320 is the default without it); return 640 for 640x480 frames. Called once, before init().
53
+ int32_t frame_width() { return 320; }
54
+
55
+ // Called once per instance; step() is only called after init() succeeded.
56
+ int32_t init(const char* json, uint32_t length) {
57
+ std::setvbuf(stdout, nullptr, _IONBF, 0); // a reactor never exits: unbuffered prints reach the host log
58
+ std::setvbuf(stderr, nullptr, _IONBF, 0);
59
+ std::string text(json, length);
60
+ double value;
61
+ if (find_number(text, "width", value)) state.config.width = static_cast<int>(value);
62
+ if (find_number(text, "height", value)) state.config.height = static_cast<int>(value);
63
+ find_number(text, "fx", state.config.fx);
64
+ find_number(text, "fy", state.config.fy);
65
+ find_number(text, "cx", state.config.cx);
66
+ find_number(text, "cy", state.config.cy);
67
+ if (state.config.width <= 0 || state.config.height <= 0) {
68
+ std::fprintf(stderr, "config without image size\n");
69
+ return VIOBENCH_STATUS_ERROR;
70
+ }
71
+ state.previous_frame.resize(static_cast<size_t>(state.config.width) * state.config.height * 3); // RGB
72
+ std::printf("init: %dx%d fx=%.1f cx=%.1f\n", state.config.width, state.config.height, state.config.fx, state.config.cx);
73
+ return VIOBENCH_STATUS_OK;
74
+ }
75
+
76
+ // frame: height*width*3 uint8 RGB; imu: imu_count rows [t, ax, ay, az, gx, gy, gz];
77
+ // out: [x, y, z, qx, qy, qz, qw, vx, vy, vz, wx, wy, wz] (all 13 values, every step)
78
+ int32_t step(double t, const uint8_t* frame, const double* imu, int32_t imu_count, double* out) {
79
+ state.frames++;
80
+ double mean = 0;
81
+ for (size_t i = 0; i < state.previous_frame.size(); i++) mean += frame[i];
82
+ mean /= static_cast<double>(state.previous_frame.size());
83
+ double gyro_norm = 0;
84
+ for (int32_t i = 0; i < imu_count; i++) {
85
+ const double* row = imu + 7 * i;
86
+ gyro_norm += std::sqrt(row[4] * row[4] + row[5] * row[5] + row[6] * row[6]);
87
+ }
88
+ if (state.frames == 1) std::printf("first frame t=%.3f mean=%.1f imu rows=%d\n", t, mean, imu_count);
89
+ std::memcpy(state.previous_frame.data(), frame, state.previous_frame.size());
90
+ // identity pose, standing still: replace with your estimate of the current camera frame in the first camera frame
91
+ // and of its twist
92
+ for (int i = 0; i < 3; i++) out[i] = state.position[i];
93
+ for (int i = 0; i < 4; i++) out[3 + i] = state.quaternion[i];
94
+ for (int i = 0; i < 3; i++) out[7 + i] = state.velocity[i];
95
+ for (int i = 0; i < 3; i++) out[10 + i] = state.angular_velocity[i];
96
+ return VIOBENCH_STATUS_OK;
97
+ }
@@ -0,0 +1,29 @@
1
+ """The smallest valid solution: reports the identity pose and a zero twist (standing still) for every frame.
2
+
3
+ Use it as the template for your own (`viobench init python <dir>` starts a workspace with it): keep the class name
4
+ `Solution`, the constructor signature and `step`.
5
+ Bundle and check it with
6
+ viobench bundle <this directory> -o solution.wasm
7
+ viobench check solution.wasm
8
+ """
9
+ import numpy as np
10
+
11
+
12
+ class Solution:
13
+ frame_width = 320 # optional (320 is the default); 640 asks for 640x480 frames
14
+
15
+ def __init__(self, config):
16
+ # config: frame size, intrinsics, camera-IMU extrinsics (docs/ABI.md)
17
+ self.config = config
18
+ self.frames = 0
19
+
20
+ def step(self, t, frame, imu):
21
+ """t: frame timestamp [s]; frame: uint8 (H, W, 3) RGB, read-only view valid until the next step;
22
+ imu: float64 (N, 7) rows [t, ax, ay, az, gx, gy, gz] with t_prev < t <= t_frame.
23
+ Return 13 values for every frame: [x, y, z, qx, qy, qz, qw], the pose of this camera frame in the frame of the
24
+ first one, then [vx, vy, vz, wx, wy, wz], the velocity of the camera centre and the angular velocity, both in
25
+ this camera frame's axes."""
26
+ if self.frames == 0:
27
+ print(f"first frame at t={t:.3f}s shape={frame.shape} dtype={frame.dtype} imu rows={len(imu)}")
28
+ self.frames += 1
29
+ return np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
@@ -0,0 +1,22 @@
1
+ # Native solutions (C, C++, Rust)
2
+
3
+ Implement the exports of `viobench_abi.h` (`docs/ABI.md`). Start from `viobench init native <workspace>` (the
4
+ `examples/native/cpp_minimal` example as `solution/`); its `CMakeLists.txt` needs only
5
+ `include(${VIOBENCH_NATIVE_DIR}/viobench_reactor.cmake)` and `viobench_solution(<target> <sources...>)`.
6
+
7
+ ```bash
8
+ viobench bundle <dir> -o solution.wasm # runtime/native/build.sh: wasi-sdk (fetched into the workspace's build/toolchain) → <dir>/build/, copied to -o
9
+ viobench check solution.wasm
10
+ ```
11
+
12
+ The same CMake call configured with the host compiler builds `build-host/debug` (`docs/DEBUGGING.md`).
13
+
14
+ - `exit()`, `abort()` and uncaught C++ exceptions trap: return `-1` instead. Make stdout and stderr unbuffered; a
15
+ reactor never exits.
16
+ - **Other libraries**: cross-build them with the workspace's `build/toolchain/wasi-sdk` (fetch their sources in
17
+ `toolchain/download_dependencies.sh` if you like) and link them in your `CMakeLists.txt`.
18
+ - **OpenCV**: the Python runtime build leaves static libraries and headers in `build/runtime/opencv-build`. Compile
19
+ with `-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false`; link with `-fwasm-exceptions -lc++ -lc++abi -lunwind`.
20
+ `viobench/solution/examples/native/openvins` does this and ports a larger C++ project (OpenVINS, Ceres, Eigen).
21
+ - **Rust**: `crate-type = ["cdylib"]`, target `wasm32-wasip1`, `#[no_mangle] pub extern "C" fn step(t: f64, frame:
22
+ *const u8, imu: *const f64, n: i32, out: *mut f64) -> i32` and the other exports.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Builds a native (C/C++) solution directory with wasi-sdk (`viobench bundle <dir> -o x.wasm` runs this): runtime/native/build.sh <dir> [cmake arguments]
3
+ # The directory needs a CMakeLists.txt (see examples/native/cpp_minimal); the module lands in <dir>/build/<target>.wasm.
4
+ # wasi-sdk comes from the workspace's build/toolchain (toolchain/download_dependencies.sh native, run here first).
5
+ set -euo pipefail
6
+ if [ $# -lt 1 ]; then
7
+ echo "usage: $0 <solution directory> [extra cmake arguments]" >&2
8
+ exit 2
9
+ fi
10
+ HERE=$(cd "$(dirname "$0")" && pwd)
11
+ TOOLCHAIN=${TOOLCHAIN:-$HERE/../../build/toolchain}
12
+ SOLUTION=$(cd "$1" && pwd)
13
+ shift
14
+ bash "$HERE/../../toolchain/download_dependencies.sh" native
15
+ mkdir -p "$SOLUTION/build"
16
+ cmake -S "$SOLUTION" -B "$SOLUTION/build" -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN/wasi-sdk/share/cmake/wasi-sdk-p1.cmake" -DVIOBENCH_NATIVE_DIR="$HERE" "$@" > "$SOLUTION/build/cmake.log" 2>&1 || (cat "$SOLUTION/build/cmake.log"; exit 1)
17
+ cmake --build "$SOLUTION/build"
18
+ ls -la "$SOLUTION"/build/*.wasm
@@ -0,0 +1,51 @@
1
+ /* viobench solution ABI, version 1 (docs/ABI.md) — the whole contract between a solution module and the harness.
2
+ *
3
+ * A solution is a wasm32-wasip1 *reactor* that exports the functions declared below (plus `memory`). The harness
4
+ * calls, per episode: _initialize (if present) -> frame_width (if present) -> init(config_json) -> alloc x3 -> step(...) x N.
5
+ * Anything may run inside; this header is a convenience for C/C++ solutions, the Python runtime
6
+ * (viobench/solution/runtime/python/reactor.c) uses it too, so status codes exist in exactly one place.
7
+ */
8
+ #ifndef VIOBENCH_ABI_H
9
+ #define VIOBENCH_ABI_H
10
+
11
+ #include <stdint.h>
12
+
13
+ #define VIOBENCH_ABI_VERSION 1
14
+
15
+ /* Mark a function as a wasm export under its own name (a no-op in a host debug build). */
16
+ #if defined(__wasm__)
17
+ #define VIOBENCH_EXPORT(name) __attribute__((export_name(#name)))
18
+ #else
19
+ #define VIOBENCH_EXPORT(name)
20
+ #endif
21
+
22
+ /* Buffer layouts: everything little-endian; pointers are offsets into the module's linear memory. */
23
+ #define VIOBENCH_IMU_ROW_VALUES 7 /* [t_s, ax, ay, az, gx, gy, gz] as double */
24
+ #define VIOBENCH_POSE_VALUES 7 /* [x, y, z, qx, qy, qz, qw] as double, camera k in camera 0 */
25
+ #define VIOBENCH_TWIST_VALUES 6 /* [vx, vy, vz, wx, wy, wz] as double, velocity and angular velocity of camera k in its own axes */
26
+ #define VIOBENCH_STATE_VALUES 13 /* out[]: the pose, then the twist */
27
+
28
+ /* Status codes returned by init() and step(): anything but OK is an error (explain it on stderr). */
29
+ #define VIOBENCH_STATUS_OK 0 /* init: ready; step: out[] holds the pose and the twist */
30
+ #define VIOBENCH_STATUS_ERROR -1 /* the episode ends and is marked failed */
31
+
32
+ #ifdef __cplusplus
33
+ extern "C" {
34
+ #endif
35
+
36
+ /* Exports every solution must provide (`memory` is exported by the toolchain automatically). */
37
+ VIOBENCH_EXPORT(abi_version) int32_t abi_version(void); /* returns VIOBENCH_ABI_VERSION */
38
+ VIOBENCH_EXPORT(alloc) void *alloc(uint32_t size); /* malloc; never freed by the host */
39
+ VIOBENCH_EXPORT(frame_width) int32_t frame_width(void); /* optional: 320 (the default without it) or 640 (640x480) */
40
+ VIOBENCH_EXPORT(init) int32_t init(const char *config_json, uint32_t length); /* once; the bytes are valid during the call */
41
+ VIOBENCH_EXPORT(step) int32_t step(double t, const uint8_t *frame, const double *imu, int32_t imu_count, double *out);
42
+ /* frame: height*width*3 uint8 RGB (row-major), size per the config; imu: imu_count (always 10) rows of VIOBENCH_IMU_ROW_VALUES
43
+ * doubles with t_prev_frame < t <= t_frame; out: VIOBENCH_STATE_VALUES doubles, all written on VIOBENCH_STATUS_OK (the host fills
44
+ * them with NaN before the call, so a value left out fails the step). The frame and IMU buffers are reused for the next
45
+ * step: copy what you want to keep. */
46
+
47
+ #ifdef __cplusplus
48
+ }
49
+ #endif
50
+
51
+ #endif /* VIOBENCH_ABI_H */
@@ -0,0 +1,136 @@
1
+ /* Host-side driver for debugging a native solution outside the wasm sandbox.
2
+ *
3
+ * Linked with your solution's sources by viobench_reactor.cmake whenever the configure is not WASI
4
+ * (`cmake -S . -B build-host && cmake --build build-host` -> build-host/debug). It reads a feed directory written by
5
+ * `viobench feed <episode> -o <dir>` (raw arrays, no PNG/JSON parsing needed), calls init() and step() exactly like the
6
+ * sandbox would, writes estimate.tum and twist.txt next to it and prints the position RMSE. Score it fully with
7
+ * `viobench score <episode> estimate.tum`.
8
+ * Run under gdb/lldb: gdb --args build-host/debug <feed dir>
9
+ */
10
+ #include "viobench_abi.h"
11
+
12
+ #include <math.h>
13
+ #include <stdio.h>
14
+ #include <stdlib.h>
15
+ #include <string.h>
16
+ #include <time.h>
17
+
18
+ #define TIME_TOLERANCE 1e-6 /* seconds: the same slack the host harness uses when selecting IMU rows */
19
+
20
+ /* The harness's test for a usable state (pose and twist): all finite, with a non-zero quaternion. */
21
+ static int usable(const double *state) {
22
+ for (int i = 0; i < VIOBENCH_STATE_VALUES; i++) {
23
+ if (!isfinite(state[i])) return 0;
24
+ }
25
+ return state[3] != 0 || state[4] != 0 || state[5] != 0 || state[6] != 0;
26
+ }
27
+
28
+ static void *read_file(const char *directory, const char *name, size_t *size) {
29
+ char path[4096];
30
+ snprintf(path, sizeof path, "%s/%s", directory, name);
31
+ FILE *file = fopen(path, "rb");
32
+ if (file == NULL) {
33
+ fprintf(stderr, "cannot open %s\n", path);
34
+ exit(2);
35
+ }
36
+ fseek(file, 0, SEEK_END);
37
+ long length = ftell(file);
38
+ fseek(file, 0, SEEK_SET);
39
+ void *data = malloc((size_t)length + 1);
40
+ if (fread(data, 1, (size_t)length, file) != (size_t)length) {
41
+ fprintf(stderr, "short read on %s\n", path);
42
+ exit(2);
43
+ }
44
+ ((char *)data)[length] = 0;
45
+ fclose(file);
46
+ *size = (size_t)length;
47
+ return data;
48
+ }
49
+
50
+ static double now_seconds(void) {
51
+ struct timespec ts;
52
+ clock_gettime(CLOCK_MONOTONIC, &ts);
53
+ return (double)ts.tv_sec + 1e-9 * (double)ts.tv_nsec;
54
+ }
55
+
56
+ int main(int argc, char **argv) {
57
+ if (argc < 2) {
58
+ fprintf(stderr, "usage: %s <feed directory from `viobench feed`> [estimate.tum]\n", argv[0]);
59
+ return 2;
60
+ }
61
+ const char *directory = argv[1];
62
+ const char *output = argc > 2 ? argv[2] : "estimate.tum";
63
+
64
+ size_t size;
65
+ char *shape = read_file(directory, "shape.txt", &size);
66
+ long frames, height, width, channels, imu_rows;
67
+ if (sscanf(shape, "%ld %ld %ld %ld %ld", &frames, &height, &width, &channels, &imu_rows) != 5) {
68
+ fprintf(stderr, "bad shape.txt\n");
69
+ return 2;
70
+ }
71
+ char *config = read_file(directory, "config.json", &size);
72
+ size_t config_length = size;
73
+ size_t frame_bytes = (size_t)height * (size_t)width * (size_t)channels;
74
+ uint8_t *pixels = read_file(directory, "frames.u8", &size);
75
+ if (size != frame_bytes * (size_t)frames) {
76
+ fprintf(stderr, "frames.u8 has %zu bytes, expected %zu\n", size, frame_bytes * (size_t)frames);
77
+ return 2;
78
+ }
79
+ double *frame_times = read_file(directory, "frame_times.f64", &size);
80
+ double *imu = read_file(directory, "imu.f64", &size);
81
+ double *ground_truth = read_file(directory, "gt.f64", &size);
82
+
83
+ printf("feed: %ld frames %ldx%ldx%ld, %ld IMU rows, abi %d\n", frames, width, height, channels, imu_rows, abi_version());
84
+ int32_t status = init(config, (uint32_t)config_length);
85
+ if (status != VIOBENCH_STATUS_OK) {
86
+ fprintf(stderr, "init returned %d\n", status);
87
+ return 1;
88
+ }
89
+
90
+ char twist_output[4096]; /* twist.txt next to the pose file */
91
+ const char *slash = strrchr(output, '/');
92
+ snprintf(twist_output, sizeof twist_output, "%.*stwist.txt", slash ? (int)(slash - output + 1) : 0, output);
93
+ FILE *tum = fopen(output, "w");
94
+ FILE *twist = fopen(twist_output, "w");
95
+ if (tum == NULL || twist == NULL) {
96
+ fprintf(stderr, "cannot write %s\n", tum == NULL ? output : twist_output);
97
+ return 2;
98
+ }
99
+ fprintf(tum, "# t x y z qx qy qz qw (camera k in camera 0 frame)\n");
100
+ fprintf(twist, "# t vx vy vz wx wy wz (velocity and angular velocity of camera k in camera k axes)\n");
101
+ double state[VIOBENCH_STATE_VALUES];
102
+ double previous = -INFINITY;
103
+ long window_start = 0;
104
+ long k = 0;
105
+ double squared_error = 0.0, total_seconds = 0.0;
106
+ for (; k < frames; k++) {
107
+ double t = frame_times[k];
108
+ /* IMU rows with t_prev < t_row <= t (the feed starts at frame 0's window, so frame 0 gets everything up to its own time) */
109
+ while (window_start < imu_rows && imu[window_start * VIOBENCH_IMU_ROW_VALUES] <= previous + TIME_TOLERANCE) window_start++;
110
+ long window_end = window_start;
111
+ while (window_end < imu_rows && imu[window_end * VIOBENCH_IMU_ROW_VALUES] <= t + TIME_TOLERANCE) window_end++;
112
+ for (int i = 0; i < VIOBENCH_STATE_VALUES; i++) state[i] = NAN; /* like the host: a value not written fails the step */
113
+ double started = now_seconds();
114
+ status = step(t, pixels + (size_t)k * frame_bytes, imu + window_start * VIOBENCH_IMU_ROW_VALUES, (int32_t)(window_end - window_start), state);
115
+ double seconds = now_seconds() - started;
116
+ if (status != VIOBENCH_STATUS_OK || !usable(state)) {
117
+ printf("frame %ld: %s; the harness ends the episode here and marks it failed\n", k,
118
+ status != VIOBENCH_STATUS_OK ? "step returned an error"
119
+ : "state not usable (not all 13 values written, NaN/inf, or all-zero quaternion)");
120
+ break;
121
+ }
122
+ total_seconds += seconds;
123
+ fprintf(tum, "%.9g %.9g %.9g %.9g %.9g %.9g %.9g %.9g\n", t, state[0], state[1], state[2], state[3], state[4], state[5], state[6]);
124
+ fprintf(twist, "%.9g %.9g %.9g %.9g %.9g %.9g %.9g\n", t, state[7], state[8], state[9], state[10], state[11], state[12]);
125
+ const double *truth = ground_truth + k * VIOBENCH_POSE_VALUES;
126
+ double dx = state[0] - truth[0], dy = state[1] - truth[1], dz = state[2] - truth[2];
127
+ squared_error += dx * dx + dy * dy + dz * dz;
128
+ previous = t;
129
+ window_start = window_end;
130
+ }
131
+ fclose(tum);
132
+ fclose(twist);
133
+ printf("%ld/%ld frames ok, position RMSE over them %.3f m, %.2f ms/frame (host, no fuel); poses in %s, twists in %s\n", k, frames,
134
+ sqrt(squared_error / (double)k), 1e3 * total_seconds / (double)k, output, twist_output);
135
+ return k < frames;
136
+ }
@@ -0,0 +1,24 @@
1
+ # viobench_solution(<target> <sources...>) — one call, two builds:
2
+ # * configured with the wasi-sdk toolchain file (viobench bundle does that):
3
+ # the solution reactor <target>.wasm that you submit;
4
+ # * configured with your host compiler (`cmake -S . -B build-host && cmake --build build-host`): an executable
5
+ # `debug` = your sources + viobench_debug_main.c, built with -g, that reads a feed from `viobench feed` and calls your
6
+ # init()/step() exactly like the sandbox — for gdb/lldb/VS Code.
7
+ function(viobench_solution target)
8
+ set(sources ${ARGN})
9
+ if(CMAKE_SYSTEM_NAME STREQUAL "WASI")
10
+ add_executable(${target} ${sources})
11
+ target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_FUNCTION_LIST_DIR})
12
+ target_compile_options(${target} PRIVATE -O2 -msimd128)
13
+ # a reactor: no main(), the VIOBENCH_EXPORT functions are the entry points; 64 MiB initial memory avoids early grows
14
+ target_link_options(${target} PRIVATE -mexec-model=reactor -Wl,--initial-memory=67108864 -Wl,--strip-debug)
15
+ set_target_properties(${target} PROPERTIES SUFFIX ".wasm")
16
+ else()
17
+ enable_language(C)
18
+ add_executable(${target} ${sources} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/viobench_debug_main.c)
19
+ target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_FUNCTION_LIST_DIR})
20
+ target_compile_options(${target} PRIVATE -g -O1)
21
+ set_target_properties(${target} PROPERTIES OUTPUT_NAME debug)
22
+ message(STATUS "viobench: host configure -> building the `debug` driver (not a submission); use viobench bundle for the .wasm")
23
+ endif()
24
+ endfunction()
@@ -0,0 +1,55 @@
1
+ # The Python runtime: build
2
+
3
+ `build.sh` (what `viobench build-runtime` runs: `toolchain/download_dependencies.sh`, then `cpython/build-wasm.sh`;
4
+ 10–15 min) writes two modules to the workspace's `build/runtime/dist/`:
5
+
6
+ - `runtime.wasm`: CPython 3.14.4, numpy 2.3.4 and OpenCV 4.12.0 as a reactor implementing `docs/ABI.md`;
7
+ - `python.wasm`: the same as a command-line interpreter.
8
+
9
+ Both are single-threaded and need no directory: the stdlib and numpy's Python code are frozen in. OpenCV uses SIMD128
10
+ and wasm exceptions (wasi-sdk 34 `eh` runtime, Wasmtime ≥ 50).
11
+
12
+ ```bash
13
+ runtime/python/build.sh # or viobench build-runtime
14
+ runtime/python/cpython/test.sh # numpy's tests inside python.wasm
15
+ runtime/python/cpython/python.sh # a REPL
16
+ ```
17
+
18
+ Pure-Python packages need no rebuild: copy them into your solution directory. Extension modules are cross-built in
19
+ `cpython/build-wasm.sh` and linked in like numpy and OpenCV (below).
20
+
21
+ | file | role |
22
+ |---|---|
23
+ | `reactor.c` | the ABI exports (with `../native/viobench_abi.h`) and the 32-byte bundle descriptor that `viobench bundle` patches |
24
+ | `guest/viobench_guest.py` | frozen shim: imports the bundle, loads `Solution`, glues `step` |
25
+ | `cpython/build-wasm.sh`, `patches/`, `scripts/` | the cross-build |
26
+
27
+ Overrides (paths in the workspace): `TOOLCHAIN` (default `build/toolchain`), `SRC` (pristine sources), `WASI_SDK_PATH`, `VENV`, `WASMTIME`,
28
+ `BUILD` (default `build/runtime`). The patched sources in `build/runtime/src` are plain copies:
29
+ `diff -ru build/toolchain/src/numpy build/runtime/src/numpy` shows the changes; delete `build/runtime/src/<project>`
30
+ after editing a patch.
31
+
32
+ ## How it links
33
+
34
+ - WASI has no dynamic loading. `scripts/collect_numpy.py` archives each extension module's objects, and
35
+ `scripts/link.py` links them into CPython with a generated `main.c` that registers each module in the inittab under
36
+ its dotted name (`numpy._core._multiarray_umath`).
37
+ - `scripts/freeze.py` compiles the `.py` files into one blob, embedded with `#embed` and installed as
38
+ `PyImport_FrozenModules`. Tracebacks show files and lines but no source.
39
+ - `link.py --reactor reactor.c` links with `-mexec-model=reactor`: `_initialize` runs the C++ static constructors, and
40
+ `reactor.c` starts the interpreter in its `frame_width` export (isolated config, unbuffered stdio, fixed hash seed).
41
+
42
+ | patch | reason |
43
+ |---|---|
44
+ | numpy `npy_cpu.h` | wasm CPU detection only under Emscripten |
45
+ | numpy `random/meson.build`, `static_link_rename.h` | `mtrand`'s copy of `distributions.c` collides with the other once linked into one binary |
46
+ | OpenCV `cv_cpu_dispatch.h`, `intrin_wasm.hpp` | wasm SIMD gated on Emscripten |
47
+ | OpenCV `stereo_geom.cpp`, `cv2.cpp` | missing `<iterator>` with libc++ 23 |
48
+
49
+ Left out: the `zlib`, `bz2`, `lzma`, `ssl`, `ctypes`, `sqlite3` modules; stdlib `test`, `idlelib`, `tkinter`,
50
+ `ensurepip`, `venv`, `pydoc_data`; numpy `tests`, `f2py`; OpenCV `imgcodecs`, `highgui`, `objdetect`. `dnn` needs
51
+ `BUILD_opencv_dnn=ON` explicitly (thread support is disabled). numpy never warns on overflow or invalid values (wasm
52
+ has no floating-point flags), and its default integer is 32-bit.
53
+
54
+ Compile `.cwasm` files on the machine that runs them, and never accept them from others: `--allow-precompiled` skips
55
+ validation.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # Builds the reference Python runtime (CPython + numpy + OpenCV as a wasip1 reactor) natively on Linux or macOS (Windows: inside WSL2).
3
+ # Output in the workspace's build/runtime/dist: runtime.wasm (what `viobench bundle` embeds your code into; `viobench build-runtime`
4
+ # runs this script, and `viobench bundle` does when runtime.wasm is missing) and python.wasm (a REPL for experiments).
5
+ # First build 10-15 minutes; fetches the toolchain into build/toolchain first (toolchain/download_dependencies.sh). Run from anywhere.
6
+ set -euo pipefail
7
+ HERE=$(cd "$(dirname "$0")" && pwd)
8
+ bash "$HERE/../../toolchain/download_dependencies.sh"
9
+ bash "$HERE/cpython/build-wasm.sh" "$@"
@@ -0,0 +1,10 @@
1
+ """The Python runtime's packaging step: a solution directory -> one self-contained solution.wasm, and back.
2
+
3
+ `viobench bundle <dir> -o x.wasm` zips the directory and embeds it into runtime.wasm as a data segment (`embed`);
4
+ `viobench unbundle` extracts it. Depends only on the contract (viobench.tools.abi) and the workspaces (viobench.solution.workspace); the runner never
5
+ imports this.
6
+ """
7
+
8
+
9
+ class BundleError(Exception):
10
+ """Invalid solution directory or runtime binary."""