Skip to content

GeecsPanels: the viewer — zoom, pan, log scale, collapsible Live, frames only when the camera makes one - #1132

Closed
skbarber wants to merge 3 commits into
feat/geecs-panelsfrom
feat/panels-viewer
Closed

skbarber wants to merge 3 commits into
feat/geecs-panelsfrom
feat/panels-viewer

Conversation

@skbarber

@skbarber skbarber commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked PR, step 2a of the panels arc. Base: feat/geecs-panels (#1131), which is based on #1130. The maintainer asked to finish the arc before merging, so the stack merges in order at the end: #1130 → #1131 → this → the next steps. Don't delete a base branch while a child is open; GitHub auto-closes the child.

Feedback from looking at #1131: make Live collapsible, give the image the panel, add zoom, drag and log scale.

What

  • Viewer. The image fills the panel, taking most of the window's height:
    • Wheel zooms at the cursor, drag pans, double-click or Fit fits, 1:1 shows camera pixels, unsmoothed when magnified.
    • The colour-mapped frame is kept separate from the view (toView/toImage), so 2b's crosshairs and ROI box zoom and pan with the image.
  • Scale: linear, square root or log, in the same lookup table as black/white. It stretches the 8-bit stream and adds no dynamic range (said on the page).
  • Live strip: a native <details class="disc"> (rung 0). Folded, its summary keeps centroid and max; open/closed is remembered per camera.
  • Frames only when the camera makes one. FrameSource reuses a frame exactly while MJPG1:ArrayCounter_RBV stands still and sends it as X-Frame-Counter; the page skips decoding a frame it already shows. A 1 Hz camera polled at 10 Hz is now one upstream fetch and one draw a second (maintainer question on host load).
  • Fixed while measuring:
    • The reuse window was timed from the fetch's reply. A poll at the cap's period then landed inside it behind a ~55 ms fetch and got the old frame back about a third of the time: 5.5 fps shown at a 10 fps cap.
    • The window is now timed from the request, and the counter decides whenever it's known. The page also fetches the next frame while drawing the last.
    • Measured in-process: 0 repeats in 24 polls (was 10). In the browser: 9.5–10 fps.

398 lines added, 89 removed, mostly camera.js.

Tests

  • GeecsPanels: 53 passed. New tests: an unchanged counter means no new fetch (proven to bite); no counter falls back to the window; the window is timed from the request; a changed counter refetches inside the window; the frame route sends X-Frame-Counter.
  • GeecsWebTheme: 198 passed. Lint OK.

Hardware verification (done)

Against ADTEST_CAM1 from a Mac over the VPN:

  • 9.5–10 fps at the 10 fps cap.
  • 467% zoom on a log scale, and the folded Live summary. Screenshots were reviewed; both rooms are dark.
  • OWED: a lit scene, and a camera at 1 Hz, to see the fetch rate follow the camera.

🤖 Generated with Claude Code

skbarber and others added 3 commits October 22, 2026 22:21
…collapsible Live strip, and frames fetched only when the camera makes one

- The image fills the panel; wheel zooms at the cursor, drag pans,
  double-click or Fit fits, 1:1 shows camera pixels (unsmoothed when
  magnified). The colour-mapped frame is kept apart from the view, so later
  overlays zoom and pan with it (toView/toImage).
- Scale: linear / square root / log, in the same lookup table as the levels.
- Live folds into a native disclosure whose summary keeps centroid and max.
- FrameSource reuses a frame exactly while MJPG1's counter stands still and
  sends it as X-Frame-Counter; the page skips decoding a frame it already
  shows. A 1 Hz camera polled at 10 Hz is now one fetch and one draw a second.
- Fixed while measuring: the reuse window was timed from the fetch's reply,
  so a poll at the cap's period was handed the old frame a third of the
  time (5.5 fps shown of 10). Now timed from the request, and the counter
  decides whenever it is known. The page also fetches the next frame while
  drawing the last. Measured: 9.5-10 fps at the 10 fps cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ers, a reuse limit, per-fetch frame tags, decode errors, pixel density, the 1:1 floor

F1: the min_interval floor applies whatever the counter says, so N viewers
cost the IOC one fetch per floor (a 30 Hz camera and 5 viewers was ~50/s).
F2: a frame is never reused past max_reuse (1 s) under an unchanged
counter, and each fetch gets its own tag (X-Frame-Tag) so the page redraws
a refetch. ffmpegServer stores the JPEG before posting its counter, so this
is a net, not a known failure.
F3: a change of devicePixelRatio (window moved between screens) re-sizes.
F4: a frame that fails to decode shows the error and is retried.
F5: 1:1 reaches 100% on a frame smaller than the view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…decode-error box hides only after a frame draws; two comments brought up to date

Review round 2 on #1132 (both low).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@skbarber

Copy link
Copy Markdown
Collaborator Author

Adversarial review (fresh-context reviewer, two rounds)

# Finding Disposition
F1 With the counter known there was no fetch floor: a fast camera with N out-of-step viewers cost the IOC N× fetches, which broke "N viewers cost one stream". Fixed in 051a5b4a0: the min_interval floor applies first, whatever the counter says. test_every_viewer_shares_one_fetch_per_floor_interval (5 viewers, a 100 Hz camera: at most 21 fetches/s) fails with the floor removed. Confirmed.
F2 A frame tagged by a counter that arrived before its JPEG would be served stale for a whole camera period. Fixed (safety net) in 051a5b4a0. ffmpegServer stores the JPEG (ffmpegServer.cpp:569) before callParamCallbacks (:586), so the counter should follow its JPEG. Even so, a frame is now reused at most max_reuse (1 s) under an unchanged counter, and each fetch gets its own X-Frame-Tag, so the page redraws a refetch. Pinned. Confirmed.
F3 A change of devicePixelRatio (window moved between screens) left the image mis-scaled and the cursor readout wrong. Fixed: a matchMedia resolution listener, re-armed after each change. Confirmed.
F4 A frame that failed to decode was an unhandled rejection: no error shown, "live" chip stayed up. Fixed: the error is shown and the frame retried. Confirmed.
F5 1:1 couldn't reach 100% on a frame smaller than half the view. Fixed: the floor is min(1, fit/2). Confirmed.
R2-1 (round 2) The fps counted safety refetches, so a stopped camera read about 0.5 fps. Fixed in the head commit: only a new camera counter counts.
R2-2 (round 2) The decode-error box flickered when every frame failed. Fixed: it hides only after a frame draws.
R2-3 (round 2) Two comments (__main__, the feed.py docstring) described the old reuse rule. Fixed.

Round-2 items were low severity and fixed without a third round (review stopping rule). Noted: for a camera at 1 Hz or faster, the 1 s net doesn't shorten a stale frame below one camera period; it's a backstop, not a guarantee.

After the fixes: GeecsPanels 54 passed, GeecsWebTheme 198 passed, lint OK. Live: 9.5–10 fps at the 10 fps cap.

@skbarber skbarber left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed this PR in the #1130–#1135 stack, including the existing review fixes. No additional actionable findings in this PR. Validation at stack head dc8ac07: panel suite plus Core EPICS bootstrap/light-import tests, 143 passed (including JavaScript syntax checks). Targeted offline probes reproduced the findings on #1134/#1135. No hardware writes or live camera testing performed.

@skbarber
skbarber deleted the branch feat/geecs-panels October 23, 2026 22:46
@skbarber skbarber closed this Oct 23, 2026
@skbarber

Copy link
Copy Markdown
Collaborator Author

Closed by GitHub when its base branch was deleted; its base cannot be changed while closed. Superseded by a new PR from the same head commit (feat/panels-viewer → master). The review record stays here.

skbarber added a commit that referenced this pull request Oct 23, 2026
GeecsPanels: the viewer — zoom, pan, log scale, collapsible Live, frames only when the camera makes one (replaces #1132)
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.

1 participant