Skip to content

feat: capture resolution + detection size dropdowns#1833

Open
5uck1ess wants to merge 2 commits into
hacksider:mainfrom
5uck1ess:pr/resolution-and-det-size
Open

feat: capture resolution + detection size dropdowns#1833
5uck1ess wants to merge 2 commits into
hacksider:mainfrom
5uck1ess:pr/resolution-and-det-size

Conversation

@5uck1ess

@5uck1ess 5uck1ess commented May 19, 2026

Copy link
Copy Markdown

Summary

Adds two dropdowns to the Camera card on the main window:

  • Resolution — requested webcam capture size (640×480 / 960×540 / 1280×720 / 1920×1080). Applied on next Live start.
  • Det size — face detection input size (160 / 320 / 640). Triggers face analyser re-init so the change takes effect immediately.

Also exposes --det-size as a CLI flag for headless runs.

Why

Resolution: Previously VideoCapturer.start() was hardcoded to PREVIEW_DEFAULT_WIDTH/HEIGHT which mixes "the preview window's pixel size" with "the camera's capture resolution" — they're separate concerns. With this PR they're decoupled and the capture side becomes user-configurable.

Why these choices specifically: 640×480 is native on virtually every USB 2.0 webcam and the safest 30fps mode (USB 2.0 isochronous bandwidth caps ~30fps on common YUYV at this size). Higher tiers are useful for users with capture cards or USB 3.0 webcams; the tooltip explains that 720p+ typically drops to ~10fps over USB 2.0 due to bandwidth, so users know what they're trading.

Det size: insightface defaults to 640×640 detection which is plenty accurate but expensive per frame. At 320 or 160 the detector runs much faster — a meaningful speedup when the face is large in frame (live webcam usage), with negligible accuracy loss for that distance regime. Currently the only way to change det_size is by editing the source.

What changed

  • modules/globals.py
    • New det_size: int = 640 and capture_resolution: tuple = (640, 480) globals with documentation.
  • modules/face_analyser.py
    • The hardcoded DET_SIZE = (640, 640) constant is replaced by _current_det_size() that reads modules.globals.det_size per call.
    • New reset_face_analyser() clears the cached FACE_ANALYSER so the next call re-prepares insightface with the new size.
  • modules/core.py — new --det-size argparse flag with choices=[160, 320, 640] for headless runs.
  • modules/ui.py
    • Camera card now uses a QGridLayout (was a single row) with the two new dropdowns on rows 1 and 2 below the camera selector + Live button.
    • _on_resolution_change writes the selected (w, h) tuple to modules.globals.capture_resolution.
    • _on_det_size_change writes the selected int and calls reset_face_analyser() so the new size kicks in on the next analyser call.
    • WebcamPreviewWindow reads modules.globals.capture_resolution instead of PREVIEW_DEFAULT_WIDTH/HEIGHT (the constants still drive preview-window sizing, separate concern).

Test plan

  • Defaults: launch, Camera card shows Resolution=640x480 and Det size=640 x 640. Live mode behaves as before
  • Switch Resolution to 1280 x 720, start Live → console shows [VideoCapturer] 1280x720 or the camera's nearest supported size
  • Switch Det size from 640 → 320 while no Live is running → next face-analysis call (e.g. starting Live with map_faces) uses the new size
  • Switch Det size mid-Live → analyser re-inits on the next frame; no crash
  • python run.py --det-size 320 headless honors the flag

No new dependencies.

Summary by Sourcery

Add configurable camera capture resolution and face detection size for both GUI and headless live mode.

New Features:

  • Introduce Resolution dropdown in the Camera card to choose webcam capture size for live mode.
  • Introduce Det size dropdown in the Camera card to configure face detection input resolution and reinitialize the analyser on change.
  • Expose a --det-size CLI flag to control face detection input size in headless runs.

Enhancements:

  • Make the face analyser read a configurable global det_size and allow forcing a re-init when it changes.
  • Have the webcam preview use the global capture_resolution instead of hardcoded preview dimensions.

Ported behavior from April 2026 Fork:

- globals.det_size (160/320/640) — face detection input resolution.
  Lower = faster, less accurate at distance.
- globals.capture_resolution (tuple) — requested webcam resolution.
  Camera may negotiate down; actual size logged by VideoCapturer.

UI changes (camera card now uses QGridLayout):
- Resolution dropdown: 640x360, 640x480, 960x540, 1280x720, 1920x1080.
  Applies on next Live start.
- Det size dropdown: 160, 320, 640. Triggers FACE_ANALYSER re-init
  via reset_face_analyser() so the new size takes effect on next call.

Backend changes:
- face_analyser.py: DET_SIZE constant replaced with _current_det_size()
  that reads modules.globals.det_size. Added reset_face_analyser()
  helper for hot-swap.
- ui.py: VideoCapturer.start() now passes globals.capture_resolution
  instead of hardcoded PREVIEW_DEFAULT_WIDTH/HEIGHT (which are kept
  for preview window sizing, separate concern).
- core.py: --det-size CLI flag (choices: 160, 320, 640).
@sourcery-ai

sourcery-ai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds configurable webcam capture resolution and face-detection input size, wires them through globals/CLI, updates the face analyser to use a dynamic det_size with reset support, and reworks the Camera card UI layout to expose the new dropdowns and use the selected capture resolution when starting the preview.

Sequence diagram for dynamic det_size change and face analyser re-init

sequenceDiagram
    actor User
    participant CameraCardUI as CameraCardUI
    participant Globals as modules.globals
    participant FaceAnalyserModule as modules.face_analyser
    participant InsightFace as FaceAnalysis

    User->>CameraCardUI: select det size in cb_det_size
    CameraCardUI->>CameraCardUI: _on_det_size_change(idx)
    CameraCardUI->>Globals: det_size = selected_value
    CameraCardUI->>FaceAnalyserModule: reset_face_analyser()
    FaceAnalyserModule->>FaceAnalyserModule: FACE_ANALYSER = None

    User-->>CameraCardUI: start Live / run face analysis
    CameraCardUI->>FaceAnalyserModule: get_face_analyser()
    FaceAnalyserModule->>FaceAnalyserModule: _current_det_size()
    FaceAnalyserModule->>Globals: read det_size
    FaceAnalyserModule-->>FaceAnalyserModule: (s, s)
    FaceAnalyserModule->>InsightFace: FaceAnalysis(model=..., providers=...)
    FaceAnalyserModule->>InsightFace: prepare(ctx_id=0, det_size=_current_det_size())
    FaceAnalyserModule-->>CameraCardUI: FACE_ANALYSER instance
Loading

Sequence diagram for capture_resolution selection and VideoCapturer.start

sequenceDiagram
    actor User
    participant CameraCardUI as CameraCardUI
    participant Globals as modules.globals
    participant PreviewWindow as WebcamPreviewWindow
    participant VideoCapturer as VideoCapturer

    User->>CameraCardUI: select Resolution in cb_resolution
    CameraCardUI->>CameraCardUI: _on_resolution_change(idx)
    CameraCardUI->>Globals: capture_resolution = (w, h)

    User->>CameraCardUI: click Live
    CameraCardUI->>PreviewWindow: __init__(camera_index)
    PreviewWindow->>Globals: req_w, req_h = capture_resolution
    PreviewWindow->>VideoCapturer: start(req_w, req_h, 60)
    VideoCapturer-->>PreviewWindow: success/failure
Loading

File-Level Changes

Change Details Files
Make webcam capture resolution user-configurable and propagate it into webcam preview start-up.
  • Replace the Camera card HBox layout with a QGridLayout to accommodate new controls and alignment.
  • Add a Resolution QComboBox with fixed presets, initialised from modules.globals.capture_resolution and updating that global on change with a status message.
  • Change WebcamPreviewWindow to read modules.globals.capture_resolution and pass the requested width/height into VideoCapturer.start instead of PREVIEW_DEFAULT_WIDTH/HEIGHT.
modules/ui.py
modules/globals.py
Make face detection input size (det_size) configurable via globals, UI, and CLI, and ensure the analyser and model optimisation honour the current size.
  • Introduce modules.globals.det_size default and wire a --det-size CLI argument into globals in parse_args for headless runs.
  • Replace the fixed DET_SIZE constant in face_analyser with a _current_det_size() helper that reads modules.globals.det_size every time.
  • Add reset_face_analyser() that clears the cached FACE_ANALYSER under lock so the next get_face_analyser() re-prepares insightface with the updated det_size.
  • Update FACE_ANALYSER.prepare and _optimize_det_model to use _current_det_size() for the det_size and CoreML input_shape.
  • Add a Det size QComboBox in the Camera card with 160/320/640 options, initialised from modules.globals.det_size, updating the global and calling reset_face_analyser() with a status message when changed.
modules/face_analyser.py
modules/core.py
modules/ui.py
modules/globals.py

Possibly linked issues

  • #unknown: PR decouples capture from preview size and adds manual resolution options, addressing webcam auto-zoom/crop regression

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The new update_status messages and some tooltips added in _build_camera_card are hardcoded English strings; consider wrapping them in _() like the other UI text so they participate in localization.
  • In _on_det_size_change, importing reset_face_analyser inside the slot will run on every change; it would be cleaner and slightly more efficient to move this import to the module level unless you are explicitly avoiding a circular import.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `update_status` messages and some tooltips added in `_build_camera_card` are hardcoded English strings; consider wrapping them in `_()` like the other UI text so they participate in localization.
- In `_on_det_size_change`, importing `reset_face_analyser` inside the slot will run on every change; it would be cleaner and slightly more efficient to move this import to the module level unless you are explicitly avoiding a circular import.

## Individual Comments

### Comment 1
<location path="modules/ui.py" line_range="759" />
<code_context>
+        self._det_size_options = [160, 320, 640]
+        for v in self._det_size_options:
+            self.cb_det_size.addItem(f"{v} x {v}")
+        cur_det = int(getattr(modules.globals, 'det_size', 640))
+        idx = self._det_size_options.index(cur_det) if cur_det in self._det_size_options else 2
+        self.cb_det_size.setCurrentIndex(idx)
</code_context>
<issue_to_address>
**suggestion:** Centralize the default det_size handling to avoid repeating literal defaults in multiple places.

The default `640` for `det_size` now lives here, in `face_analyser._current_det_size`, and in the CLI defaults. If one changes and the others don’t, the CLI, backend, and UI could diverge. Consider defining a single default (e.g., a constant in `modules.globals` or `face_analyser`) and referencing it everywhere instead of repeating the literal.

Suggested implementation:

```python
        self.cb_det_size = QComboBox()
        self._det_size_options = [160, 320, 640]

        for v in self._det_size_options:
            self.cb_det_size.addItem(f"{v} x {v}")

        # Use a centralized default detection size so UI/CLI/backend stay in sync.
        default_det_size = getattr(
            modules.globals,
            "DEFAULT_DET_SIZE",
            self._det_size_options[-1],
        )
        cur_det = int(
            getattr(
                modules.globals,
                "det_size",
                default_det_size,
            )
        )

        if cur_det in self._det_size_options:
            idx = self._det_size_options.index(cur_det)
        elif default_det_size in self._det_size_options:
            idx = self._det_size_options.index(default_det_size)
        else:
            idx = len(self._det_size_options) - 1

        self.cb_det_size.setCurrentIndex(idx)

```

To fully implement the centralization you suggested, the following should also be done elsewhere in the codebase:

1. In `modules/globals` (or a similar configuration module), define a single constant:
   ```python
   DEFAULT_DET_SIZE = 640  # or whatever default you want
   ```
2. Update `face_analyser._current_det_size` to use `modules.globals.DEFAULT_DET_SIZE` instead of an inline literal (e.g. `640`).
3. Update CLI default handling for `det_size` to also use `modules.globals.DEFAULT_DET_SIZE` (e.g. via `default=DEFAULT_DET_SIZE` or equivalent).
4. Ensure `DEFAULT_DET_SIZE` remains consistent with `self._det_size_options` (e.g., is one of `[160, 320, 640]`), or adjust the options list to match.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread modules/ui.py Outdated
Addresses sourcery-ai review on PR hacksider#1833: the default detection size
(640) was duplicated in UI cb_det_size init, face_analyser._current_det_size,
and CLI parse_args. Hoist a single DEFAULT_DET_SIZE constant in
modules.globals so UI/CLI/backend stay in sync if it ever needs to
change. UI fallback also picks the matching index instead of a
positional literal.
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