Preyanshz commited on
Commit
c686517
·
verified ·
1 Parent(s): 24e566f

Update app.py

Browse files

added Progress Bar and ETA

Files changed (1) hide show
  1. app.py +305 -284
app.py CHANGED
@@ -1,284 +1,305 @@
1
- import streamlit as st
2
- import tempfile
3
- import pandas as pd
4
- import numpy as np
5
- from PIL import Image
6
- from ultralytics import YOLO
7
-
8
- def save_uploaded_file(uploaded_file):
9
- """Save an uploaded file to a temporary file and return its path."""
10
- with tempfile.NamedTemporaryFile(delete=False, suffix=uploaded_file.name) as tmp_file:
11
- tmp_file.write(uploaded_file.getbuffer())
12
- return tmp_file.name
13
-
14
- def yolo_inference_tool():
15
- """
16
- Single-model, single-image inference subpage (example).
17
- """
18
- st.header("YOLO Model Inference Tool")
19
- st.write("Upload an image and a YOLO model (.pt) file to run inference and view detailed results.")
20
-
21
- image_file = st.file_uploader("Upload Image", type=["jpg", "jpeg", "png"], key="inference_image")
22
- model_file = st.file_uploader("Upload YOLO model (.pt)", type=["pt"], key="inference_model")
23
-
24
- if st.button("Submit (Single-Model Inference)"):
25
- if not image_file or not model_file:
26
- st.error("Please upload both an image and a model.")
27
- return
28
-
29
- # Save files
30
- image_path = save_uploaded_file(image_file)
31
- model_path = save_uploaded_file(model_file)
32
-
33
- # Load image
34
- try:
35
- image = Image.open(image_file).convert("RGB")
36
- except Exception as e:
37
- st.error(f"Error reading image: {e}")
38
- return
39
-
40
- st.subheader("Image Details")
41
- st.write(f"**Image Size:** {image.size[0]} x {image.size[1]}")
42
- st.write(f"**File Type:** {image_file.type}")
43
-
44
- # Load model
45
- try:
46
- model = YOLO(model_path)
47
- except Exception as e:
48
- st.error(f"Error loading model: {e}")
49
- return
50
-
51
- # Inference
52
- st.subheader("Inference Results")
53
- try:
54
- results = model(np.array(image))
55
- except Exception as e:
56
- st.error(f"Inference error: {e}")
57
- return
58
-
59
- r = results[0]
60
- boxes_data = []
61
- if r.boxes is not None and len(r.boxes) > 0:
62
- for i in range(len(r.boxes)):
63
- coords = r.boxes.xyxy[i].cpu().numpy() if hasattr(r.boxes.xyxy[i], "cpu") else r.boxes.xyxy[i]
64
- conf = r.boxes.conf[i].cpu().numpy() if hasattr(r.boxes.conf[i], "cpu") else r.boxes.conf[i]
65
- cls_idx = int(r.boxes.cls[i].cpu().numpy()) if hasattr(r.boxes.cls[i], "cpu") else int(r.boxes.cls[i])
66
- class_name = r.names.get(cls_idx, "Unknown")
67
- boxes_data.append({
68
- "Box": i + 1,
69
- "Coordinates": f"[{coords[0]:.1f}, {coords[1]:.1f}, {coords[2]:.1f}, {coords[3]:.1f}]",
70
- "Confidence": f"{conf:.2f}",
71
- "Class": class_name
72
- })
73
- df_boxes = pd.DataFrame(boxes_data)
74
- st.subheader("Detected Objects")
75
- st.dataframe(df_boxes, use_container_width=True)
76
- else:
77
- st.write("No objects detected.")
78
-
79
- # Annotated image
80
- try:
81
- annotated_img_bgr = r.plot(conf=True, boxes=True, labels=True)
82
- annotated_img_rgb = Image.fromarray(annotated_img_bgr[..., ::-1])
83
- st.subheader("Annotated Image")
84
- st.image(annotated_img_rgb, caption="Inference Output", use_container_width=True)
85
- except Exception as e:
86
- st.error(f"Error generating annotated image: {e}")
87
-
88
- def yolo_model_comparison_tool():
89
- """
90
- Multi-model, multi-image comparison subpage,
91
- with Weighted Scoring that uses a reciprocal-based speed metric.
92
- """
93
- st.header("YOLO Models Comparison Tool (Multi-Image, Weighted Score)")
94
- st.write(
95
- "Upload **one or more images** and **multiple YOLO model (.pt) files**. "
96
- "Then click **Submit** to run inference across all images with each model. "
97
- "We aggregate metrics (Avg Inference Time, Total Detections, Avg Confidence) "
98
- "and compute a Weighted Score that balances these factors.\n\n"
99
- "**Speed** is handled by converting inference time to a 'speed' value: `1/time`. "
100
- "That way, the fastest model is near 1, and slower models get fractions in (0,1), "
101
- "rather than forcing the slowest model to 0."
102
- )
103
-
104
- images = st.file_uploader("Upload Images", type=["jpg", "jpeg", "png"], key="comparison_images", accept_multiple_files=True)
105
- model_files = st.file_uploader("Upload YOLO models (.pt)", type=["pt"], key="comparison_models", accept_multiple_files=True)
106
-
107
- # Example weights. You can expose them as sliders if you want user customization.
108
- alpha_detection = 0.4
109
- beta_confidence = 0.3
110
- gamma_speed = 0.3 # speed = reciprocal of time
111
-
112
- if st.button("Submit (Multi-Model Comparison)"):
113
- if not images or not model_files:
114
- st.error("Please upload at least one image and at least one model.")
115
- return
116
-
117
- # We'll store aggregated metrics here
118
- model_agg_data = {}
119
- # We'll store results for each (model, image) so we can display side-by-side
120
- model_image_results = {m.name: {} for m in model_files}
121
-
122
- for model_file in model_files:
123
- model_path = save_uploaded_file(model_file)
124
- try:
125
- model = YOLO(model_path)
126
- except Exception as e:
127
- st.error(f"Error loading model {model_file.name}: {e}")
128
- continue
129
-
130
- total_inference_time = 0.0
131
- total_detections = 0
132
- sum_confidences = 0.0
133
- total_conf_count = 0
134
-
135
- for img_file in images:
136
- try:
137
- pil_img = Image.open(img_file).convert("RGB")
138
- np_img = np.array(pil_img)
139
- except Exception as e:
140
- st.error(f"Error reading image {img_file.name}: {e}")
141
- continue
142
-
143
- # Run inference
144
- try:
145
- result = model(np_img)
146
- except Exception as e:
147
- st.error(f"Inference error for model {model_file.name} on {img_file.name}: {e}")
148
- continue
149
-
150
- r = result[0]
151
- model_image_results[model_file.name][img_file.name] = r
152
-
153
- # Accumulate inference time
154
- if isinstance(r.speed, dict) and "inference" in r.speed:
155
- total_inference_time += r.speed["inference"]
156
-
157
- # Count detections & confidence
158
- if r.boxes is not None:
159
- det_count = len(r.boxes)
160
- total_detections += det_count
161
- if det_count > 0:
162
- confs = r.boxes.conf.cpu().numpy() if hasattr(r.boxes.conf, "cpu") else r.boxes.conf
163
- sum_confidences += confs.sum()
164
- total_conf_count += det_count
165
-
166
- # After all images for this model
167
- image_count = len(images)
168
- avg_inference_time = total_inference_time / image_count if image_count > 0 else float("inf")
169
- avg_confidence = sum_confidences / total_conf_count if total_conf_count > 0 else 0.0
170
-
171
- model_agg_data[model_file.name] = {
172
- "Model File": model_file.name,
173
- "Avg Inference Time (ms)": avg_inference_time,
174
- "Total Detections": total_detections,
175
- "Average Confidence": avg_confidence
176
- }
177
-
178
- if not model_agg_data:
179
- st.write("No valid models processed.")
180
- return
181
-
182
- # Display aggregated metrics
183
- df = pd.DataFrame(model_agg_data.values())
184
- st.subheader("Aggregated Metrics (Across All Images)")
185
- st.dataframe(df, use_container_width=True)
186
-
187
- # Weighted Scoring with reciprocal-based speed
188
- # 1) Normalize detection & confidence as usual
189
- detection_max = df["Total Detections"].max()
190
- confidence_max = df["Average Confidence"].max()
191
- if detection_max == 0: detection_max = 1
192
- if confidence_max == 0: confidence_max = 1
193
-
194
- df["Detection Norm"] = df["Total Detections"] / detection_max
195
- df["Confidence Norm"] = df["Average Confidence"] / confidence_max
196
-
197
- # 2) Convert time to speed = 1 / time, then normalize
198
- # If time = 0, add small epsilon to avoid dividing by zero
199
- eps = 1e-9
200
- df["Speed Val"] = 1.0 / (df["Avg Inference Time (ms)"] + eps)
201
- max_speed_val = df["Speed Val"].max() if not df["Speed Val"].isnull().all() else 1
202
- if max_speed_val == 0:
203
- max_speed_val = 1 # fallback
204
-
205
- df["Speed Norm"] = df["Speed Val"] / max_speed_val
206
-
207
- # 3) Weighted Score
208
- df["Weighted Score"] = (
209
- alpha_detection * df["Detection Norm"] +
210
- beta_confidence * df["Confidence Norm"] +
211
- gamma_speed * df["Speed Norm"]
212
- )
213
-
214
- st.subheader("Weighted Score Analysis")
215
- st.write(f"Weights: Detection={alpha_detection}, Confidence={beta_confidence}, Speed={gamma_speed}")
216
- st.dataframe(df[[
217
- "Model File",
218
- "Avg Inference Time (ms)",
219
- "Total Detections",
220
- "Average Confidence",
221
- "Detection Norm",
222
- "Confidence Norm",
223
- "Speed Val",
224
- "Speed Norm",
225
- "Weighted Score"
226
- ]], use_container_width=True)
227
-
228
- # Identify best overall model (highest Weighted Score)
229
- best_idx = df["Weighted Score"].idxmax()
230
- best_model = df.loc[best_idx, "Model File"]
231
- best_score = df.loc[best_idx, "Weighted Score"]
232
-
233
- st.markdown(f"""
234
- **Best Overall Model** based on Weighted Score:
235
- **{best_model}** (Score: {best_score:.3f}).
236
-
237
- ### Interpretation:
238
- - **Detection Norm** → fraction of the best detection count.
239
- - **Confidence Norm** → fraction of the highest average confidence.
240
- - **Speed Norm** → fraction of the highest (1/time). The fastest model is near 1; others are a fraction of that speed.
241
-
242
- If you find one factor more important, adjust the weights:
243
- - Increase **Detection** weight if you care about finding as many objects as possible.
244
- - Increase **Confidence** weight if you only trust high‐confidence detections.
245
- - Increase **Speed** weight if you need real‐time inference.
246
- """)
247
-
248
- # Display annotated images in a grid (row = image, column = model)
249
- st.subheader("Annotated Images Grid (Row = Image, Column = Model)")
250
- model_names_sorted = sorted(model_agg_data.keys())
251
-
252
- for img_file in images:
253
- st.markdown(f"### Image: {img_file.name}")
254
- columns = st.columns(len(model_names_sorted))
255
- for col, model_name in zip(columns, model_names_sorted):
256
- # Retrieve the result object
257
- r = model_image_results.get(model_name, {}).get(img_file.name, None)
258
- if r is None:
259
- col.write(f"No results for {model_name}")
260
- continue
261
-
262
- # Generate annotated image
263
- try:
264
- annotated_img_bgr = r.plot(conf=True, boxes=True, labels=True)
265
- annotated_img_rgb = Image.fromarray(annotated_img_bgr[..., ::-1])
266
- col.image(
267
- annotated_img_rgb,
268
- caption=f"{model_name}",
269
- use_container_width=True
270
- )
271
- except Exception as e:
272
- col.error(f"Error annotating image for {model_name}: {e}")
273
-
274
- def main():
275
- st.sidebar.title("Navigation")
276
- page = st.sidebar.radio("Go to", ("YOLO Model Inference Tool", "YOLO Models Comparison Tool"))
277
-
278
- if page == "YOLO Model Inference Tool":
279
- yolo_inference_tool()
280
- else:
281
- yolo_model_comparison_tool()
282
-
283
- if __name__ == "__main__":
284
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import tempfile
3
+ import pandas as pd
4
+ import numpy as np
5
+ import time
6
+ from PIL import Image
7
+ from ultralytics import YOLO
8
+
9
+ def save_uploaded_file(uploaded_file):
10
+ """Save an uploaded file to a temporary file and return its path."""
11
+ with tempfile.NamedTemporaryFile(delete=False, suffix=uploaded_file.name) as tmp_file:
12
+ tmp_file.write(uploaded_file.getbuffer())
13
+ return tmp_file.name
14
+
15
+ def yolo_inference_tool():
16
+ """
17
+ Single-model, single-image inference subpage (example).
18
+ """
19
+ st.header("YOLO Model Inference Tool")
20
+ st.write("Upload an image and a YOLO model (.pt) file to run inference and view detailed results.")
21
+
22
+ image_file = st.file_uploader("Upload Image", type=["jpg", "jpeg", "png"], key="inference_image")
23
+ model_file = st.file_uploader("Upload YOLO model (.pt)", type=["pt"], key="inference_model")
24
+
25
+ if st.button("Submit (Single-Model Inference)"):
26
+ if not image_file or not model_file:
27
+ st.error("Please upload both an image and a model.")
28
+ return
29
+
30
+ # Save files
31
+ image_path = save_uploaded_file(image_file)
32
+ model_path = save_uploaded_file(model_file)
33
+
34
+ # Load image
35
+ try:
36
+ image = Image.open(image_file).convert("RGB")
37
+ except Exception as e:
38
+ st.error(f"Error reading image: {e}")
39
+ return
40
+
41
+ st.subheader("Image Details")
42
+ st.write(f"**Image Size:** {image.size[0]} x {image.size[1]}")
43
+ st.write(f"**File Type:** {image_file.type}")
44
+
45
+ # Load model
46
+ try:
47
+ model = YOLO(model_path)
48
+ except Exception as e:
49
+ st.error(f"Error loading model: {e}")
50
+ return
51
+
52
+ # Inference
53
+ st.subheader("Inference Results")
54
+ try:
55
+ results = model(np.array(image))
56
+ except Exception as e:
57
+ st.error(f"Inference error: {e}")
58
+ return
59
+
60
+ r = results[0]
61
+ boxes_data = []
62
+ if r.boxes is not None and len(r.boxes) > 0:
63
+ for i in range(len(r.boxes)):
64
+ coords = r.boxes.xyxy[i].cpu().numpy() if hasattr(r.boxes.xyxy[i], "cpu") else r.boxes.xyxy[i]
65
+ conf = r.boxes.conf[i].cpu().numpy() if hasattr(r.boxes.conf[i], "cpu") else r.boxes.conf[i]
66
+ cls_idx = int(r.boxes.cls[i].cpu().numpy()) if hasattr(r.boxes.cls[i], "cpu") else int(r.boxes.cls[i])
67
+ class_name = r.names.get(cls_idx, "Unknown")
68
+ boxes_data.append({
69
+ "Box": i + 1,
70
+ "Coordinates": f"[{coords[0]:.1f}, {coords[1]:.1f}, {coords[2]:.1f}, {coords[3]:.1f}]",
71
+ "Confidence": f"{conf:.2f}",
72
+ "Class": class_name
73
+ })
74
+ df_boxes = pd.DataFrame(boxes_data)
75
+ st.subheader("Detected Objects")
76
+ st.dataframe(df_boxes, use_container_width=True)
77
+ else:
78
+ st.write("No objects detected.")
79
+
80
+ # Annotated image
81
+ try:
82
+ annotated_img_bgr = r.plot(conf=True, boxes=True, labels=True)
83
+ annotated_img_rgb = Image.fromarray(annotated_img_bgr[..., ::-1])
84
+ st.subheader("Annotated Image")
85
+ st.image(annotated_img_rgb, caption="Inference Output", use_container_width=True)
86
+ except Exception as e:
87
+ st.error(f"Error generating annotated image: {e}")
88
+
89
+ def yolo_model_comparison_tool():
90
+ """
91
+ Multi-model, multi-image comparison subpage,
92
+ with Weighted Scoring that uses a reciprocal-based speed metric
93
+ and a real-time progress bar + ETA display.
94
+ """
95
+ st.header("YOLO Models Comparison Tool (Multi-Image, Weighted Score + Progress Bar)")
96
+ st.write(
97
+ "Upload **one or more images** and **multiple YOLO model (.pt) files**. "
98
+ "Then click **Submit** to run inference across all images with each model. "
99
+ "We aggregate metrics (Avg Inference Time, Total Detections, Avg Confidence) "
100
+ "and compute a Weighted Score that balances these factors.\n\n"
101
+ "A progress bar and ETA are shown in real time after you click Submit."
102
+ )
103
+
104
+ images = st.file_uploader("Upload Images", type=["jpg", "jpeg", "png"], key="comparison_images", accept_multiple_files=True)
105
+ model_files = st.file_uploader("Upload YOLO models (.pt)", type=["pt"], key="comparison_models", accept_multiple_files=True)
106
+
107
+ # Example weights. You can expose them as sliders if you want user customization.
108
+ alpha_detection = 0.4
109
+ beta_confidence = 0.3
110
+ gamma_speed = 0.3 # speed = reciprocal of time
111
+
112
+ if st.button("Submit (Multi-Model Comparison)"):
113
+ if not images or not model_files:
114
+ st.error("Please upload at least one image and at least one model.")
115
+ return
116
+
117
+ # Initialize progress tracking
118
+ total_inferences = len(images) * len(model_files)
119
+ if total_inferences == 0:
120
+ st.error("No valid images or models to process.")
121
+ return
122
+
123
+ progress_bar = st.progress(0)
124
+ eta_placeholder = st.empty()
125
+ start_time = time.time()
126
+ steps_done = 0
127
+
128
+ # We'll store aggregated metrics here
129
+ model_agg_data = {}
130
+ # We'll store results for each (model, image) so we can display side-by-side
131
+ model_image_results = {m.name: {} for m in model_files}
132
+
133
+ for model_file in model_files:
134
+ model_path = save_uploaded_file(model_file)
135
+ try:
136
+ model = YOLO(model_path)
137
+ except Exception as e:
138
+ st.error(f"Error loading model {model_file.name}: {e}")
139
+ continue
140
+
141
+ total_inference_time = 0.0
142
+ total_detections = 0
143
+ sum_confidences = 0.0
144
+ total_conf_count = 0
145
+
146
+ for img_file in images:
147
+ # Update progress/ETA before processing next image
148
+ steps_done += 1
149
+ fraction_done = steps_done / total_inferences
150
+ progress_bar.progress(fraction_done)
151
+
152
+ elapsed_time = time.time() - start_time
153
+ time_per_step = elapsed_time / steps_done
154
+ remaining_steps = total_inferences - steps_done
155
+ eta_seconds = remaining_steps * time_per_step
156
+ eta_placeholder.info(f"Progress: {fraction_done:.1%}. ETA: ~{eta_seconds:.1f} s")
157
+
158
+ # Load image
159
+ try:
160
+ pil_img = Image.open(img_file).convert("RGB")
161
+ np_img = np.array(pil_img)
162
+ except Exception as e:
163
+ st.error(f"Error reading image {img_file.name}: {e}")
164
+ continue
165
+
166
+ # Run inference
167
+ try:
168
+ result = model(np_img)
169
+ except Exception as e:
170
+ st.error(f"Inference error for model {model_file.name} on {img_file.name}: {e}")
171
+ continue
172
+
173
+ r = result[0]
174
+ model_image_results[model_file.name][img_file.name] = r
175
+
176
+ # Accumulate inference time
177
+ if isinstance(r.speed, dict) and "inference" in r.speed:
178
+ total_inference_time += r.speed["inference"]
179
+
180
+ # Count detections & confidence
181
+ if r.boxes is not None:
182
+ det_count = len(r.boxes)
183
+ total_detections += det_count
184
+ if det_count > 0:
185
+ confs = r.boxes.conf.cpu().numpy() if hasattr(r.boxes.conf, "cpu") else r.boxes.conf
186
+ sum_confidences += confs.sum()
187
+ total_conf_count += det_count
188
+
189
+ # After all images for this model
190
+ image_count = len(images)
191
+ avg_inference_time = total_inference_time / image_count if image_count > 0 else float("inf")
192
+ avg_confidence = sum_confidences / total_conf_count if total_conf_count > 0 else 0.0
193
+
194
+ model_agg_data[model_file.name] = {
195
+ "Model File": model_file.name,
196
+ "Avg Inference Time (ms)": avg_inference_time,
197
+ "Total Detections": total_detections,
198
+ "Average Confidence": avg_confidence
199
+ }
200
+
201
+ if not model_agg_data:
202
+ st.write("No valid models processed.")
203
+ return
204
+
205
+ # Now that all inferences are done, remove the ETA info
206
+ eta_placeholder.empty()
207
+
208
+ # Display aggregated metrics
209
+ df = pd.DataFrame(model_agg_data.values())
210
+ st.subheader("Aggregated Metrics (Across All Images)")
211
+ st.dataframe(df, use_container_width=True)
212
+
213
+ # Weighted Scoring with reciprocal-based speed
214
+ detection_max = df["Total Detections"].max()
215
+ confidence_max = df["Average Confidence"].max()
216
+ if detection_max == 0: detection_max = 1
217
+ if confidence_max == 0: confidence_max = 1
218
+
219
+ df["Detection Norm"] = df["Total Detections"] / detection_max
220
+ df["Confidence Norm"] = df["Average Confidence"] / confidence_max
221
+
222
+ # Convert time to speed = 1 / time, then normalize
223
+ eps = 1e-9
224
+ df["Speed Val"] = 1.0 / (df["Avg Inference Time (ms)"] + eps)
225
+ max_speed_val = df["Speed Val"].max() if not df["Speed Val"].isnull().all() else 1
226
+ if max_speed_val == 0:
227
+ max_speed_val = 1
228
+
229
+ df["Speed Norm"] = df["Speed Val"] / max_speed_val
230
+
231
+ df["Weighted Score"] = (
232
+ alpha_detection * df["Detection Norm"] +
233
+ beta_confidence * df["Confidence Norm"] +
234
+ gamma_speed * df["Speed Norm"]
235
+ )
236
+
237
+ st.subheader("Weighted Score Analysis")
238
+ st.write(f"Weights: Detection={alpha_detection}, Confidence={beta_confidence}, Speed={gamma_speed}")
239
+ st.dataframe(df[[
240
+ "Model File",
241
+ "Avg Inference Time (ms)",
242
+ "Total Detections",
243
+ "Average Confidence",
244
+ "Detection Norm",
245
+ "Confidence Norm",
246
+ "Speed Val",
247
+ "Speed Norm",
248
+ "Weighted Score"
249
+ ]], use_container_width=True)
250
+
251
+ # Identify best overall model (highest Weighted Score)
252
+ best_idx = df["Weighted Score"].idxmax()
253
+ best_model = df.loc[best_idx, "Model File"]
254
+ best_score = df.loc[best_idx, "Weighted Score"]
255
+
256
+ st.markdown(f"""
257
+ **Best Overall Model** based on Weighted Score:
258
+ **{best_model}** (Score: {best_score:.3f}).
259
+
260
+ ### Interpretation:
261
+ - **Detection Norm** → fraction of the best detection count.
262
+ - **Confidence Norm** → fraction of the highest average confidence.
263
+ - **Speed Norm** → fraction of the highest (1/time). The fastest model is near 1; others are a fraction of that speed.
264
+
265
+ If you find one factor more important, adjust the weights:
266
+ - Increase **Detection** weight if you care about finding as many objects as possible.
267
+ - Increase **Confidence** weight if you only trust high‐confidence detections.
268
+ - Increase **Speed** weight if you need real‐time inference.
269
+ """)
270
+
271
+ # Display annotated images in a grid (row = image, column = model)
272
+ st.subheader("Annotated Images Grid (Row = Image, Column = Model)")
273
+ model_names_sorted = sorted(model_agg_data.keys())
274
+
275
+ for img_file in images:
276
+ st.markdown(f"### Image: {img_file.name}")
277
+ columns = st.columns(len(model_names_sorted))
278
+ for col, model_name in zip(columns, model_names_sorted):
279
+ r = model_image_results.get(model_name, {}).get(img_file.name, None)
280
+ if r is None:
281
+ col.write(f"No results for {model_name}")
282
+ continue
283
+
284
+ try:
285
+ # Return a PIL image in correct RGB color space
286
+ annotated_img_pil = r.plot(conf=True, boxes=True, labels=True, pil=True)
287
+ col.image(
288
+ annotated_img_pil,
289
+ caption=f"{model_name}",
290
+ use_container_width=True
291
+ )
292
+ except Exception as e:
293
+ col.error(f"Error annotating image for {model_name}: {e}")
294
+
295
+ def main():
296
+ st.sidebar.title("Navigation")
297
+ page = st.sidebar.radio("Go to", ("YOLO Model Inference Tool", "YOLO Models Comparison Tool"))
298
+
299
+ if page == "YOLO Model Inference Tool":
300
+ yolo_inference_tool()
301
+ else:
302
+ yolo_model_comparison_tool()
303
+
304
+ if __name__ == "__main__":
305
+ main()