Add per-frame RHEED segmentation masks to the SDK - #96
Conversation
|
| Filename | Overview |
|---|---|
| src/atomscale/client.py | Adds the public mask-fetching API and optional timeseries enrichment; restricted timeseries requests still retrieve the complete mask artifact. |
| src/atomscale/results/rheed_image.py | Extracts the existing pycocotools decoding operation into a shared public helper without changing its decoding semantics. |
| src/atomscale/timeseries/rheed.py | Adds sparse left-join support for attaching mask metadata by absolute Frame Number while preserving unmatched timeseries rows. |
| src/atomscale/results/init.py | Exports decode_mask_rle through the atomscale.results public namespace. |
| tests/test_rheed_image.py | Adds offline coverage for range handling, raw and decoded masks, missing artifacts, and RLE round trips. |
| tests/test_rheed_timeseries.py | Covers sparse, empty, axis-free, repeated attachment, opt-in integration, and default no-fetch behavior. |
Sequence Diagram
sequenceDiagram
participant U as SDK caller
participant C as Client
participant T as RHEED timeseries endpoint
participant M as Frame-mask endpoint
participant P as RHEEDProvider
U->>C: "get_rheed_timeseries(data_id, include_masks=True)"
C->>T: "GET /rheed/timeseries/{data_id}/"
T-->>C: Timeseries rows
C->>P: to_dataframe(raw)
P-->>C: DataFrame keyed by Frame Number
alt DataFrame is non-empty
C->>M: "GET /rheed/images/{data_id}/frame_masks"
M-->>C: Sparse COCO-RLE mask rows
C->>P: attach_frame_masks(df, rows)
P-->>C: Left-joined DataFrame
end
C-->>U: RHEED timeseries DataFrame
Prompt To Fix All With AI
### Issue 1
src/atomscale/client.py:650
**Windowed queries fetch every mask**
With `include_masks=True`, `last_n` and `elapsed_seconds` restrict the timeseries response but the subsequent `get_frame_masks` call still retrieves the complete mask artifact. Most masks are then discarded by the left join, adding avoidable transfer, deserialization, latency, and memory costs for long videos.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "add masks to timeseries fetch" | Re-trigger Greptile
| ts_df = provider.to_dataframe(raw) | ||
| # Skip the mask fetch when the series is empty — there are no frames to key | ||
| # masks onto, so the extra request would be wasted. | ||
| if include_masks and not ts_df.empty: |
There was a problem hiding this comment.
Windowed queries fetch every mask
With include_masks=True, last_n and elapsed_seconds restrict the timeseries response but the subsequent get_frame_masks call still retrieves the complete mask artifact. Most masks are then discarded by the left join, adding avoidable transfer, deserialization, latency, and memory costs for long videos.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/atomscale/client.py
Line: 650
Comment:
**Windowed queries fetch every mask**
With `include_masks=True`, `last_n` and `elapsed_seconds` restrict the timeseries response but the subsequent `get_frame_masks` call still retrieves the complete mask artifact. Most masks are then discarded by the left join, adding avoidable transfer, deserialization, latency, and memory costs for long videos.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
With include_masks=True, a last_n / elapsed_seconds query windowed the timeseries but get_frame_masks still fetched the whole video's mask artifact, discarding most rows in the left join. Add RHEEDProvider.frame_number_bounds() and pass from_frame/to_frame derived from the returned series so only the spanned frames' masks are fetched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
tests are passing locally |
What
Adds SDK support for the backend's per-frame RHEED segmentation-mask endpoint (
GET /rheed/images/{data_id}/frame_masks) and bundles the masks into the RHEED timeseries.New public surface
Client.get_frame_masks(data_id, *, from_frame=0, to_frame=None, decode=False)— fetches per-frame COCO-RLE masks for a processed RHEED video.to_frame=Nonefetches the whole video via a clamp sentinel (no preliminary frame-count lookup).decode=Falsereturns raw rows;decode=Truereturns{frame_number: (H, W) uint8 ndarray}.[]/{}rather than raising.from >= 0,to >= 0,to >= from) fails fast with a clearValueErrorinstead of a server 422.Client.get_rheed_timeseries(..., include_masks=False)— whenTrue, fetches the masks and joins them onto the timeseries DataFrame'sFrame Numberaxis asmask_rle/mask_height/mask_widthcolumns. Skips the fetch when the series is empty.decode_mask_rle(mask_rle, height, width)— reusable COCO-RLE → binary-mask helper, exported fromatomscale.results.Internals
RHEEDProvider.attach_frame_masks(df, mask_rows)— left-joins mask rows onto the timeseries by absolute frame number. Handles sparse coverage (rotating videos → NA for frames with no mask), missing artifact (columns present, all-NA), no-frame-axis passthrough, and re-attach (drops existing mask columns so the join never suffixes duplicates)._get_rheed_image_resultto use the shareddecode_mask_rle, so the single-frame/maskand per-frame/frame_maskspaths decode through one implementation.Why
Per-frame masks let callers overlay the segmented diffraction pattern on any frame of the processed video, keyed identically to the RHEED timeseries
Frame Numberaxis. Bundling intoget_rheed_timeseries(include_masks=True)makes the RLE first-class alongside the per-frame features so everything is keyed by frame in one DataFrame.Usage
Tests
Adds offline unit tests (monkeypatched
_get, no network):test_rheed_image.py: raw rows, whole-video sentinel, explicit ranges, RLE decode round-trip, 404→empty, invalid-range rejection, and the missing-mask regression still passes after the decode refactor.test_rheed_timeseries.py:attach_frame_maskssparse/empty/no-axis/re-attach cases, theinclude_masks=Trueintegration path, and a guard that the default makes no/frame_maskscall.All 25 offline tests pass; source is clean under CI-pinned
ruff@0.9.4.Reviewer notes / open choices
include_masksfetches the whole video's masks (not scoped to alast_n/elapsed_secondswindow). Easy to scope to the returned window if preferred.client.get()→RHEEDVideoResult.timeseries_datapath (that method has no per-type options); could be added toRHEEDProvider.build_resultif we want masks on bulkget.get_frame_masks(pairs with the existingget_frame); the source plan sketchedget_rheed_frame_masks— trivial rename if you'd rather match it.🤖 Generated with Claude Code