-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
202 lines (165 loc) · 7.61 KB
/
main.py
File metadata and controls
202 lines (165 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env python3
"""
Simple Dear PyGUI Multi-Camera Display
A clean, minimal implementation to display multiple webcam feeds
"""
import numpy as np
import dearpygui.dearpygui as dpg
import time
import logging
from utils.logger import setup_logging, get_logger
from camera import Camera, detect_cameras
from gui import state
from gui.layout import resize_images, update_quadrant_sizes, rebuild_camera_layout, create_menu_bar
from gui.startup import StartupWorkflow
logger = get_logger(__name__)
# Global flag to track if we should show main UI
show_main_ui = False
def cleanup():
"""Cleanup resources"""
logger.info("Starting cleanup...")
for camera in state.cameras:
camera.release()
dpg.destroy_context()
logger.info("Cleanup complete")
def initialize_cameras_and_ui():
"""Initialize cameras and show main UI (called after startup workflow)"""
global show_main_ui
# Detect available cameras
available_cameras = detect_cameras()
if not available_cameras:
logger.warning("No cameras found. Exiting.")
return
# Initialize all available cameras (max 4 for display purposes)
max_cameras = min(len(available_cameras), 4)
logger.info(f"Initializing {max_cameras} camera(s)...")
# Load camera names from settings
from utils.settings import get_settings
settings = get_settings()
for idx, camera_id in enumerate(available_cameras[:max_cameras]):
camera = Camera(camera_id, max_display_width=640, max_display_height=480, is_video_file=False)
if camera.cap and camera.cap.isOpened():
# Load friendly name from settings
friendly_name = settings.get_camera_name(camera_id)
if friendly_name:
camera.set_friendly_name(friendly_name)
state.cameras.append(camera)
# Load saved camera positions from settings, or use default order
for camera in state.cameras:
saved_position = settings.get_camera_position_by_camera_id(camera.camera_id)
if saved_position is not None and 0 <= saved_position < 4:
state.camera_positions[id(camera)] = saved_position
else:
# Use default order (first camera at position 0, second at position 1, etc.)
for idx, cam in enumerate(state.cameras):
if cam == camera and id(camera) not in state.camera_positions:
state.camera_positions[id(camera)] = idx
break
if not state.cameras:
logger.error("Failed to initialize any cameras. Exiting.")
return
# Context already created in main(), just create texture registry
logger.info("Creating texture registry for cameras")
# Create texture registry
logger.debug("Creating textures for cameras")
with dpg.texture_registry():
for camera in state.cameras:
# Create texture for each camera
black_texture = np.zeros((camera.height, camera.width, 4), dtype=np.float32)
dpg.add_dynamic_texture(
width=camera.width,
height=camera.height,
default_value=black_texture.flatten(),
tag=camera.texture_tag
)
logger.debug(f"Created texture for {camera.name}")
# Calculate window size based on number of cameras
num_cameras = len(state.cameras)
if num_cameras == 1:
window_width = state.cameras[0].display_width + 50
window_height = state.cameras[0].display_height + 100
elif num_cameras == 2:
window_width = state.cameras[0].display_width + state.cameras[1].display_width + 100
window_height = max(state.cameras[0].display_height, state.cameras[1].display_height) + 100
else: # 3 or 4 cameras - 2x2 grid
window_width = max(state.cameras[0].display_width, state.cameras[1].display_width if len(state.cameras) > 1 else 0) * 2 + 100
window_height = max(state.cameras[0].display_height, state.cameras[1].display_height if len(state.cameras) > 1 else 0) * 2 + 100
# Create main window
with dpg.window(label="Multi-Camera Display", tag="main_window"):
# Create menu bar
create_menu_bar()
# Build initial camera layout using the rebuild function
logger.info("Building initial camera layout")
rebuild_camera_layout()
# Mark that main UI should be shown
show_main_ui = True
# Update viewport title (use set_viewport_title instead of configure)
dpg.set_viewport_title(f"Multi-Camera Display - {len(state.cameras)} Camera(s)")
dpg.set_primary_window("main_window", True)
# Start background capture threads for all cameras
logger.info("Starting background capture threads")
for camera in state.cameras:
camera.start_capture()
def main():
"""Main application entry point"""
# Setup logging - change to DEBUG for more detailed logs
setup_logging(log_level=logging.INFO, console_output=True)
logger.info("=== Multi-Camera Display Application Starting ===")
# Create DearPyGUI context first
dpg.create_context()
# Setup and show viewport early (so dialogs have somewhere to appear)
dpg.create_viewport(title="Bike Fit Analyzer - Loading...", width=800, height=600)
dpg.setup_dearpygui()
dpg.show_viewport()
# Callback for when startup workflow completes
def on_startup_complete(athlete_name, bike_name):
logger.info(f"Startup complete - Athlete: {athlete_name}, Bike: {bike_name}")
# Store athlete/bike info in state
state.current_athlete = athlete_name
state.current_bike = bike_name
# Set recordings directory if athlete and bike are selected
if athlete_name and bike_name:
from utils.settings import get_settings
settings = get_settings()
if settings.default_directory:
import os
state.recordings_directory = os.path.join(settings.default_directory, athlete_name, bike_name)
logger.info(f"Recordings will be saved to: {state.recordings_directory}")
# Initialize cameras and main UI
initialize_cameras_and_ui()
# Start the startup workflow
workflow = StartupWorkflow(on_startup_complete)
workflow.start()
# Main render loop
last_resize_time = time.time()
try:
while dpg.is_dearpygui_running():
# Only update camera textures if main UI is showing
if show_main_ui:
# Update camera textures
for camera in state.cameras:
camera.update_texture()
# Update video slider position for video files
if camera.is_video_file:
# Find which quadrant this camera is in
for cam_id, quad_pos in state.camera_positions.items():
if id(camera) == cam_id:
slider_tag = f"video_slider_{quad_pos}"
if dpg.does_item_exist(slider_tag):
position = camera.get_position()
dpg.set_value(slider_tag, position)
break
# Resize quadrants and images periodically (check every 0.1 seconds)
current_time = time.time()
if current_time - last_resize_time > 0.1:
update_quadrant_sizes()
resize_images()
last_resize_time = current_time
dpg.render_dearpygui_frame()
except Exception as e:
logger.critical(f"Fatal error in main loop: {e}", exc_info=True)
raise
finally:
cleanup()
if __name__ == "__main__":
main()