Carry an image state through the socket.io upload - #96
Merged
Merged
Conversation
The loop's upload endpoint reads a `state` from each image's json and files the image into that state instead of its default `inbox`, but `ImageMetadata` declared no such field: dacite dropped the key on the way in and `asdict` could not emit it on the way out, so a client had no way to reach it. Declaring the field is enough — the socket.io handler and the outbox are generic over the dataclass, so both already pass it on. Assisted-by: Claude:claude-opus-5
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The updated README section still documents an incorrect Socket.IO upload return value and should be corrected while this area is being modified.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends the library’s Socket.IO image upload pipeline to preserve an image “state” (e.g. trash) through ImageMetadata so the Learning Loop can file uploaded images into a non-default kanban column.
Changes:
- Add an optional
statefield toImageMetadataso it survivesdacite.from_dict(...)parsing andasdict(...)serialization. - Document the new
statemetadata key in the Socket.IO upload docs (README + detector node docstring). - Add a detector Socket.IO test asserting
stateis written into the outbox metadata JSON file.
File summaries
| File | Description |
|---|---|
| README.md | Documents state as an accepted Socket.IO upload metadata key. |
| learning_loop_node/tests/detector/test_client_communication.py | Adds a test ensuring state reaches the outbox JSON metadata file. |
| learning_loop_node/detector/detector_node.py | Updates the Socket.IO upload event docstring to mention state. |
| learning_loop_node/data_classes/image_metadata.py | Adds state to the ImageMetadata wire type. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
HannesDeittert
requested review from
denniswittich and
jfrieli
and removed request for
jfrieli
September 7, 2026 13:54
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
denniswittich
approved these changes
Sep 9, 2026
denniswittich
added a commit
to zauberzeug/rosys
that referenced
this pull request
Sep 11, 2026
### Motivation The Learning Loop organises images as a kanban board, and its upload endpoint accepts the state an image should enter in — `trash` among them. That is useful for images worth keeping but not worth annotating: they reach the loop without being pushed into the annotation queue. `Detector.upload()` had no way to express that, so every uploaded image landed in the loop's default state. ### Implementation - New `ImageState` enum in `vision/detector.py` with `INBOX` and `TRASH`, exported from `rosys.vision`. - `Detector.upload()` takes an optional `state`; `DetectorHardware` puts it into the metadata it emits, `DetectorSimulation` logs it. - Only the two states that are meaningful on upload are offered. The loop knows more (`backlog`, `annotate`, `review`, `complete`), but uploading straight into those would assert annotations the image does not have yet. - An enum rather than a bare string, to match the `Autoupload` enum next to it and to keep a further state a new member instead of a signature change. The key is written unconditionally, as `None` when no state is given, matching how `source` and `creation_date` already behave. `None` means "loop decides" — deliberately not a hardcoded `'inbox'`, so a future change to the loop's default reaches existing robots without a release here. Deliberately left off `detect()` and `batch_detect()`: the autoupload path decides for itself whether an image is interesting enough to upload, so "file this into the trash" is not a meaningful instruction there, and keeping it off narrows the detector-node contract. ### Dependency Needs a `learning_loop_node` release containing [#96](zauberzeug/learning_loop_node#96) to have any effect — `ImageMetadata` there declares the field this sends. Against an older detector node the field is silently dropped, so this is safe to merge and release independently. ### Testing New `tests/test_detector.py` asserts the emitted socket.io payload carries the state, and carries `None` when none is given — the RoSys-side mirror of the pass-through test in #96. [※](https://zauberzeug.github.io/colophon/#m=claude-opus-5&t=Requirement%20and%20scoping%20decisions%20from%20the%20author%3B%20cross-repo%20investigation%2C%20implementation%20and%20wording%20from%20the%20model.&a=claude-code "claude-opus-5: Requirement and scoping decisions from the author; cross-repo investigation, implementation and wording from the model.") --------- Co-authored-by: Dennis <dennis@zauberzeug.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The Learning Loop organises images as a kanban board and its upload endpoint already accepts a
stateper image, filing it into that state instead of the defaultinbox—trashincluded. A robot that wants to archive an image without pushing it into the annotation queue therefore only needs a way to say so.ImageMetadatadeclared no such field, and it was lost twice over:dacite.from_dictis called non-strict, so an unknownstatekey in the metadata was discarded silently, andOutbox._save_files_to_diskwritesasdict(image_metadata), which can only emit declared fields. A client could send the key and see no error, no log line, and no effect.Implementation
ImageMetadatagainsstate: str | None = None.uploadevent and the README's upload section document the field.test_sio_upload_with_stateasserts the state reaches the json file the outbox uploads.Deliberately a plain
strrather than an enum: the state vocabulary belongs to the loop backend, which this library does not depend on, and its own exchange type accepts a bare string here. Written asstr | Nonerather thanOptional[str]to match CONTRIBUTING and keep ruff quiet, which does read slightly against the neighbouring fields in that dataclass — say the word if you would rather have local consistency.Compatibility
Additive and optional. Existing clients send no
stateand get the loop default; older nodes receiving astatefrom a newer client keep ignoring it exactly as before.Testing
learning_loop_node/tests/detector→ 28 passed, plus the new test.test_outbox.py::test_set_outbox_modeerrors, on this branch and onmainalike: it wantsLOOP_HOSTfrom a local.envthat is not present here.uvx ruff check .→ 717 findings before and after; the whole diff is shifted line numbers.Follow-up
RoSys needs a matching
state=parameter onDetector.upload()before a robot can actually use this, and the detector images have to be rebuilt against a release that contains this change.※