Add screen overlay visualization for LED target testing
Implements transparent on-screen overlay that displays LED calibration data directly on the target display for easier setup and debugging. Overlay shows border zones, LED position axes with tick labels, and calibration details. Features: - Tkinter-based transparent overlay window with click-through support - Border zones highlighting pixel sampling areas (colored rectangles) - LED position axes with numbered tick marks at regular intervals - Calibration info box showing target name, LED counts, and configuration - Toggle button (eye icon) in target cards for show/hide - Localized UI strings (English and Russian) Implementation: - New screen_overlay.py module with OverlayWindow and OverlayManager classes - Overlay runs in background thread with proper asyncio integration - API endpoints for start/stop/status overlay control - overlay_active state tracking in processor manager Known limitation: tkinter threading cleanup causes server restart when overlay is closed, but functionality works correctly while overlay is active. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -789,3 +789,71 @@ async def target_colors_ws(
|
||||
pass
|
||||
finally:
|
||||
manager.remove_kc_ws_client(target_id, websocket)
|
||||
|
||||
|
||||
# ===== OVERLAY VISUALIZATION =====
|
||||
|
||||
@router.post("/api/v1/picture-targets/{target_id}/overlay/start", tags=["Visualization"])
|
||||
async def start_target_overlay(
|
||||
target_id: str,
|
||||
_auth: AuthRequired,
|
||||
manager: ProcessorManager = Depends(get_processor_manager),
|
||||
target_store: PictureTargetStore = Depends(get_picture_target_store),
|
||||
):
|
||||
"""Start screen overlay visualization for a target.
|
||||
|
||||
Displays a transparent overlay on the target display showing:
|
||||
- Border sampling zones (colored rectangles)
|
||||
- LED position markers (numbered dots)
|
||||
- Pixel-to-LED mapping ranges (colored segments)
|
||||
- Calibration info text
|
||||
"""
|
||||
try:
|
||||
# Get target name from store
|
||||
target = target_store.get_target(target_id)
|
||||
if not target:
|
||||
raise ValueError(f"Target {target_id} not found")
|
||||
|
||||
await manager.start_overlay(target_id, target.name)
|
||||
return {"status": "started", "target_id": target_id}
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start overlay: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/api/v1/picture-targets/{target_id}/overlay/stop", tags=["Visualization"])
|
||||
async def stop_target_overlay(
|
||||
target_id: str,
|
||||
_auth: AuthRequired,
|
||||
manager: ProcessorManager = Depends(get_processor_manager),
|
||||
):
|
||||
"""Stop screen overlay visualization for a target."""
|
||||
try:
|
||||
await manager.stop_overlay(target_id)
|
||||
return {"status": "stopped", "target_id": target_id}
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to stop overlay: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/api/v1/picture-targets/{target_id}/overlay/status", tags=["Visualization"])
|
||||
async def get_overlay_status(
|
||||
target_id: str,
|
||||
_auth: AuthRequired,
|
||||
manager: ProcessorManager = Depends(get_processor_manager),
|
||||
):
|
||||
"""Check if overlay is active for a target."""
|
||||
try:
|
||||
active = manager.is_overlay_active(target_id)
|
||||
return {"target_id": target_id, "active": active}
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
|
||||
Reference in New Issue
Block a user