Skip to content

Carry an image state through the socket.io upload - #96

Merged
denniswittich merged 2 commits into
mainfrom
image_metadata_state
Sep 9, 2026
Merged

denniswittich merged 2 commits into
mainfrom
image_metadata_state

Conversation

@HannesDeittert

Copy link
Copy Markdown
Contributor

Motivation

The Learning Loop organises images as a kanban board and its upload endpoint already accepts a state per image, filing it into that state instead of the default inbox — trash included. A robot that wants to archive an image without pushing it into the annotation queue therefore only needs a way to say so.

ImageMetadata declared no such field, and it was lost twice over: dacite.from_dict is called non-strict, so an unknown state key in the metadata was discarded silently, and Outbox._save_files_to_disk writes asdict(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

  • ImageMetadata gains state: str | None = None.
  • Nothing else in the node needed touching: the socket.io handler parses into the dataclass and the outbox serialises out of it, so both are already generic over its fields.
  • Docstring of the socket.io upload event and the README's upload section document the field.
  • test_sio_upload_with_state asserts the state reaches the json file the outbox uploads.

Deliberately a plain str rather 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 as str | None rather than Optional[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 state and get the loop default; older nodes receiving a state from 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_mode errors, on this branch and on main alike: it wants LOOP_HOST from a local .env that 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 on Detector.upload() before a robot can actually use this, and the detector images have to be rebuilt against a release that contains this change.

※

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
Copilot AI lite review requested due to automatic review settings September 7, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 state field to ImageMetadata so it survives dacite.from_dict(...) parsing and asdict(...) serialization.
  • Document the new state metadata key in the Socket.IO upload docs (README + detector node docstring).
  • Add a detector Socket.IO test asserting state is 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.

Comment thread README.md Outdated
Comment thread learning_loop_node/data_classes/image_metadata.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@denniswittich
denniswittich merged commit 9a0076e into main Sep 9, 2026
3 checks passed
@denniswittich
denniswittich deleted the image_metadata_state branch September 9, 2026 07:59
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants