sacpaint 0.2.0__tar.gz

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 (33) hide show
  1. sacpaint-0.2.0/PKG-INFO +229 -0
  2. sacpaint-0.2.0/README.md +202 -0
  3. sacpaint-0.2.0/pyproject.toml +65 -0
  4. sacpaint-0.2.0/setup.cfg +4 -0
  5. sacpaint-0.2.0/src/sacpaint/__init__.py +28 -0
  6. sacpaint-0.2.0/src/sacpaint/assets/sacramento-line-v0.json +97 -0
  7. sacpaint-0.2.0/src/sacpaint/assets/sacramento-line-v0.png +0 -0
  8. sacpaint-0.2.0/src/sacpaint/assets/sacramento-line-v0.spec.json +531 -0
  9. sacpaint-0.2.0/src/sacpaint/claude_shim/__init__.py +45 -0
  10. sacpaint-0.2.0/src/sacpaint/claude_shim/__main__.py +80 -0
  11. sacpaint-0.2.0/src/sacpaint/claude_shim/server.py +357 -0
  12. sacpaint-0.2.0/src/sacpaint/claude_shim/translate.py +580 -0
  13. sacpaint-0.2.0/src/sacpaint/cli.py +511 -0
  14. sacpaint-0.2.0/src/sacpaint/mock.py +254 -0
  15. sacpaint-0.2.0/src/sacpaint/opencastor/__init__.py +52 -0
  16. sacpaint-0.2.0/src/sacpaint/opencastor/calibrate.py +180 -0
  17. sacpaint-0.2.0/src/sacpaint/opencastor/calibration.py +305 -0
  18. sacpaint-0.2.0/src/sacpaint/opencastor/cameras.py +378 -0
  19. sacpaint-0.2.0/src/sacpaint/opencastor/client.py +514 -0
  20. sacpaint-0.2.0/src/sacpaint/opencastor/embodiment.py +606 -0
  21. sacpaint-0.2.0/src/sacpaint/rectify.py +171 -0
  22. sacpaint-0.2.0/src/sacpaint/reference.py +379 -0
  23. sacpaint-0.2.0/src/sacpaint/scorers.py +414 -0
  24. sacpaint-0.2.0/src/sacpaint/tasks.py +94 -0
  25. sacpaint-0.2.0/src/sacpaint.egg-info/PKG-INFO +229 -0
  26. sacpaint-0.2.0/src/sacpaint.egg-info/SOURCES.txt +31 -0
  27. sacpaint-0.2.0/src/sacpaint.egg-info/dependency_links.txt +1 -0
  28. sacpaint-0.2.0/src/sacpaint.egg-info/entry_points.txt +19 -0
  29. sacpaint-0.2.0/src/sacpaint.egg-info/requires.txt +14 -0
  30. sacpaint-0.2.0/src/sacpaint.egg-info/top_level.txt +1 -0
  31. sacpaint-0.2.0/tests/test_claude_shim.py +862 -0
  32. sacpaint-0.2.0/tests/test_opencastor.py +987 -0
  33. sacpaint-0.2.0/tests/test_sacpaint.py +254 -0
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: sacpaint
3
+ Version: 0.2.0
4
+ Summary: Sacramento PaintBench: draw a fixed Sacramento skyline reference from camera feedback, scored by landmark geometry. An Inspect Robots benchmark.
5
+ Author: Craig Merry
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/craigm26/sacpaint
8
+ Project-URL: Issues, https://github.com/craigm26/sacpaint/issues
9
+ Keywords: robotics,benchmark,inspect-robots,evaluation,vision-language-action
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: inspect-robots>=0.58.0
17
+ Requires-Dist: numpy>=1.26
18
+ Requires-Dist: opencv-python-headless>=4.9
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8; extra == "dev"
21
+ Requires-Dist: ruff>=0.5; extra == "dev"
22
+ Requires-Dist: build; extra == "dev"
23
+ Requires-Dist: twine; extra == "dev"
24
+ Provides-Extra: agent
25
+ Requires-Dist: inspect-robots-agent>=0.26.0; extra == "agent"
26
+ Provides-Extra: opencastor
27
+
28
+ # Sacramento PaintBench (`sacpaint`)
29
+
30
+ A physical-AI benchmark on [Inspect Robots](https://github.com/robocurve/inspect-robots):
31
+ a robot with a pen must reproduce a fixed reference drawing from camera
32
+ feedback. One fixed prompt, one pinned reference image, geometric scoring that
33
+ anyone can recompute offline from a photo. No printed markers, no special
34
+ fixture: a sheet of paper on a desk and any camera, including an iPhone.
35
+
36
+ > Can a general-purpose frontier model reproduce a visual target with a physical
37
+ > tool, using camera feedback to correct itself, with no task-specific training?
38
+
39
+ The built-in reference is a simplified Sacramento composition: the Tower
40
+ Bridge above the Capitol dome and cupola, joined by the Capitol Mall, two
41
+ building masses, a horizon. Its SHA-256 is the benchmark's identity. The
42
+ physical sheet is 150 × 200 mm (A5 or half-letter with margins), small enough
43
+ for a desk arm such as the SO-ARM101 to reach every corner.
44
+
45
+ ![reference](src/sacpaint/assets/sacramento-line-v0.png)
46
+
47
+ ## Sixty seconds, no robot
48
+
49
+ ```bash
50
+ pip install sacpaint
51
+ sacpaint score photo-of-my-drawing.jpg # any photo of a finished sheet -> score + overlay
52
+ ```
53
+
54
+ `score` finds the sheet in the photo (markers if present, otherwise the largest
55
+ bright quadrilateral, otherwise corners you pass with `--corners`), rectifies
56
+ it to the canonical canvas, and writes three files next to the photo: the
57
+ rectified canvas, an overlay with the reference ink in red, and a JSON
58
+ breakdown. That is the whole "process a new eval" path for a drawing made by
59
+ any robot, any policy, any hardware.
60
+
61
+ The full benchmark, in the built-in mock plotter world:
62
+
63
+ ```bash
64
+ sacpaint run --policy sacpaint_trace --embodiment sacpaint_plotter --no-rerun --no-prompt # oracle, composite ~0.98
65
+ sacpaint run --policy sacpaint_idle --embodiment sacpaint_plotter --no-rerun --no-prompt # floor, 0.00
66
+ sacpaint export logs --out submission --label oracle # publishable bundle
67
+ ```
68
+
69
+ With a frontier model driving the same mock world (needs `pip install inspect-robots-agent` and a key):
70
+
71
+ ```bash
72
+ ANTHROPIC_API_KEY=... sacpaint run --policy agent --model anthropic/claude-fable-5 --embodiment sacpaint_plotter -- -P images=on_demand
73
+ ```
74
+
75
+ No API key? One flag runs the same policy through a Claude subscription (the
76
+ Claude Code CLI, logged in on the machine) for development runs. Scores from
77
+ this path are labelled `wire=claude-code-cli` and are not leaderboard-comparable;
78
+ see [docs/subscription.md](docs/subscription.md).
79
+
80
+ ```bash
81
+ sacpaint run --subscription --model haiku --policy agent --embodiment sacpaint_plotter --max-llm-calls 40 --no-rerun --no-prompt
82
+ ```
83
+
84
+ ## The prompt
85
+
86
+ Fixed. Do not tune it per model.
87
+
88
+ > Draw the reference image on the canvas with the pen. You may look at the
89
+ > overhead camera to inspect your work and make corrections. Stop when you
90
+ > believe the drawing is complete.
91
+
92
+ The model receives the reference as an image stream named `reference` and the
93
+ canvas as a stream named `overhead`, because the agent policy attaches camera
94
+ frames to observations and the instruction cannot carry an image. Every
95
+ embodiment exposes those two streams.
96
+
97
+ ## Scoring
98
+
99
+ All scorers are pure readers of the final canvas. Nothing calls a model. The
100
+ final canvas is the `observe_parked()` frame (pen lifted clear), rectified
101
+ unless the embodiment marks it canonical, then thresholded to ink.
102
+
103
+ | Scorer | Weight | What it measures |
104
+ |---|---|---|
105
+ | `landmark_geometry` | 0.45 | Per landmark: precision × recall of ink inside its box at 1% of the canvas diagonal (2.5 mm) after centroid alignment (presence), times a centroid-offset term (position). Plus relations (tower over dome, same x, horizon above tower), gated on both landmarks being present. |
106
+ | `structure` | 0.30 | Precision × recall of all ink within 2% of the diagonal (5 mm) of reference ink. Product, not F1, because a dense scribble recalls everything. |
107
+ | `discipline` | 0.15 | 1 − fraction of ink farther than 2.5% of the diagonal (6 mm) from any reference ink, scaled down past 4× the reference's ink. Blank canvas scores 0. |
108
+ | `efficiency` | 0.10 | 1 − steps/max_steps for a declared finish; scaled by `structure` in the composite so finishing a bad drawing fast earns nothing. |
109
+ | `composite` | | The leaderboard number. |
110
+
111
+ Calibration on synthetic canvases:
112
+
113
+ | Canvas | composite | landmark | structure | discipline |
114
+ |---|---|---|---|---|
115
+ | perfect trace | 0.98 | 1.00 | 1.00 | 1.00 |
116
+ | perfect trace photographed at an angle, plain sheet, rectified | 0.97 | 0.97 | 1.00 | 1.00 |
117
+ | hand wobble, σ = 1 mm | 0.97 | 0.97 | 1.00 | 1.00 |
118
+ | whole drawing shifted 5 mm | 0.77 | 0.63 | 1.00 | 0.69 |
119
+ | top half only (bridge, no dome) | 0.53 | 0.38 | 0.55 | 1.00 |
120
+ | random scribble, 30 / 60 / 400 lines | 0.30 / 0.42 / 0.33 | 0.32 / 0.47 / 0.33 | 0.28 / 0.41 / 0.43 | 0.33 / 0.35 / 0.09 |
121
+ | blank | 0.00 | 0.00 | 0.00 | 0.00 |
122
+
123
+ Through the CLI with the framework's default guardrails, 3 epochs: oracle
124
+ `sacpaint_trace` composite 0.984, `sacpaint_idle` 0.000.
125
+
126
+ Known properties: global registration counts (a 5 mm offset is a placement
127
+ error, by design); a dense random scribble still collects about 0.3 to 0.4
128
+ because the reference covers much of the canvas; the tower, dome and road
129
+ dominate through their weights.
130
+
131
+ ## Tracks
132
+
133
+ | Track | Memory across episodes | Camera | Measures |
134
+ |---|---|---|---|
135
+ | Cold | none | `-P images=on_demand`, never called | raw open-loop competence |
136
+ | Closed loop (default) | none | throughout | self-correction within one drawing |
137
+ | Learning | prior attempts via `inspect-robots summarize` + `-P prior_learnings=` | throughout | improvement across 5 canvases |
138
+
139
+ The learning track reuses the framework's own `summarize` / `prior_learnings`
140
+ mechanism, so the "memory" is an auditable markdown file with a recorded hash.
141
+ Report initial, final, best-of-5, and slope per attempt.
142
+
143
+ ## Your own reference in two commands
144
+
145
+ ```bash
146
+ sacpaint new mytown --canvas 210x297 # writes ~/.sacpaint/references/mytown.spec.json + a preview PNG
147
+ sacpaint run --task sacpaint/mytown --policy sacpaint_trace --embodiment sacpaint_plotter -- -E reference=mytown
148
+ ```
149
+
150
+ A reference is a JSON file of polylines grouped by landmark (millimetres,
151
+ origin bottom-left, y up), optional landmark weights and boxes, relations
152
+ (`above`, `left_of`, `same_x`), and tolerances. Every spec in
153
+ `~/.sacpaint/references/` registers as the task `sacpaint/<name>` the moment
154
+ the package is imported. The built-in spec is at
155
+ `src/sacpaint/assets/sacramento-line-v0.spec.json`; copy it, edit it, done.
156
+
157
+ ## Real robots
158
+
159
+ Any embodiment that exposes this contract runs the benchmark unchanged:
160
+
161
+ - action space `eef_abs_pose`, dims `(x, y, z)` in metres in the canvas frame
162
+ (x right, y up the sheet, z above the paper; the pen marks at z ≤ 0.002 m,
163
+ travels at z ≥ 0.005 m);
164
+ - images `overhead` and `reference`, state `eef_pos` (3,);
165
+ - `supported_target_kinds` includes `reference_drawing`;
166
+ - `observe_parked()` lifts the pen clear and returns a fresh observation;
167
+ - corners of the sheet in the overhead frame, if known, as
168
+ `observation.extra["canvas_corners"]` (four `[x, y]` pairs, 0..1, TL TR BR BL);
169
+ otherwise the scorer finds the sheet itself.
170
+
171
+ | Body | How |
172
+ |---|---|
173
+ | Mock plotter (built in) | `--embodiment sacpaint_plotter`, options `-E reference=NAME -E photo_mode=sheet` |
174
+ | OpenCastor + SO-ARM101 with signed receipts | [docs/opencastor.md](docs/opencastor.md): `--embodiment opencastor` |
175
+ | iPhone as the overhead camera and operator microphone | the OpenCastor iOS app's Eval mode, see [docs/opencastor.md](docs/opencastor.md) |
176
+ | Any other arm | implement the contract above; `inspect-robots-so101` (LeRobot, joint space) is a fallback body that needs the agent's `move_joints` |
177
+
178
+ ## Publishing a run the way robocurve does
179
+
180
+ ```bash
181
+ sacpaint export logs --out submission --label opus
182
+ ```
183
+
184
+ produces the layout of robocurve's published run datasets (clapboardbench):
185
+
186
+ ```
187
+ submission/
188
+ ├── README.md # provenance, reference hash, how to recompute
189
+ ├── <log>.json # raw EvalLog: config, git rev, versions, scores, transcript
190
+ ├── runs/README.md # index table: model, policy, embodiment, status, composite, steps
191
+ ├── runs/<label>-<n>.md # one page per run: metadata, scores per epoch, per-landmark table,
192
+ │ # final canvas, the model's note for every tool call
193
+ ├── html/ # inspect-robots view reports (frames the model saw)
194
+ ├── canvases/ # the rectified final canvas and score JSON per trial
195
+ ├── videos/ # inspect-robots video (when ffmpeg is installed)
196
+ └── reference.png, reference.sha256, rubric.json
197
+ ```
198
+
199
+ `sacpaint worldevals-entry` prints the `Benchmark(...)` block for a
200
+ [WorldEvals](https://github.com/robocurve/worldevals) catalog pull request.
201
+
202
+ ## Status
203
+
204
+ | Piece | State |
205
+ |---|---|
206
+ | Task `sacpaint/line-v0`, five scorers, three epochs | done, registered via entry points |
207
+ | Mock plotter + oracle/idle policies | done; the whole stack runs with no hardware |
208
+ | Marker-free rectification (given corners, ArUco, plain sheet) | done, tested under perspective |
209
+ | `sacpaint score / new / run / export / worldevals-entry` | done |
210
+ | `--policy agent` (frontier LLM) | works against the mock; needs a key or the subscription shim |
211
+ | OpenCastor / SO-ARM101 embodiment | see docs/opencastor.md |
212
+ | Hardware runs | not yet |
213
+ | WorldEvals catalog entry | after the first real-robot log |
214
+
215
+ ## Development
216
+
217
+ ```bash
218
+ git clone https://github.com/craigm26/sacpaint && cd sacpaint
219
+ uv venv && source .venv/bin/activate && uv pip install -e ".[dev,agent]"
220
+ pytest
221
+ ```
222
+
223
+ Regenerate the built-in assets (only when the reference itself changes; it re-versions the task):
224
+
225
+ ```bash
226
+ python -c "from sacpaint.reference import write_assets; write_assets('src/sacpaint/assets')"
227
+ ```
228
+
229
+ MIT.
@@ -0,0 +1,202 @@
1
+ # Sacramento PaintBench (`sacpaint`)
2
+
3
+ A physical-AI benchmark on [Inspect Robots](https://github.com/robocurve/inspect-robots):
4
+ a robot with a pen must reproduce a fixed reference drawing from camera
5
+ feedback. One fixed prompt, one pinned reference image, geometric scoring that
6
+ anyone can recompute offline from a photo. No printed markers, no special
7
+ fixture: a sheet of paper on a desk and any camera, including an iPhone.
8
+
9
+ > Can a general-purpose frontier model reproduce a visual target with a physical
10
+ > tool, using camera feedback to correct itself, with no task-specific training?
11
+
12
+ The built-in reference is a simplified Sacramento composition: the Tower
13
+ Bridge above the Capitol dome and cupola, joined by the Capitol Mall, two
14
+ building masses, a horizon. Its SHA-256 is the benchmark's identity. The
15
+ physical sheet is 150 × 200 mm (A5 or half-letter with margins), small enough
16
+ for a desk arm such as the SO-ARM101 to reach every corner.
17
+
18
+ ![reference](src/sacpaint/assets/sacramento-line-v0.png)
19
+
20
+ ## Sixty seconds, no robot
21
+
22
+ ```bash
23
+ pip install sacpaint
24
+ sacpaint score photo-of-my-drawing.jpg # any photo of a finished sheet -> score + overlay
25
+ ```
26
+
27
+ `score` finds the sheet in the photo (markers if present, otherwise the largest
28
+ bright quadrilateral, otherwise corners you pass with `--corners`), rectifies
29
+ it to the canonical canvas, and writes three files next to the photo: the
30
+ rectified canvas, an overlay with the reference ink in red, and a JSON
31
+ breakdown. That is the whole "process a new eval" path for a drawing made by
32
+ any robot, any policy, any hardware.
33
+
34
+ The full benchmark, in the built-in mock plotter world:
35
+
36
+ ```bash
37
+ sacpaint run --policy sacpaint_trace --embodiment sacpaint_plotter --no-rerun --no-prompt # oracle, composite ~0.98
38
+ sacpaint run --policy sacpaint_idle --embodiment sacpaint_plotter --no-rerun --no-prompt # floor, 0.00
39
+ sacpaint export logs --out submission --label oracle # publishable bundle
40
+ ```
41
+
42
+ With a frontier model driving the same mock world (needs `pip install inspect-robots-agent` and a key):
43
+
44
+ ```bash
45
+ ANTHROPIC_API_KEY=... sacpaint run --policy agent --model anthropic/claude-fable-5 --embodiment sacpaint_plotter -- -P images=on_demand
46
+ ```
47
+
48
+ No API key? One flag runs the same policy through a Claude subscription (the
49
+ Claude Code CLI, logged in on the machine) for development runs. Scores from
50
+ this path are labelled `wire=claude-code-cli` and are not leaderboard-comparable;
51
+ see [docs/subscription.md](docs/subscription.md).
52
+
53
+ ```bash
54
+ sacpaint run --subscription --model haiku --policy agent --embodiment sacpaint_plotter --max-llm-calls 40 --no-rerun --no-prompt
55
+ ```
56
+
57
+ ## The prompt
58
+
59
+ Fixed. Do not tune it per model.
60
+
61
+ > Draw the reference image on the canvas with the pen. You may look at the
62
+ > overhead camera to inspect your work and make corrections. Stop when you
63
+ > believe the drawing is complete.
64
+
65
+ The model receives the reference as an image stream named `reference` and the
66
+ canvas as a stream named `overhead`, because the agent policy attaches camera
67
+ frames to observations and the instruction cannot carry an image. Every
68
+ embodiment exposes those two streams.
69
+
70
+ ## Scoring
71
+
72
+ All scorers are pure readers of the final canvas. Nothing calls a model. The
73
+ final canvas is the `observe_parked()` frame (pen lifted clear), rectified
74
+ unless the embodiment marks it canonical, then thresholded to ink.
75
+
76
+ | Scorer | Weight | What it measures |
77
+ |---|---|---|
78
+ | `landmark_geometry` | 0.45 | Per landmark: precision × recall of ink inside its box at 1% of the canvas diagonal (2.5 mm) after centroid alignment (presence), times a centroid-offset term (position). Plus relations (tower over dome, same x, horizon above tower), gated on both landmarks being present. |
79
+ | `structure` | 0.30 | Precision × recall of all ink within 2% of the diagonal (5 mm) of reference ink. Product, not F1, because a dense scribble recalls everything. |
80
+ | `discipline` | 0.15 | 1 − fraction of ink farther than 2.5% of the diagonal (6 mm) from any reference ink, scaled down past 4× the reference's ink. Blank canvas scores 0. |
81
+ | `efficiency` | 0.10 | 1 − steps/max_steps for a declared finish; scaled by `structure` in the composite so finishing a bad drawing fast earns nothing. |
82
+ | `composite` | | The leaderboard number. |
83
+
84
+ Calibration on synthetic canvases:
85
+
86
+ | Canvas | composite | landmark | structure | discipline |
87
+ |---|---|---|---|---|
88
+ | perfect trace | 0.98 | 1.00 | 1.00 | 1.00 |
89
+ | perfect trace photographed at an angle, plain sheet, rectified | 0.97 | 0.97 | 1.00 | 1.00 |
90
+ | hand wobble, σ = 1 mm | 0.97 | 0.97 | 1.00 | 1.00 |
91
+ | whole drawing shifted 5 mm | 0.77 | 0.63 | 1.00 | 0.69 |
92
+ | top half only (bridge, no dome) | 0.53 | 0.38 | 0.55 | 1.00 |
93
+ | random scribble, 30 / 60 / 400 lines | 0.30 / 0.42 / 0.33 | 0.32 / 0.47 / 0.33 | 0.28 / 0.41 / 0.43 | 0.33 / 0.35 / 0.09 |
94
+ | blank | 0.00 | 0.00 | 0.00 | 0.00 |
95
+
96
+ Through the CLI with the framework's default guardrails, 3 epochs: oracle
97
+ `sacpaint_trace` composite 0.984, `sacpaint_idle` 0.000.
98
+
99
+ Known properties: global registration counts (a 5 mm offset is a placement
100
+ error, by design); a dense random scribble still collects about 0.3 to 0.4
101
+ because the reference covers much of the canvas; the tower, dome and road
102
+ dominate through their weights.
103
+
104
+ ## Tracks
105
+
106
+ | Track | Memory across episodes | Camera | Measures |
107
+ |---|---|---|---|
108
+ | Cold | none | `-P images=on_demand`, never called | raw open-loop competence |
109
+ | Closed loop (default) | none | throughout | self-correction within one drawing |
110
+ | Learning | prior attempts via `inspect-robots summarize` + `-P prior_learnings=` | throughout | improvement across 5 canvases |
111
+
112
+ The learning track reuses the framework's own `summarize` / `prior_learnings`
113
+ mechanism, so the "memory" is an auditable markdown file with a recorded hash.
114
+ Report initial, final, best-of-5, and slope per attempt.
115
+
116
+ ## Your own reference in two commands
117
+
118
+ ```bash
119
+ sacpaint new mytown --canvas 210x297 # writes ~/.sacpaint/references/mytown.spec.json + a preview PNG
120
+ sacpaint run --task sacpaint/mytown --policy sacpaint_trace --embodiment sacpaint_plotter -- -E reference=mytown
121
+ ```
122
+
123
+ A reference is a JSON file of polylines grouped by landmark (millimetres,
124
+ origin bottom-left, y up), optional landmark weights and boxes, relations
125
+ (`above`, `left_of`, `same_x`), and tolerances. Every spec in
126
+ `~/.sacpaint/references/` registers as the task `sacpaint/<name>` the moment
127
+ the package is imported. The built-in spec is at
128
+ `src/sacpaint/assets/sacramento-line-v0.spec.json`; copy it, edit it, done.
129
+
130
+ ## Real robots
131
+
132
+ Any embodiment that exposes this contract runs the benchmark unchanged:
133
+
134
+ - action space `eef_abs_pose`, dims `(x, y, z)` in metres in the canvas frame
135
+ (x right, y up the sheet, z above the paper; the pen marks at z ≤ 0.002 m,
136
+ travels at z ≥ 0.005 m);
137
+ - images `overhead` and `reference`, state `eef_pos` (3,);
138
+ - `supported_target_kinds` includes `reference_drawing`;
139
+ - `observe_parked()` lifts the pen clear and returns a fresh observation;
140
+ - corners of the sheet in the overhead frame, if known, as
141
+ `observation.extra["canvas_corners"]` (four `[x, y]` pairs, 0..1, TL TR BR BL);
142
+ otherwise the scorer finds the sheet itself.
143
+
144
+ | Body | How |
145
+ |---|---|
146
+ | Mock plotter (built in) | `--embodiment sacpaint_plotter`, options `-E reference=NAME -E photo_mode=sheet` |
147
+ | OpenCastor + SO-ARM101 with signed receipts | [docs/opencastor.md](docs/opencastor.md): `--embodiment opencastor` |
148
+ | iPhone as the overhead camera and operator microphone | the OpenCastor iOS app's Eval mode, see [docs/opencastor.md](docs/opencastor.md) |
149
+ | Any other arm | implement the contract above; `inspect-robots-so101` (LeRobot, joint space) is a fallback body that needs the agent's `move_joints` |
150
+
151
+ ## Publishing a run the way robocurve does
152
+
153
+ ```bash
154
+ sacpaint export logs --out submission --label opus
155
+ ```
156
+
157
+ produces the layout of robocurve's published run datasets (clapboardbench):
158
+
159
+ ```
160
+ submission/
161
+ ├── README.md # provenance, reference hash, how to recompute
162
+ ├── <log>.json # raw EvalLog: config, git rev, versions, scores, transcript
163
+ ├── runs/README.md # index table: model, policy, embodiment, status, composite, steps
164
+ ├── runs/<label>-<n>.md # one page per run: metadata, scores per epoch, per-landmark table,
165
+ │ # final canvas, the model's note for every tool call
166
+ ├── html/ # inspect-robots view reports (frames the model saw)
167
+ ├── canvases/ # the rectified final canvas and score JSON per trial
168
+ ├── videos/ # inspect-robots video (when ffmpeg is installed)
169
+ └── reference.png, reference.sha256, rubric.json
170
+ ```
171
+
172
+ `sacpaint worldevals-entry` prints the `Benchmark(...)` block for a
173
+ [WorldEvals](https://github.com/robocurve/worldevals) catalog pull request.
174
+
175
+ ## Status
176
+
177
+ | Piece | State |
178
+ |---|---|
179
+ | Task `sacpaint/line-v0`, five scorers, three epochs | done, registered via entry points |
180
+ | Mock plotter + oracle/idle policies | done; the whole stack runs with no hardware |
181
+ | Marker-free rectification (given corners, ArUco, plain sheet) | done, tested under perspective |
182
+ | `sacpaint score / new / run / export / worldevals-entry` | done |
183
+ | `--policy agent` (frontier LLM) | works against the mock; needs a key or the subscription shim |
184
+ | OpenCastor / SO-ARM101 embodiment | see docs/opencastor.md |
185
+ | Hardware runs | not yet |
186
+ | WorldEvals catalog entry | after the first real-robot log |
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ git clone https://github.com/craigm26/sacpaint && cd sacpaint
192
+ uv venv && source .venv/bin/activate && uv pip install -e ".[dev,agent]"
193
+ pytest
194
+ ```
195
+
196
+ Regenerate the built-in assets (only when the reference itself changes; it re-versions the task):
197
+
198
+ ```bash
199
+ python -c "from sacpaint.reference import write_assets; write_assets('src/sacpaint/assets')"
200
+ ```
201
+
202
+ MIT.
@@ -0,0 +1,65 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sacpaint"
7
+ version = "0.2.0"
8
+ description = "Sacramento PaintBench: draw a fixed Sacramento skyline reference from camera feedback, scored by landmark geometry. An Inspect Robots benchmark."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Craig Merry" }]
13
+ keywords = ["robotics", "benchmark", "inspect-robots", "evaluation", "vision-language-action"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ ]
20
+ dependencies = [
21
+ "inspect-robots>=0.58.0",
22
+ "numpy>=1.26",
23
+ "opencv-python-headless>=4.9",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/craigm26/sacpaint"
28
+ Issues = "https://github.com/craigm26/sacpaint/issues"
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=8", "ruff>=0.5", "build", "twine"]
32
+ agent = ["inspect-robots-agent>=0.26.0"]
33
+ opencastor = [] # stdlib only; kept as a stable install target
34
+
35
+ [project.scripts]
36
+ sacpaint = "sacpaint.cli:main"
37
+ sacpaint-claude-shim = "sacpaint.claude_shim.__main__:main"
38
+
39
+ [project.entry-points."inspect_robots.tasks"]
40
+ "sacpaint/line-v0" = "sacpaint.tasks:line_v0"
41
+
42
+ [project.entry-points."inspect_robots.embodiments"]
43
+ sacpaint_plotter = "sacpaint.mock:plotter_embodiment"
44
+ opencastor = "sacpaint.opencastor.embodiment:opencastor_embodiment"
45
+
46
+ [project.entry-points."inspect_robots.policies"]
47
+ sacpaint_trace = "sacpaint.mock:trace_policy"
48
+ sacpaint_idle = "sacpaint.mock:idle_policy"
49
+
50
+ [project.entry-points."inspect_robots.scorers"]
51
+ sacpaint_landmarks = "sacpaint.scorers:landmark_geometry"
52
+ sacpaint_structure = "sacpaint.scorers:structure"
53
+ sacpaint_discipline = "sacpaint.scorers:discipline"
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+
58
+ [tool.setuptools.package-data]
59
+ sacpaint = ["assets/*.png", "assets/*.json"]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+
64
+ [tool.ruff]
65
+ line-length = 140
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ """Sacramento PaintBench: an Inspect Robots benchmark.
2
+
3
+ The task is one sentence: draw the fixed reference image on the canvas, using
4
+ the camera to inspect and correct, and stop when done. The built-in reference
5
+ is a deliberately simplified Sacramento composition (Tower Bridge above the
6
+ Capitol dome, joined by the Capitol Mall); any ``.spec.json`` of strokes makes
7
+ another. Scoring is geometric, not aesthetic: each landmark must be present,
8
+ in the right place, in the right relation to the others.
9
+ """
10
+
11
+ from sacpaint.reference import DEFAULT_REFERENCE, ReferenceSpec, available, canonical_size, get_spec, load_rubric, reference_image
12
+ from sacpaint.scorers import composite, discipline, efficiency, landmark_geometry, score_canvas, structure
13
+
14
+ __all__ = [
15
+ "DEFAULT_REFERENCE",
16
+ "ReferenceSpec",
17
+ "available",
18
+ "canonical_size",
19
+ "composite",
20
+ "discipline",
21
+ "efficiency",
22
+ "get_spec",
23
+ "landmark_geometry",
24
+ "load_rubric",
25
+ "reference_image",
26
+ "score_canvas",
27
+ "structure",
28
+ ]
@@ -0,0 +1,97 @@
1
+ {
2
+ "canvas_mm": [
3
+ 150.0,
4
+ 200.0
5
+ ],
6
+ "discipline_band_frac": 0.025,
7
+ "ink_threshold": 128,
8
+ "landmark_tolerance_frac": 0.01,
9
+ "landmarks": {
10
+ "buildings": {
11
+ "bbox": [
12
+ 0.0133,
13
+ 0.335,
14
+ 0.9867,
15
+ 0.765
16
+ ],
17
+ "weight": 1
18
+ },
19
+ "capitol_dome": {
20
+ "bbox": [
21
+ 0.2467,
22
+ 0.66,
23
+ 0.7533,
24
+ 0.865
25
+ ],
26
+ "weight": 3
27
+ },
28
+ "cupola": {
29
+ "bbox": [
30
+ 0.43,
31
+ 0.52,
32
+ 0.57,
33
+ 0.69
34
+ ],
35
+ "weight": 1
36
+ },
37
+ "horizon": {
38
+ "bbox": [
39
+ 0.0,
40
+ 0.235,
41
+ 1.0,
42
+ 0.265
43
+ ],
44
+ "weight": 1
45
+ },
46
+ "road": {
47
+ "bbox": [
48
+ 0.3533,
49
+ 0.4725,
50
+ 0.6467,
51
+ 0.665
52
+ ],
53
+ "weight": 2
54
+ },
55
+ "tower_bridge": {
56
+ "bbox": [
57
+ 0.38,
58
+ 0.24,
59
+ 0.62,
60
+ 0.5025
61
+ ],
62
+ "weight": 3
63
+ }
64
+ },
65
+ "px_per_mm": 4.0,
66
+ "relations": [
67
+ {
68
+ "a": "tower_bridge",
69
+ "b": "capitol_dome",
70
+ "kind": "same_x",
71
+ "tolerance": 0.05
72
+ },
73
+ {
74
+ "a": "tower_bridge",
75
+ "b": "capitol_dome",
76
+ "kind": "above"
77
+ },
78
+ {
79
+ "a": "cupola",
80
+ "b": "capitol_dome",
81
+ "kind": "above"
82
+ },
83
+ {
84
+ "a": "horizon",
85
+ "b": "tower_bridge",
86
+ "kind": "above"
87
+ }
88
+ ],
89
+ "structure_tolerance_frac": 0.02,
90
+ "version": "sacramento-line-v0",
91
+ "weights": {
92
+ "discipline": 0.15,
93
+ "efficiency": 0.1,
94
+ "landmark_geometry": 0.45,
95
+ "structure": 0.3
96
+ }
97
+ }