- Implemented CameraSettingsDialog to manage UVC and Qt camera controls. - Integrated UVC parameter sliders and auto controls for brightness, contrast, saturation, hue, sharpness, gamma, white balance, backlight compensation, and exposure. - Added functionality to change white balance and exposure settings via Qt controls. - Updated MainWindow to open CameraSettingsDialog and manage UVC controller lifecycle. - Enhanced AppMenuBar to include a Camera Settings option. - Created tests for UVC controller abstraction layer and parameter info. - Documented camera specifications and supported features in new markdown files.
481 lines
6.6 KiB
Markdown
481 lines
6.6 KiB
Markdown
# Goal
|
||
|
||
Zbudować aplikację do podglądu kamery w czasie rzeczywistym (PySide6) z telemetrią, overlayami i systemem logowania do pliku — docelowo na Mac Mini z kamerą ELP.
|
||
|
||
---
|
||
|
||
# Constraints & Preferences
|
||
|
||
- Python 3.12.10, venv w `.venv-win`
|
||
- Dev: Windows 11
|
||
- Target: Mac Mini Intel i7, macOS Ventura, kamera ELP USB
|
||
- `PySide6 6.11.0`, `psutil`, `pytest`, `ruff`
|
||
- `QVideoWidget` porzucony — na Windows zasłania wszystkie child/sibling widgets przez natywny HWND D3D
|
||
- Logi:
|
||
- `logs/` w katalogu projektu
|
||
- nowy plik z timestamp per sesja
|
||
- max 20 plików
|
||
- CSV telemetrii:
|
||
- co 5 sekund
|
||
- `flush` po każdym wierszu
|
||
- CPU:
|
||
- pokazywać oba:
|
||
- sys %
|
||
- per-core % (jak Task Manager)
|
||
- Zmiana formatu kamery przez `stop+start`
|
||
- nie `setCameraFormat` na żywej kamerze
|
||
|
||
---
|
||
|
||
# Progress
|
||
|
||
## Done
|
||
|
||
### Faza 0–7 MVP
|
||
|
||
- scaffolding
|
||
- `CameraService`
|
||
- `FrameDispatcher`
|
||
- `TelemetryCollector`
|
||
- `CameraView`
|
||
- `TelemetryOverlay`
|
||
- `IOverlayLayer`
|
||
- `AppMenuBar`
|
||
- `MainWindow`
|
||
|
||
### Rendering
|
||
|
||
- Usunięto `QVideoWidget`
|
||
- `CameraView(QWidget)` z `paintEvent`
|
||
- render + overlay w jednym przejściu
|
||
|
||
### Telemetria
|
||
|
||
`TelemetrySnapshot` — pola:
|
||
|
||
- `fps`
|
||
- `target_fps`
|
||
- `frame_time_ms`
|
||
- `dropped_frames`
|
||
- `cpu_percent_sys`
|
||
- `cpu_percent_core`
|
||
- `memory_mb`
|
||
|
||
### Kamera
|
||
|
||
- `CameraService.format_changed(float)` sygnał
|
||
- `stop+start` przy:
|
||
- `set_resolution`
|
||
- `set_fps`
|
||
|
||
### Logowanie
|
||
|
||
- `_log_actual_format()`
|
||
- loguje rzeczywisty format po starcie kamery
|
||
- warning jeśli FPS się nie zgadza
|
||
|
||
### CameraFormat
|
||
|
||
`CameraFormat` dataclass z:
|
||
|
||
```python
|
||
pixel_format: str
|
||
```
|
||
|
||
Przykłady:
|
||
|
||
- `MJPG`
|
||
- `YUY2`
|
||
- `NV12`
|
||
|
||
w `camera_enumerator.py`
|
||
|
||
### Logging
|
||
|
||
`logging_setup.py`
|
||
|
||
- `FileHandler`
|
||
- `DEBUG`
|
||
- zawsze aktywny
|
||
- `StreamHandler`
|
||
- `WARNING`
|
||
- przełączalny
|
||
- nagłówek sesji:
|
||
- wersja
|
||
- platforma
|
||
- Python
|
||
- PySide6
|
||
- CPU
|
||
- RAM
|
||
- pruning starych logów
|
||
|
||
### CSV
|
||
|
||
`csv_logger.py`
|
||
|
||
- `CsvTelemetryLogger`
|
||
- throttling:
|
||
- 5 s
|
||
- `flush` po każdym wierszu
|
||
|
||
### Menu
|
||
|
||
`menu_bar.py`
|
||
|
||
- dostosowany do `CameraFormat`
|
||
- `set_log_file_path()`
|
||
- `set_console_level()`
|
||
|
||
### Config
|
||
|
||
`config.py`
|
||
|
||
- `LOG_DIR`
|
||
- `MAX_LOG_FILES`
|
||
- `TELEMETRY_CSV_INTERVAL_S`
|
||
|
||
### Main
|
||
|
||
`main.py`
|
||
|
||
- wywołuje `setup_logging()`
|
||
- przekazuje `log_path` do `MainWindow`
|
||
|
||
### Main Window
|
||
|
||
`main_window.py`
|
||
|
||
- przyjmuje:
|
||
|
||
```python
|
||
log_path: Path | None
|
||
```
|
||
|
||
- tworzy:
|
||
|
||
```python
|
||
CsvTelemetryLogger(log_path.with_suffix(".csv"))
|
||
```
|
||
|
||
- podpina do:
|
||
- `telemetry.metrics_updated`
|
||
- wywołuje:
|
||
- `menu.set_log_file_path()`
|
||
- `csv_logger.close()` w `closeEvent`
|
||
|
||
### Repo
|
||
|
||
`.gitignore`
|
||
|
||
- dodano `logs/`
|
||
|
||
### Testy i jakość
|
||
|
||
- 20 testów jednostkowych
|
||
- wszystkie zielone
|
||
- `ruff`
|
||
- czysty
|
||
- wszystkie błędy naprawione
|
||
|
||
### Artefakty sesji
|
||
|
||
Para plików per sesja:
|
||
|
||
```text
|
||
logs/duck-preview_<timestamp>.log
|
||
logs/duck-preview_<timestamp>.csv
|
||
```
|
||
|
||
---
|
||
|
||
## In Progress
|
||
|
||
- (none)
|
||
|
||
---
|
||
|
||
## Blocked
|
||
|
||
- (none)
|
||
|
||
---
|
||
|
||
# Key Decisions
|
||
|
||
## Rendering
|
||
|
||
### `QVideoWidget` → własny `CameraView(QWidget)`
|
||
|
||
Jedyny skuteczny sposób obejścia problemu z natywnym HWND na Windows.
|
||
|
||
## Overlay Architecture
|
||
|
||
### `IOverlayLayer` ABC
|
||
|
||
Pluggable overlaye bez modyfikacji `CameraView`.
|
||
|
||
Gotowe pod:
|
||
|
||
- YOLO
|
||
- OCR
|
||
- inne overlaye
|
||
|
||
## Pipeline
|
||
|
||
### `FrameDispatcher`
|
||
|
||
Pub/sub z `drop_if_busy`:
|
||
|
||
- render może gubić klatki
|
||
- telemetria nigdy
|
||
|
||
## CPU Metrics
|
||
|
||
- CPU per-core (`psutil`)
|
||
- dzielone przez `cpu_count`
|
||
- wynik zgodny z Task Manager
|
||
|
||
## Memory Metrics
|
||
|
||
- `memory_info().wset` (Windows)
|
||
- zamiast `rss`
|
||
- odpowiada `Private Working Set`
|
||
|
||
## Logging Strategy
|
||
|
||
Dwa osobne pliki per sesja:
|
||
|
||
- `.log` — diagnostyka
|
||
- `.csv` — metryki szeregów czasowych
|
||
|
||
## CameraFormat
|
||
|
||
`CameraFormat` dataclass zamiast krotki:
|
||
|
||
- niesie `pixel_format`
|
||
- potrzebny w logach
|
||
|
||
---
|
||
|
||
# Next Steps
|
||
|
||
1. Przetestować na Windows:
|
||
- czy logi powstają w `logs/`
|
||
- czy CSV zapisuje dane co ~5 s
|
||
|
||
2. Przenieść na Mac Mini i przetestować z kamerą ELP:
|
||
- sprawdzić `pixel_format` w logu
|
||
- sprawdzić wykrycie backendu AVFoundation
|
||
|
||
3. Zweryfikować overlay telemetrii na żywym obrazie
|
||
|
||
4. (Opcjonalnie) dodać kolejne `IOverlayLayer`
|
||
- YOLO
|
||
- OCR
|
||
|
||
---
|
||
|
||
# Critical Context
|
||
|
||
## Camera Enumerator
|
||
|
||
`CameraEnumerator.list_cameras()` zwraca:
|
||
|
||
```python
|
||
list[CameraInfo]
|
||
```
|
||
|
||
gdzie:
|
||
|
||
```python
|
||
formats: list[CameraFormat]
|
||
```
|
||
|
||
`menu_bar.py` i `camera_service.py` iterują po:
|
||
|
||
- `fmt.width`
|
||
- `fmt.height`
|
||
- `fmt.max_fps`
|
||
|
||
---
|
||
|
||
## CSV Logger
|
||
|
||
Ścieżka CSV:
|
||
|
||
```python
|
||
log_path.with_suffix(".csv")
|
||
```
|
||
|
||
Powstaje para:
|
||
|
||
- `.log`
|
||
- `.csv`
|
||
|
||
z tym samym timestamp.
|
||
|
||
---
|
||
|
||
## Console Logging
|
||
|
||
```python
|
||
logging_setup.set_console_level(debug: bool)
|
||
```
|
||
|
||
wywoływane z:
|
||
|
||
```python
|
||
menu_bar._on_log_toggled
|
||
```
|
||
|
||
---
|
||
|
||
## Pixel Format
|
||
|
||
```python
|
||
pixel_format_name()
|
||
```
|
||
|
||
eksportowane z:
|
||
|
||
```python
|
||
camera_enumerator.py
|
||
```
|
||
|
||
używane również w:
|
||
|
||
```python
|
||
camera_service.py
|
||
```
|
||
|
||
---
|
||
|
||
## Log Header
|
||
|
||
Nagłówek logu zawiera:
|
||
|
||
- wersję aplikacji
|
||
- platformę
|
||
- Python
|
||
- PySide6
|
||
- liczbę CPU
|
||
- RAM
|
||
|
||
---
|
||
|
||
## Uruchamianie
|
||
|
||
```bash
|
||
.venv-win\Scripts\python.exe -m app.main
|
||
```
|
||
|
||
---
|
||
|
||
## Ruff Fixes
|
||
|
||
Naprawiono:
|
||
|
||
- `E501`
|
||
- długa linia w `camera_enumerator.py`
|
||
- `F401`
|
||
- `I001`
|
||
- w `camera_service.py`
|
||
- w `main.py`
|
||
|
||
---
|
||
|
||
# Relevant Files
|
||
|
||
## Entry Point
|
||
|
||
- `app/main.py`
|
||
- wywołuje `setup_logging()`
|
||
- przekazuje `log_path`
|
||
|
||
## Config
|
||
|
||
- `app/config.py`
|
||
- `LOG_DIR`
|
||
- `MAX_LOG_FILES`
|
||
- `TELEMETRY_CSV_INTERVAL_S`
|
||
|
||
## Logging
|
||
|
||
- `app/logging_setup.py`
|
||
- konfiguracja logowania
|
||
- nagłówek sesji
|
||
- pruning
|
||
|
||
## Telemetry
|
||
|
||
- `app/telemetry/csv_logger.py`
|
||
- zapis CSV z throttlingiem
|
||
|
||
- `app/telemetry/telemetry_collector.py`
|
||
- `TelemetrySnapshot`
|
||
- `TelemetryCollector`
|
||
|
||
## Camera
|
||
|
||
- `app/camera/camera_enumerator.py`
|
||
- `CameraFormat`
|
||
- `CameraInfo`
|
||
- `pixel_format_name()`
|
||
|
||
- `app/camera/camera_service.py`
|
||
- `CameraService`
|
||
- `format_changed`
|
||
- `_log_actual_format()`
|
||
|
||
## UI
|
||
|
||
- `app/ui/main_window.py`
|
||
- kompletny
|
||
- podpięty `CsvTelemetryLogger`
|
||
|
||
- `app/ui/menu_bar.py`
|
||
- `set_log_file_path()`
|
||
- `set_console_level()`
|
||
|
||
- `app/ui/camera_view.py`
|
||
- `CameraView`
|
||
- rejestr `IOverlayLayer`
|
||
|
||
## Overlay
|
||
|
||
- `app/overlay/overlay_layer.py`
|
||
- `IOverlayLayer` ABC
|
||
|
||
- `app/overlay/telemetry_overlay.py`
|
||
- `TelemetryOverlay(IOverlayLayer)`
|
||
|
||
## Pipeline
|
||
|
||
- `app/pipeline/frame_dispatcher.py`
|
||
- pub/sub
|
||
- `drop_if_busy`
|
||
|
||
## Tests
|
||
|
||
- `tests/test_telemetry_collector.py`
|
||
- 12 testów
|
||
- mockuje `cpu_count`
|
||
|
||
- `tests/test_frame_dispatcher.py`
|
||
- 8 testów
|
||
|
||
## Repo / Docs
|
||
|
||
- `.gitignore`
|
||
- zawiera `logs/`
|
||
|
||
- `notes/01-mvp-preview.md`
|
||
- PRD
|
||
|
||
- `notes/01-mvp-plan.md`
|
||
- plan implementacji
|
||
|
||
- `notes/02-mvp-app.md`
|
||
- stan aplikacji
|
||
- historia prób i decyzji architektonicznych
|