Public: anyone can read this; the crew can change it.
Migrated from github.com/Trevato/SO-ARM100
Grow stills at 1920×1080, cropped to the camera's region and colour-corrected (split from #584) #603
Check · Review · Land done · Live — Landed; nothing deploys from this branch, so this is the end of its path.
The grow box's stills are now taken at 1920×1080 when the camera is one of the Pi's, while the stream stays at 640×360. Each still is cropped to the camera's region, and a colour-corrected copy is kept beside the raw one. `bun test` passes (1210 pass, 3 skipped, 0 fail). Nothing here has run against a real camera or a real Pi: the grab, the sudo calls and the service restart are tested only with s…
shipped merged
The grow box's stills are now taken at 1920×1080 when the camera is one of the Pi's, while the stream stays at 640×360. Each still is cropped to the camera's region, and a colour-corrected copy is kept beside the raw one. bun test passes (1210 pass, 3 skipped, 0 fail). Nothing here has run against a real camera or a real Pi: the grab, the sudo calls and the service restart are tested only with stand-ins.
What changed
- Full-size grab on the Pi (
src/camera-still.ts,POST /camera-still/<n>insrc/server.ts): ustreamer holds the camera, so the arm server stopsso101-camera@<n>. It then grabs one MJPEG frame at 1920×1080 withv4l2-ctl, skipping the first 15 frames while the camera settles. The service is started again however the grab went, so the stream comes back at its own 640×360. The stream drops out for about two seconds every ten minutes. Commands are argument lists built from a checked unit number and are never run through a shell. One grab runs at a time: a second request gets 409, a failed grab 502, and the simulation 501. - Pausing needs a sudo rule: the arm server's user must be allowed to run
systemctl stopandsystemctl starton the camera units, and nothing more.Software/so101/README.mdgives the exact sudoers lines. - Crop and correction (
src/grow-frame.ts): the relay crops withcropRect(look.region, width, height)using the still's real size, so a camera that can't do 1080p still gets the right crop. When the camera has gains, it also keeps a copy withapplyGains, and the raw still is never corrected. With no region and no gains, the camera's JPEG is kept byte for byte. A 1080p decode takes about half a second, so it runs in a worker (src/grow-frame-worker.ts) and pages aren't held up while it does. - Fallback: if the Pi can't grab a still (arm offline, no unit, no sudo rule), the stream's own snapshot is used and cropped the same way.
- Storage and routes (
src/grow-stills.ts): file names now record whether a still is cropped (k) and which file is the corrected copy (c). The copy is written first and pruned with its still./grow/stillsnow reportscroppedandcorrectedfor each still, andGET /grow/stills/<ms>-corrected.jpgserves the copy. - Card (
src/ui/grow-timelapse.js): shows the corrected copy with "· corrected" after its time, and a Corrected button switches back to the raw colours. A still that is already cropped isn't cropped again when the green area is measured. - One new dependency:
jpeg-js, pure JavaScript with no dependencies of its own, because neither Bun nor Node can decode a JPEG. The reason is in both READMEs.
Not done: the optional white photo light around each still. The grow light's switch has no photo light yet, and the README says so.
What to look at: src/grow-frame.test.ts covers the issue's test:
- A fixture still is cropped to the right half and its corrected copy is about 2× red; the raw one keeps its own colours and the input bytes are unchanged.
- The worker gives the same result as the direct call.
- Raw and corrected copies are stored together and pruned together.
- The stream is started again after a failed grab, and a second grab while one runs is refused.
- The
/camera-stillroute answers correctly, including on the simulation. - An end-to-end relay run stores a cropped still with its
-corrected.jpgcopy.
To try it on the Pi: add the sudoers line, draw a region and press Measure colour on /admin → Cameras → Tune, then wait one capture interval. The grow card should then show a still cropped to the region, labelled "corrected".
🤖 Generated with Claude Code
Does what #584 asked: stills grabbed at 1920x1080, cropped by cropRect on the still own size, a corrected copy beside the raw one, a Corrected toggle on the card. The commands are argument lists with a checked unit, the stream is started again in a finally, one grab at a time, and the tests cover crop, gains, worker, store, route and an end-to-end relay run. The preview answers 200. The new route follows the same trust model as /camera-profile. jpeg-js is justified in both READMEs. The card keeps working without JavaScript (latest still plus link). Two things I would do next: (1) when systemctl start fails after a grab, the stream stays down until the next capture ten minutes later and only a console.error says so; retry the start once or twice and surface it on the Tune/camera status, since the live cameras must not stay dead silently. (2) In GrowStills.add, if the raw write throws after the corrected copy was written, the orphaned c-file is never listed or pruned (files() only counts copies that have a raw still); remove the copy on failure or prune orphans.
The grow box's stills are now taken at 1920×1080 when the camera is one of the Pi's, while the stream stays at 640×360. Each still is cropped to the camera's region, and a colour-corrected copy is kept beside the raw one.
bun testpasses (1210 pass, 3 skipped, 0 fail). Nothing here has run against a real camera or a real Pi: the grab, the sudo calls and the service restart are tested only with stand-ins.What changed
src/camera-still.ts,POST /camera-still/<n>insrc/server.ts): ustreamer holds the camera, so the arm server stopsso101-camera@<n>. It then grabs one MJPEG frame at 1920×1080 withv4l2-ctl, skipping the first 15 frames while the camera settles. The service is started again however the grab went, so the stream comes back at its own 640×360. The stream drops out for about two seconds every ten minutes. Commands are argument lists built from a checked unit number and are never run through a shell. One grab runs at a time: a second request gets 409, a failed grab 502, and the simulation 501.systemctl stopandsystemctl starton the camera units, and nothing more.Software/so101/README.mdgives the exact sudoers lines.src/grow-frame.ts): the relay crops withcropRect(look.region, width, height)using the still's real size, so a camera that can't do 1080p still gets the right crop. When the camera has gains, it also keeps a copy withapplyGains, and the raw still is never corrected. With no region and no gains, the camera's JPEG is kept byte for byte. A 1080p decode takes about half a second, so it runs in a worker (src/grow-frame-worker.ts) and pages aren't held up while it does.src/grow-stills.ts): file names now record whether a still is cropped (k) and which file is the corrected copy (c). The copy is written first and pruned with its still./grow/stillsnow reportscroppedandcorrectedfor each still, andGET /grow/stills/<ms>-corrected.jpgserves the copy.src/ui/grow-timelapse.js): shows the corrected copy with "· corrected" after its time, and a Corrected button switches back to the raw colours. A still that is already cropped isn't cropped again when the green area is measured.jpeg-js, pure JavaScript with no dependencies of its own, because neither Bun nor Node can decode a JPEG. The reason is in both READMEs.Not done: the optional white photo light around each still. The grow light's switch has no photo light yet, and the README says so.
What to look at:
src/grow-frame.test.tscovers the issue's test:/camera-stillroute answers correctly, including on the simulation.-corrected.jpgcopy.To try it on the Pi: add the sudoers line, draw a region and press Measure colour on /admin → Cameras → Tune, then wait one capture interval. The grow card should then show a still cropped to the region, labelled "corrected".
🤖 Generated with Claude Code
Closes #589