ruvnet/RuView · error · ValueError

{path}: expected {{'nodes': [...]}} or a top-level list

Error message

{path}: expected {{'nodes': [...]}} or a top-level list

What it means

load_geometry_file reads the transceiver-geometry JSON passed via --geometry-file to calibrate-camera-room.py. It expects a document shaped {'nodes': [...]} (a 'nodes' key when the top level is an object); if no node list can be extracted it raises ValueError stating the expected shape. Each element is then validated for 'id' and 'position_m'.

Source

Thrown at scripts/calibrate-camera-room.py:169

            node = {"id": parts[0], "position_m": [float(parts[1]), float(parts[2]), float(parts[3])]}
            if len(parts) == 5:
                node["antenna_yaw_deg"] = float(parts[4])
        except ValueError:
            print("  positions must be numeric", file=sys.stderr)
            continue
        nodes.append(node)
    if not nodes:
        print("WARNING: no transceiver nodes entered; bundle will carry empty geometry.",
              file=sys.stderr)
    return {"nodes": nodes, "units": "meters", "source": "tape-measure-prompt"}


def load_geometry_file(path: Path) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)
    nodes = data.get("nodes", data if isinstance(data, list) else None)
    if nodes is None:
        raise ValueError(f"{path}: expected {{'nodes': [...]}} or a top-level list")
    for node in nodes:
        if "id" not in node or "position_m" not in node:
            raise ValueError(f"{path}: each node needs 'id' and 'position_m' [x,y,z]")
    return {"nodes": nodes, "units": "meters", "source": "file"}


def main():
    parser = argparse.ArgumentParser(
        description="Two-checkerboard camera-room calibration (ADR-152 S2.1.3 / ADR-079)."
    )
    parser.add_argument("--wall-image", required=True,
                        help="Photo of the checkerboard on the origin wall")
    parser.add_argument("--floor-image", required=True,
                        help="Photo of the checkerboard on the floor (camera NOT moved)")
    parser.add_argument("--wall-origin", type=parse_vec3, default="0.5,0.0,1.6",
                        help="Room xyz (m) of the wall board's first inner corner "
                             "(default: 0.5,0.0,1.6)")
    parser.add_argument("--floor-origin", type=parse_vec3, default="1.0,1.0,0.0",

View on GitHub (pinned to 4685618388)

Solutions

  1. Rewrite the JSON as {"nodes": [{"id": "n1", "position_m": [x, y, z]}, ...]} with positions in meters
  2. Or omit --geometry-file and use the interactive tape-measure prompt instead
  3. Pre-validate the file: jq 'has("nodes")' nodes.json

Example fix

// before
// nodes.json
{"anchors": [{"id": "n1", "position_m": [0, 0, 1]}]}

// after
// nodes.json
{"nodes": [{"id": "n1", "position_m": [0.0, 0.0, 1.0]}]}
Defensive patterns

Strategy: validation

Validate before calling

import json

def valid_geometry_file(path: str) -> bool:
    with open(path, encoding="utf-8") as f:
        data = json.load(f)
    return isinstance(data, dict) and isinstance(data.get("nodes"), list)

if not valid_geometry_file("nodes.json"):
    raise SystemExit("nodes.json must be {'nodes': [...]} or omit --geometry-file")

Try / catch

try:
    geom = load_geometry_file(path)
except ValueError as e:
    raise SystemExit(f"bad geometry file: {e}") from e

Prevention

When it happens

Trigger: Passing --geometry-file with a JSON object that lacks the 'nodes' key — e.g. {'anchors': [...]}, {'transceivers': [...]}, or a full calibration bundle — so `data.get("nodes", ...)` yields None.

Common situations: Hand-written room geometry files; exporting from a CAD or notes tool that names the array differently; feeding an intrinsics file where a geometry file was expected.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/fb8c7448204d2a46. Report an issue: GitHub.