Skip to content

feat: optional camera view outside the workarea - #543

Open
atkaper wants to merge 2 commits into
barebaric:mainfrom
atkaper:feat/optional-camera-view-outside-workarea
Open

atkaper wants to merge 2 commits into
barebaric:mainfrom
atkaper:feat/optional-camera-view-outside-workarea

Conversation

@atkaper

@atkaper atkaper commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

I thought this is a nice feature to help positioning work-pieces, giving just a bit more camera view...
Hoping you agree ;-)

Note: by playing around with this, I see my own camera calibration is a bit difficult / off.
AI suggested a new future PR I might look at, which is choosing some different lens calibration models.
For example my machine might be better suited with a fish-eye, or one with 8 instead of 5 coefficients.

Here an image of the result:

image

And here the raw camera image for above result:

raw-camera-example-image

PR Commit Description

feat: optional camera view outside the workspace

After lens calibration and image alignment, the work surface only shows
the part of the camera image inside the workspace. The camera usually sees
more. This PR adds an optional, semi-transparent border of camera image
around the workspace to help position workpieces, while keeping the
workspace clearly distinguishable.

Highlights

  • Per-camera settings in Machine Settings → Camera → "View Outside
    Workspace": an on/off switch (default off), a margin (0–100 mm, default
    30, shown in the user's length unit) and a transparency slider (0–1,
    default 0.65; higher is more visible, like the existing camera
    transparency). Persisted with the camera; old configurations load with
    the feature off.
  • The workspace image is unchanged. The border is a separate pass,
    clipped to the area outside the workspace (even-odd clip), so the
    centre is never painted twice. With the feature off, the existing
    render path runs and no extra warp is done.
  • Reaches past lens-correction cropping. cv2.undistort keeps the
    frame size, so barrel correction pushes the frame edges out of the
    corrected image. With lens calibration, the border is sampled from the
    raw frame instead: world → corrected image (existing homography) →
    raw frame (lens model), via cached cv2.remap tables. Without lens
    calibration it uses the same perspective warp as the workspace. The
    border is rendered at the camera's own pixel density (capped at 2048
    px), so window resizes never rebuild the tables.
  • No fake content. Areas the camera does not see are transparent, not
    black; real black objects stay visible. Where the 5-coefficient lens
    model folds back (very wide lenses), pixels are masked, so the border
    never shows mirrored content. The margin is clamped at render time to
    what the camera actually covers; the stored value is never changed.
  • One frame for both passes. A new paired API,
    CameraController.get_work_surface_images(), snapshots the processed
    and raw frame once under the frame lock. The existing
    get_work_surface_image() keeps its signature and output, so stock
    detection and other consumers are unaffected.
  • Cache hygiene. Processing jobs are de-duplicated and dropped when
    stale (new frame, settings change, element removed). A cached border is
    only drawn while it matches the current alignment, workspace size and
    margin, so it disappears immediately when disabled or invalidated.
    Transparency changes apply at paint time.
  • Scope. Only the main work surface opts in
    (outside_view_supported=True); the sketch editor is unchanged.
    Calibration data, alignment, machine dimensions and job bounds are
    untouched.

Files

  • rayforge/camera/models/camera.py: three settings with validation
    (clamped; NaN/inf rejected), signals and persistence.
  • rayforge/camera/outside_view.py (new): border geometry, coverage
    extent, lens fold limit, raw-frame remap tables.
  • rayforge/camera/controller.py: paired single-frame render and caches.
  • rayforge/ui_gtk/canvas2d/elements/camera_image.py: outside pass,
    clipping, cache keys and stale-job checks; surface.py opts in.
  • rayforge/ui_gtk/camera/outside_view_widget.py (new) and
    camera_preferences_page.py: settings group in the Camera tab.
  • rayforge/locale/*: 8 new strings, appended to the template and all
    catalogs (additions only).
  • Tests: tests/camera/test_camera.py, tests/camera/test_controller.py,
    tests/ui_gtk/canvas2d/test_camera_image.py,
    tests/ui_gtk/camera/test_outside_view_widget.py.

How to review / test

  1. Use a camera with lens calibration and image alignment. In Machine
    Settings → Camera, enable "Show Outside Workspace".
  2. Check that the workspace image looks exactly as before, and that the
    border lines up with it at the workspace edge (no seam or offset).
  3. Change margin and transparency; zoom out or pan to see the border.
    Toggle off: the border disappears immediately.
  4. Toggle the global camera visibility, disable the camera, and switch
    between cameras: no stale border remains.
  5. Without alignment, nothing extra is drawn.
  6. Automated: pixi run pytest tests/camera and
    pixi run pytest -m ui tests/ui_gtk/camera tests/ui_gtk/canvas2d.

Known limitations

  • The border is extrapolated from the bed-plane alignment: objects above
    the bed (gantry, walls) show parallax.
  • With very wide-angle lenses the 5-coefficient lens model only holds up
    to a certain distance from the image centre; beyond it the border is
    cut (seen as a rounded or clipped outline at large margins). A follow-up
    for selectable lens models (rational, fisheye) is planned.
  • The border is taken from the frame before noise reduction, so it is not
    smoothed when noise reduction is enabled.
  • With lens calibration, the first render after an alignment,
    calibration or margin change builds remap tables (~120 ms on a
    1440x1080 camera); later frames and window resizes reuse them (~3-7 ms
    per update). The tables are freed when the feature is turned off.

🤖 Generated with Claude Code

This branch was successfully deployed

1 active deployment
pypi — 458ba41b Deployed Oct 10, 2026 by atkaper via Build and Publish Wheel #2204
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