Skip to content

Commit f83bdac

Browse files
authored
Update index.html
1 parent 32fc3c2 commit f83bdac

File tree

1 file changed

+60
-121
lines changed

1 file changed

+60
-121
lines changed

index.html

Lines changed: 60 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<title>Wall Ball Rep Tracker</title>
7+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
8+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-converter"></script>
9+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgl"></script>
10+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/pose-detection"></script>
711
<style>
812
body {
913
background: #121212;
@@ -68,12 +72,11 @@
6872
<body>
6973
<h1>Wall Ball Rep Tracker (Chrome)</h1>
7074
<div id="controls-container" style="margin-bottom: 1rem;">
71-
<label for="stick-color" style="margin-right: 0.5rem;">Stick Color:</label>
72-
<select id="stick-color" style="padding: 0.5rem; background: #333; color: white; border: 1px solid #555; border-radius: 5px;">
73-
<option value="black">Black</option>
74-
<option value="white">White</option>
75+
<label for="hand-select" style="margin-right: 0.5rem;">Tracked Hand:</label>
76+
<select id="hand-select" style="padding: 0.5rem; background: #333; color: white; border: 1px solid #555; border-radius: 5px;">
77+
<option value="right">Right</option>
78+
<option value="left">Left</option>
7579
</select>
76-
<button id="view-toggle" style="margin-left: 1rem; padding: 0.5rem; background: #333; color: white; border: 1px solid #555; border-radius: 5px; cursor: pointer;">Toggle Lines</button>
7780
</div>
7881
<div id="reps-container">
7982
<div id="reps">Reps: 0</div>
@@ -88,11 +91,8 @@ <h1>Wall Ball Rep Tracker (Chrome)</h1>
8891
<script>
8992
async function setupCamera() {
9093
const video = document.getElementById('video');
91-
92-
// Check if user has previously granted camera access
9394
const cameraAccessGranted = localStorage.getItem('wallBallCameraAccess') === 'granted';
9495

95-
// Show camera permission request message
9696
document.getElementById('reps').textContent = 'Please allow camera access...';
9797
document.getElementById('camera-status').style.display = 'block';
9898

@@ -111,34 +111,21 @@ <h1>Wall Ball Rep Tracker (Chrome)</h1>
111111
}
112112
});
113113
video.srcObject = stream;
114-
115-
// Save camera access permission
116114
localStorage.setItem('wallBallCameraAccess', 'granted');
117-
118-
// Hide camera permission message once access is granted
119115
document.getElementById('camera-status').style.display = 'none';
120116

121-
// Wait for video to be fully loaded
122117
await new Promise((resolve) => {
123118
video.onloadedmetadata = () => {
124119
video.width = video.videoWidth;
125120
video.height = video.videoHeight;
126-
console.log('Video metadata loaded:', {
127-
width: video.width,
128-
height: video.height,
129-
videoWidth: video.videoWidth,
130-
videoHeight: video.videoHeight
131-
});
132121
resolve();
133122
};
134123
});
135124

136125
await video.play();
137126
return video;
138127
} catch (err) {
139-
// Save that camera access was denied
140128
localStorage.setItem('wallBallCameraAccess', 'denied');
141-
142129
document.getElementById('camera-status').innerHTML =
143130
'Camera access denied or unavailable. <button onclick="requestCameraAgain()" class="retry-button">Try Again</button>';
144131
console.error('Camera error:', err.message);
@@ -153,127 +140,79 @@ <h1>Wall Ball Rep Tracker (Chrome)</h1>
153140
});
154141
}
155142

143+
function drawPoint(ctx, y, x, r, color) {
144+
ctx.beginPath();
145+
ctx.arc(x, y, r, 0, 2 * Math.PI);
146+
ctx.fillStyle = color;
147+
ctx.fill();
148+
}
149+
150+
function drawKeypoints(keypoints, ctx) {
151+
for (let i = 0; i < keypoints.length; i++) {
152+
const keypoint = keypoints[i];
153+
if (keypoint.score > 0.5) {
154+
drawPoint(ctx, keypoint.y, keypoint.x, 5, 'red');
155+
}
156+
}
157+
}
158+
156159
async function main() {
157160
const video = await setupCamera();
158161
const canvas = document.getElementById('canvas');
159162
const ctx = canvas.getContext('2d');
160-
161-
// Set canvas dimensions to match video
162163
canvas.width = video.width;
163164
canvas.height = video.height;
164-
165-
// Initialize variables
165+
166+
const detector = await poseDetection.createDetector(poseDetection.SupportedModels.BlazePose, { runtime: 'tfjs' });
167+
166168
let reps = 0;
167-
let stickStage = null;
168-
let lastRepTime = 0;
169-
const REP_COOLDOWN = 1000; // Minimum time (ms) between reps
170-
171-
// Start the detection loop
172-
detect(video, ctx, canvas, reps, stickStage, lastRepTime, REP_COOLDOWN);
173-
}
169+
let stage = null; // 'up' or 'down'
174170

175-
async function detect(video, ctx, canvas, reps, stickStage, lastRepTime, REP_COOLDOWN) {
176-
try {
177-
// Get video frame data
171+
async function detect() {
172+
const poses = await detector.estimatePoses(video);
178173
ctx.clearRect(0, 0, canvas.width, canvas.height);
179174
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
180-
181-
// Get image data to analyze pixels
182-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
183-
const data = imageData.data;
184-
185-
// Track stick
186-
let stickX = 0;
187-
let stickY = 0;
188-
let matchingPixels = 0;
189-
190-
// Get selected color
191-
const selectedColor = document.getElementById('stick-color').value;
192-
193-
// Scan for matching colored pixels
194-
for (let i = 0; i < data.length; i += 4) {
195-
const r = data[i];
196-
const g = data[i + 1];
197-
const b = data[i + 2];
198-
199-
let isMatch = false;
200-
if (selectedColor === 'black') {
201-
isMatch = r < 50 && g < 50 && b < 50;
202-
} else if (selectedColor === 'white') {
203-
isMatch = r > 200 && g > 200 && b > 200;
204-
}
205-
206-
if (isMatch) {
207-
const pixelIndex = i / 4;
208-
const x = pixelIndex % canvas.width;
209-
const y = Math.floor(pixelIndex / canvas.width);
210-
stickX += x;
211-
stickY += y;
212-
matchingPixels++;
213-
}
214-
}
215-
216-
if (matchingPixels > 100) {
217-
stickX = Math.round(stickX / matchingPixels);
218-
stickY = Math.round(stickY / matchingPixels);
219-
220-
ctx.fillStyle = '#00FF00';
221-
ctx.beginPath();
222-
ctx.arc(stickX, stickY, 8, 0, 2 * Math.PI);
223-
ctx.fill();
224-
225-
ctx.fillStyle = 'white';
226-
ctx.font = '16px Arial';
227-
ctx.fillText(`${selectedColor.charAt(0).toUpperCase() + selectedColor.slice(1)} Stick: (${stickX}, ${stickY})`, 10, 20);
228-
229-
const currentTime = Date.now();
230-
const timeSinceLastRep = currentTime - lastRepTime;
231-
232-
if (stickY < canvas.height / 3) {
233-
stickStage = 'ready';
234-
}
235-
236-
if (stickY > canvas.height * 2/3 &&
237-
stickStage === 'ready' &&
238-
timeSinceLastRep > REP_COOLDOWN) {
239-
reps++;
240-
stickStage = 'down';
241-
lastRepTime = currentTime;
242-
document.getElementById('reps').textContent = `Reps: ${reps}`;
175+
176+
if (poses.length > 0) {
177+
const keypoints = poses[0].keypoints;
178+
const trackedHand = document.getElementById('hand-select').value;
179+
180+
const wrist = keypoints.find(k => k.name === `${trackedHand}_wrist`);
181+
const shoulder = keypoints.find(k => k.name === `${trackedHand}_shoulder`);
182+
183+
if (wrist && shoulder && wrist.score > 0.5 && shoulder.score > 0.5) {
184+
drawKeypoints(keypoints, ctx);
185+
const distance = Math.abs(wrist.y - shoulder.y);
186+
187+
if (distance < 50) { // Hand is up
188+
stage = 'up';
189+
}
190+
if (distance > 100 && stage === 'up') { // Hand is down
191+
stage = 'down';
192+
reps++;
193+
document.getElementById('reps').textContent = `Reps: ${reps}`;
194+
}
243195
}
244-
245-
ctx.fillText(`Stage: ${stickStage || 'none'}`, 10, 40);
246196
}
247-
} catch (error) {
248-
console.error('Detection error:', error);
197+
requestAnimationFrame(detect);
249198
}
250-
251-
requestAnimationFrame(() => detect(video, ctx, canvas, reps, stickStage, lastRepTime, REP_COOLDOWN));
199+
200+
detect();
252201
}
253202

254203
function resetReps() {
255204
reps = 0;
256-
stickStage = null;
205+
stage = null;
257206
document.getElementById('reps').textContent = 'Reps: 0';
258207
}
259208

260209
window.onload = () => {
261-
main();
262-
263-
// Load saved color preference
264-
const savedColor = localStorage.getItem('wallBallStickColor') || 'black';
265-
document.getElementById('stick-color').value = savedColor;
266-
267-
// Add event listener for color selection change
268-
document.getElementById('stick-color').addEventListener('change', function() {
269-
localStorage.setItem('wallBallStickColor', this.value);
270-
});
271-
272-
// Add event listener for view toggle
273-
let showLines = true;
274-
document.getElementById('view-toggle').addEventListener('click', function() {
275-
showLines = !showLines;
276-
this.textContent = showLines ? 'Hide Lines' : 'Show Lines';
210+
main().catch(console.error);
211+
const savedHand = localStorage.getItem('wallBallTrackedHand') || 'right';
212+
document.getElementById('hand-select').value = savedHand;
213+
214+
document.getElementById('hand-select').addEventListener('change', function() {
215+
localStorage.setItem('wallBallTrackedHand', this.value);
277216
});
278217
};
279218
</script>

0 commit comments

Comments
 (0)