-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck.jsx
374 lines (331 loc) · 12.9 KB
/
check.jsx
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
"use client";
import React, { useState, useRef, useEffect } from "react";
import {
Video, Settings, LogOut, Crosshair, Users,
Calendar, Bell, ChevronLeft, ChevronRight, BarChart3,
} from "lucide-react";
const API_URL = "https://cv.drx.rebec.in/api/analyze-pose/";
const TrainingVideosPage = () => {
const videoRef = useRef(null);
const canvasRef = useRef(null);
const [isRecording, setIsRecording] = useState(false);
const [apiStatus, setApiStatus] = useState({ status: 'idle', error: null });
const [feedback, setFeedback] = useState({
knee: { status: "Not detected", percentage: 0 },
elbow: { status: "Not detected", percentage: 0 },
headPosition: { status: "Not detected", percentage: 0 },
});
const [mediaStream, setMediaStream] = useState(null);
const analysisIntervalRef = useRef();
useEffect(() => {
checkApiConnection();
initCamera();
return cleanup;
}, []);
const checkApiConnection = async () => {
try {
const response = await fetch(API_URL, {
method: 'OPTIONS',
});
console.log('API Connection test:', response.ok ? 'Success' : 'Failed');
setApiStatus({ status: response.ok ? 'connected' : 'error', error: null });
} catch (error) {
console.error('API Connection test failed:', error);
setApiStatus({
status: 'error',
error: `Cannot connect to API at ${API_URL}. Make sure the Flask server is running.`
});
}
};
const cleanup = () => {
if (mediaStream) {
mediaStream.getTracks().forEach((track) => track.stop());
}
if (analysisIntervalRef.current) {
clearInterval(analysisIntervalRef.current);
}
setIsRecording(false);
};
const initCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 640 },
height: { ideal: 480 },
frameRate: { ideal: 30 }
}
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
setMediaStream(stream);
// Wait for video to be ready
videoRef.current.onloadedmetadata = () => {
videoRef.current.play();
initCanvas();
};
}
} catch (err) {
console.error("Camera initialization error:", err);
setApiStatus({
status: 'error',
error: `Camera access error: ${err.message}`
});
}
};
const initCanvas = () => {
if (canvasRef.current && videoRef.current) {
const { videoWidth, videoHeight } = videoRef.current;
canvasRef.current.width = videoWidth || 640;
canvasRef.current.height = videoHeight || 480;
}
};
const captureFrame = () => {
if (!videoRef.current) {
console.log('Video ref not ready');
return null;
}
const video = videoRef.current;
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
const ctx = canvas.getContext('2d');
if (video.readyState === video.HAVE_ENOUGH_DATA) {
try {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
console.log('Frame captured successfully');
return dataUrl;
} catch (error) {
console.error('Frame capture error:', error);
return null;
}
} else {
console.log('Video not ready');
return null;
}
};
const analyzePose = async (frameData) => {
if (!frameData) {
console.error('No frame data to send to API');
return;
}
try {
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ frame: frameData }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('API Response:', data);
// Update the feedback state with the API response data
updateFeedback({
knee_angle: data.knee_angle,
elbow_angle: data.elbow_angle,
head_angle: data.head_position // Assuming this is the correct field name from your API
});
// If you have annotated frame data from the API, update the canvas
if (data.annotated_frame) {
updateCanvas(data.annotated_frame);
}
} catch (error) {
console.error('API Error:', error);
setApiStatus({ status: 'error', error: error.message });
}
};
const getFeedbackStatus = (angle, min, max) => {
if (angle === null || angle === undefined) return "Not detected";
// Add a small tolerance range (e.g., ±10 degrees) for "Correct" status
const tolerance = 10;
const targetRange = (max - min) / 2 + min; // Middle of the range
if (Math.abs(angle - targetRange) <= tolerance) {
return "Correct";
} else if (angle < targetRange) {
return "Adjust Higher";
} else {
return "Adjust Lower";
}
};
const calculatePercentage = (value, min, max) => {
if (value === null || value === undefined) return 0;
// Normalize the value to a percentage
const range = max - min;
const adjustedValue = Math.min(Math.max(value, min), max);
return Math.round(((adjustedValue - min) / range) * 100);
};
const updateFeedback = (feedbackData) => {
// Define target ranges for each measurement
const ranges = {
knee: { min: 150, max: 180 }, // Typical range for knee extension
elbow: { min: 0, max: 90 }, // Typical range for elbow flexion
head: { min: -30, max: 30 } // Typical range for head position (degrees from vertical)
};
setFeedback({
knee: {
status: getFeedbackStatus(feedbackData.knee_angle, ranges.knee.min, ranges.knee.max),
percentage: calculatePercentage(feedbackData.knee_angle, ranges.knee.min, ranges.knee.max)
},
elbow: {
status: getFeedbackStatus(feedbackData.elbow_angle, ranges.elbow.min, ranges.elbow.max),
percentage: calculatePercentage(feedbackData.elbow_angle, ranges.elbow.min, ranges.elbow.max)
},
headPosition: {
status: getFeedbackStatus(feedbackData.head_angle, ranges.head.min, ranges.head.max),
percentage: calculatePercentage(feedbackData.head_angle, ranges.head.min, ranges.head.max)
}
});
};
const startRecording = () => {
setIsRecording(true);
setApiStatus({ status: 'recording', error: null });
// Run analysis every 500ms
analysisIntervalRef.current = setInterval(() => {
const frameData = captureFrame();
if (frameData) {
analyzePose(frameData); // Pass the captured frame to the API
}
}, 500);
};
const stopRecording = () => {
setIsRecording(false);
if (analysisIntervalRef.current) {
clearInterval(analysisIntervalRef.current);
}
setApiStatus({ status: 'idle', error: null });
};
return (
<div className="min-h-screen bg-black flex">
{/* Sidebar */}
<div className="w-64 bg-neutral-900 border-r border-neutral-800 p-4">
<div className="mb-8">
<a href="/">
<img className="w-32 invert mx-auto" src="/drx.png" alt="DRX Logo" />
</a>
</div>
<nav className="space-y-2">
<a href="/" className="flex items-center space-x-3 px-4 py-3 text-neutral-400 hover:bg-neutral-800 rounded-lg transition">
<BarChart3 size={20} />
<span>Dashboard</span>
</a>
<a href="/players" className="flex items-center space-x-3 px-4 py-3 text-neutral-400 hover:bg-neutral-800 rounded-lg transition">
<Users size={20} />
<span>Players</span>
</a>
<a href="/analysis" className="flex items-center space-x-3 px-4 py-3 text-neutral-400 hover:bg-neutral-800 rounded-lg transition">
<Crosshair size={20} />
<span>Trajectory Analysis</span>
</a>
<a href="/training-videos" className="flex items-center space-x-3 px-4 py-3 bg-gradient-to-r from-cyan-600 to-blue-600 rounded-lg text-white">
<Video size={20} />
<span>Training Videos</span>
</a>
<a href="/schedule" className="flex items-center space-x-3 px-4 py-3 text-neutral-400 hover:bg-neutral-800 rounded-lg transition">
<Calendar size={20} />
<span>Schedule</span>
</a>
<a href="/settings" className="flex items-center space-x-3 px-4 py-3 text-neutral-400 hover:bg-neutral-800 rounded-lg transition">
<Settings size={20} />
<span>Settings</span>
</a>
<a href="/login" className="flex items-center space-x-3 px-4 py-3 text-neutral-400 hover:bg-neutral-800 rounded-lg transition">
<LogOut size={20} />
<span>Logout</span>
</a>
</nav>
</div>
{/* Main Content */}
<div className="flex-1 p-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Video Feed */}
<div className="lg:col-span-2">
<div className="bg-neutral-900 border border-neutral-800 rounded-xl p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold text-white">Live Feed</h2>
<div className="text-sm">
{apiStatus.status === 'connected' && (
<span className="text-green-500">API Connected</span>
)}
{apiStatus.status === 'error' && (
<span className="text-red-500">API Error</span>
)}
</div>
</div>
<div className="relative aspect-video bg-neutral-800 rounded-lg overflow-hidden">
<video
ref={videoRef}
autoPlay
playsInline
muted
className="w-full h-full object-cover"
/>
<canvas
ref={canvasRef}
className="absolute top-0 left-0 w-full h-full"
/>
{apiStatus.error && (
<div className="absolute top-0 left-0 right-0 bg-red-500/80 text-white p-2 text-sm">
{apiStatus.error}
</div>
)}
</div>
<div className="mt-4 flex justify-center space-x-4">
<button
onClick={isRecording ? stopRecording : startRecording}
disabled={apiStatus.status === 'error'}
className={`px-6 py-2 rounded-lg text-white text-sm ${
isRecording
? "bg-red-500 hover:bg-red-600"
: apiStatus.status === 'error'
? "bg-gray-500 cursor-not-allowed"
: "bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500"
} transition`}
>
{isRecording ? "Stop Recording" : "Start Recording"}
</button>
</div>
</div>
</div>
{/* Feedback Panel */}
<div className="lg:col-span-1">
<div className="bg-neutral-900 border border-neutral-800 rounded-xl p-6">
<h2 className="text-xl font-bold text-white mb-4">Real-time Feedback</h2>
<div className="space-y-4">
{Object.entries(feedback).map(([key, value]) => (
<div key={key} className="p-4 bg-neutral-800 rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-neutral-400 capitalize">
{key.replace(/([A-Z])/g, ' $1').trim()}
</span>
<span
className={`${
value.status === "Correct" ? "text-green-500" :
value.status === "Not detected" ? "text-red-500" : "text-yellow-500"
}`}
>
{value.status}
</span>
</div>
<div className="w-full bg-neutral-700 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${
value.status === "Correct" ? "bg-green-500" :
value.status === "Not detected" ? "bg-red-500" : "bg-yellow-500"
}`}
style={{ width: `${value.percentage}%` }}
/>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default TrainingVideosPage;