Skip to content

Commit 0450c19

Browse files
authored
Merge pull request #19 from TingRongYou/integration/ui-results-merge
Integrated Results and some UI changes
2 parents 1212170 + ed9ee2c commit 0450c19

30 files changed

Lines changed: 1049 additions & 240 deletions

result.py renamed to analytics/generate_academic_latency_report.py

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,62 @@
22
import matplotlib.pyplot as plt
33
import os
44

5-
# --- 1. Setup ---
6-
csv_path = os.path.join("logs", "test_results.csv")
5+
# --- 1. Dynamic Path Setup ---
6+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
7+
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
8+
9+
csv_path = os.path.join(ROOT_DIR, "logs", "latency_results.csv")
10+
output_dir = os.path.join(ROOT_DIR, "stats", "Objective 1 Performance")
11+
12+
if not os.path.exists(output_dir):
13+
os.makedirs(output_dir)
714

815
def generate_consolidated_objective_1_report():
9-
"""
10-
Generates a single comprehensive graph validating Objective 1:
11-
- Latency < 100ms
12-
- Frame Rate >= 30 FPS
13-
- Markerless/Standard Hardware efficiency
14-
"""
1516
if not os.path.exists(csv_path):
1617
print(f"Error: {csv_path} not found. Run the game to generate logs first!")
1718
return
1819

1920
df = pd.read_csv(csv_path)
20-
21-
# Derive FPS from recorded processing time to prove 'Measurable' criteria
2221
df['RealTime_FPS'] = 1 / df['Proc_Time']
2322

24-
# Create figure with twin axes for a single-report focus
2523
fig, ax1 = plt.subplots(figsize=(12, 8))
2624

27-
# --- PRIMARY AXIS: Latency (Seconds) ---
28-
color_lat = '#1f77b4' # Tech Blue
25+
color_lat = '#1f77b4'
2926
ax1.set_xlabel('Punch Sample Sequence (Time)', fontsize=12)
3027
ax1.set_ylabel('Processing Latency (Seconds)', color=color_lat, fontsize=12, fontweight='bold')
3128
ax1.plot(df.index, df['Proc_Time'], color=color_lat, linewidth=2.5, label='Measured Latency')
3229

33-
# CRITICAL: Target Threshold Line (100ms) per Objective 1
3430
ax1.axhline(y=0.1, color='#d62728', linestyle='--', linewidth=2, label='Max Target (100ms)')
3531
ax1.tick_params(axis='y', labelcolor=color_lat)
36-
ax1.set_ylim(0, 0.15) # Focused view around the 100ms threshold
32+
ax1.set_ylim(0, 0.15)
3733
ax1.grid(True, linestyle=':', alpha=0.5)
3834

39-
# --- SECONDARY AXIS: Frame Rate (FPS) ---
4035
ax2 = ax1.twinx()
41-
color_fps = '#2ca02c' # Success Green
36+
color_fps = '#2ca02c'
4237
ax2.set_ylabel('Frame Rate (FPS)', color=color_fps, fontsize=12, fontweight='bold')
4338
ax2.plot(df.index, df['RealTime_FPS'], color=color_fps, linestyle='-', alpha=0.4, label='Real-time FPS')
4439

45-
# Target 30 FPS Line per SMART Criteria
4640
ax2.axhline(y=30, color='#1b5e20', linestyle=':', linewidth=2, label='Target 30 FPS')
4741
ax2.tick_params(axis='y', labelcolor=color_fps)
4842
ax2.set_ylim(0, 60)
4943

50-
# --- Annotations & Styling ---
5144
plt.title('Objective 1 Validation: Vision Pipeline Efficiency\n(Python/OpenCV Markerless Tracking)', pad=20, fontsize=14)
5245

53-
# Combined Legend
5446
lines1, labels1 = ax1.get_legend_handles_labels()
5547
lines2, labels2 = ax2.get_legend_handles_labels()
5648
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left', frameon=True, shadow=True)
5749

58-
# Objective Summary Text Box
5950
avg_lat = df['Proc_Time'].mean() * 1000
6051
avg_fps = df['RealTime_FPS'].mean()
6152
summary_text = f"Avg Latency: {avg_lat:.1f}ms\nAvg FPS: {avg_fps:.1f}"
6253
plt.gca().text(0.98, 0.02, summary_text, transform=ax1.transAxes,
6354
bbox=dict(facecolor='white', alpha=0.8), ha='right', fontsize=10)
6455

6556
fig.tight_layout()
66-
plt.savefig('objective_1_performance_validation.png')
67-
print("Success: objective_1_performance_validation.png generated.")
57+
output_file = os.path.join(output_dir, 'academic_latency_graph.png')
58+
plt.savefig(output_file)
59+
print(f"Success: Academic graph saved securely to {output_file}")
6860

6961
if __name__ == "__main__":
70-
print("--- Generating Objective 1 Audit ---")
62+
print("--- Generating Academic Objective 1 Audit ---")
7163
generate_consolidated_objective_1_report()
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import cv2 as cv
2+
import numpy as np
3+
import os
4+
import pandas as pd
5+
import matplotlib.pyplot as plt
6+
7+
# =========================
8+
# OUTPUT FOLDER
9+
# =========================
10+
base_folder = "stats"
11+
output_folder = os.path.join(base_folder, "Pseudocolor Mapping Analysis")
12+
13+
if not os.path.exists(output_folder):
14+
os.makedirs(output_folder)
15+
16+
# =========================
17+
# MAIN FUNCTION
18+
# =========================
19+
def analyze_pseudocolor_mapping(video_source=0):
20+
21+
cap = cv.VideoCapture(video_source)
22+
23+
if not cap.isOpened():
24+
print("Error: Cannot open video source.")
25+
return
26+
27+
prev_gray = None
28+
results = []
29+
30+
frame_count = 0
31+
max_frames = 300 # limit for testing
32+
33+
while frame_count < max_frames:
34+
ret, frame = cap.read()
35+
if not ret:
36+
break
37+
38+
# =========================
39+
# PREPROCESSING
40+
# =========================
41+
gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
42+
gray = cv.GaussianBlur(gray, (5, 5), 0)
43+
44+
# =========================
45+
# MOTION DETECTION
46+
# =========================
47+
if prev_gray is None:
48+
prev_gray = gray
49+
continue
50+
51+
diff = cv.absdiff(prev_gray, gray)
52+
_, mask = cv.threshold(diff, 25, 255, cv.THRESH_BINARY)
53+
54+
# Clean noise
55+
kernel = np.ones((3,3), np.uint8)
56+
mask = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel)
57+
58+
prev_gray = gray
59+
60+
# =========================
61+
# PSEUDOCOLOR (HEATMAP)
62+
# =========================
63+
heatmap = cv.applyColorMap(mask, cv.COLORMAP_JET)
64+
65+
# =========================
66+
# ANALYSIS
67+
# =========================
68+
motion = cv.countNonZero(mask)
69+
70+
hsv = cv.cvtColor(heatmap, cv.COLOR_BGR2HSV)
71+
avg_hue = hsv[:, :, 0].mean()
72+
73+
results.append((motion, avg_hue))
74+
75+
frame_count += 1
76+
77+
# Optional display
78+
cv.imshow("Mask", mask)
79+
cv.imshow("Heatmap", heatmap)
80+
81+
if cv.waitKey(1) & 0xFF == 27:
82+
break
83+
84+
cap.release()
85+
cv.destroyAllWindows()
86+
87+
# =========================
88+
# SAVE RESULTS
89+
# =========================
90+
df = pd.DataFrame(results, columns=["Motion", "Hue"])
91+
92+
csv_path = os.path.join(output_folder, "mapping_results.csv")
93+
df.to_csv(csv_path, index=False)
94+
95+
# =========================
96+
# SIMPLE SUMMARY
97+
# =========================
98+
corr = df.corr().iloc[0,1]
99+
100+
summary_path = os.path.join(output_folder, "mapping_summary.txt")
101+
with open(summary_path, "w") as f:
102+
f.write("PSEUDOCOLOR MAPPING ANALYSIS\n\n")
103+
f.write(f"Total Frames: {len(df)}\n")
104+
f.write(f"Correlation (Motion vs Hue): {corr:.4f}\n\n")
105+
106+
if corr < -0.3:
107+
f.write("Strong inverse relationship (Correct mapping)\n")
108+
elif corr < -0.1:
109+
f.write("Moderate relationship\n")
110+
else:
111+
f.write("Weak relationship (Needs improvement)\n")
112+
113+
print("✅ Analysis completed")
114+
print(f"CSV saved at: {csv_path}")
115+
print(f"Summary saved at: {summary_path}")
116+
117+
# =========================
118+
# GENERATE SCATTER PLOT
119+
# =========================
120+
# Optional: remove zero motion for clearer visualization
121+
df_plot = df[df["Motion"] > 0]
122+
123+
plt.figure(figsize=(10, 6))
124+
125+
# Scatter plot
126+
plt.scatter(df_plot["Motion"], df_plot["Hue"], alpha=0.6)
127+
128+
# Trend line
129+
if len(df_plot) > 1:
130+
z = np.polyfit(df_plot["Motion"], df_plot["Hue"], 1)
131+
p = np.poly1d(z)
132+
plt.plot(df_plot["Motion"], p(df_plot["Motion"]))
133+
134+
# Labels
135+
plt.xlabel("Motion Intensity")
136+
plt.ylabel("Hue Value")
137+
plt.title("Pseudocolor Mapping: Motion vs Hue")
138+
plt.grid()
139+
140+
# Save chart
141+
chart_path = os.path.join(output_folder, "mapping_scatter.png")
142+
plt.savefig(chart_path)
143+
144+
plt.close()
145+
146+
print(f"✅ Chart saved at: {chart_path}")
147+
148+
149+
# =========================
150+
# RUN
151+
# =========================
152+
if __name__ == "__main__":
153+
analyze_pseudocolor_mapping()
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import pandas as pd
2+
import matplotlib.pyplot as plt
3+
import os
4+
import numpy as np
5+
6+
# =========================
7+
# PATH SETUP (FIXED)
8+
# =========================
9+
csv_path = os.path.join("logs", "test_results.csv")
10+
11+
base_folder = "stats"
12+
stats_folder = os.path.join(base_folder, "Real-Time Pipeline Performance")
13+
14+
if not os.path.exists(stats_folder):
15+
os.makedirs(stats_folder)
16+
17+
# =========================
18+
# MAIN FUNCTION
19+
# =========================
20+
def generate_consolidated_objective_1_report():
21+
22+
if not os.path.exists(csv_path):
23+
print(f"Error: {csv_path} not found.")
24+
return
25+
26+
df = pd.read_csv(csv_path)
27+
28+
# === Convert ===
29+
df['Latency_ms'] = df['Proc_Time'] * 1000
30+
df['FPS'] = 1 / df['Proc_Time']
31+
32+
# === Statistics ===
33+
mean = df['Latency_ms'].mean()
34+
std = df['Latency_ms'].std()
35+
p99 = np.percentile(df['Latency_ms'], 99)
36+
37+
print("\n=== REAL-TIME PIPELINE PERFORMANCE ===")
38+
print(f"Mean Latency: {mean:.2f} ms")
39+
print(f"Std Dev: {std:.2f}")
40+
print(f"99th Percentile: {p99:.2f} ms")
41+
42+
# =========================
43+
# SAVE STATS
44+
# =========================
45+
stats_file = os.path.join(stats_folder, "latency_statistics.txt")
46+
47+
with open(stats_file, "w", encoding="utf-8") as f:
48+
f.write("REAL-TIME PIPELINE PERFORMANCE\n\n")
49+
f.write(f"Mean Latency: {mean:.2f} ms\n")
50+
f.write(f"Standard Deviation: {std:.2f}\n")
51+
f.write(f"99th Percentile: {p99:.2f} ms\n")
52+
53+
# =========================
54+
# PLOT GRAPH
55+
# =========================
56+
fig, ax1 = plt.subplots(figsize=(12, 8))
57+
58+
# Latency
59+
ax1.plot(df.index, df['Latency_ms'], linewidth=2, label='Latency (ms)')
60+
ax1.set_xlabel("Frame Number")
61+
ax1.set_ylabel("Latency (ms)")
62+
63+
# Thresholds
64+
ax1.axhline(33.3, linestyle='--', label='30 FPS Threshold (33.3 ms)')
65+
ax1.axhline(100, linestyle='--', label='Maximum Limit (100 ms)')
66+
67+
ax1.set_ylim(0, max(df['Latency_ms']) * 1.2)
68+
69+
# FPS axis
70+
ax2 = ax1.twinx()
71+
ax2.plot(df.index, df['FPS'], alpha=0.3, label='FPS')
72+
ax2.axhline(30, linestyle=':', label='30 FPS Target')
73+
ax2.set_ylabel("FPS")
74+
75+
# Legend
76+
lines1, labels1 = ax1.get_legend_handles_labels()
77+
lines2, labels2 = ax2.get_legend_handles_labels()
78+
ax1.legend(lines1 + lines2, labels1 + labels2)
79+
80+
# Summary box
81+
summary = (
82+
f"Mean: {mean:.2f} ms\n"
83+
f"Std: {std:.2f}\n"
84+
f"99%: {p99:.2f} ms"
85+
)
86+
87+
plt.gca().text(0.98, 0.02, summary,
88+
transform=ax1.transAxes,
89+
bbox=dict(facecolor='white', alpha=0.8),
90+
ha='right')
91+
92+
plt.title("Real-Time Pipeline Performance")
93+
plt.tight_layout()
94+
95+
# =========================
96+
# SAVE GRAPH
97+
# =========================
98+
output_path = os.path.join(stats_folder, "latency_performance_graph.png")
99+
plt.savefig(output_path)
100+
101+
plt.show()
102+
103+
print(f"✅ Graph saved at: {output_path}")
104+
print(f"✅ Stats saved at: {stats_file}")
105+
106+
107+
# =========================
108+
# RUN
109+
# =========================
110+
if __name__ == "__main__":
111+
generate_consolidated_objective_1_report()

0 commit comments

Comments
 (0)