tperm-visor 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>T-PERM — Touchless Permutation & Execution for Rubik’s Manipulation</title>
7
+ <meta name="description" content="T-PERM: Gesture-driven 3x3 AR Rubik's Cube manipulation. Powered by Python backend with MediaPipe hand tracking." />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="css/style.css" />
12
+ </head>
13
+ <body>
14
+
15
+ <!-- ── BOOT / CONSOLE SCREEN ─────────────────────────────────────────────── -->
16
+ <div id="boot-screen">
17
+ <div class="scanlines" aria-hidden="true"></div>
18
+ <div class="boot-frame">
19
+ <div class="boot-eyebrow">
20
+ <span class="dot"></span> T-PERM &middot; TOUCHLESS RUBIK'S MANIPULATION &middot; PYTHON CORE
21
+ </div>
22
+ <h1 class="boot-title">T-PERM</h1>
23
+ <p class="boot-sub">Touchless Permutation & Execution for Rubik's Manipulation. A physical Rubik's cube that lives in your hands — tracked by your webcam, rendered in real time, and solved with gestures alone.</p>
24
+
25
+ <div class="boot-panel">
26
+ <div class="panel-row panel-head">
27
+ <span>GESTURE</span><span>ACTION</span>
28
+ </div>
29
+ <div class="panel-row"><span>Both hands together</span><span>Spawn the cube</span></div>
30
+ <div class="panel-row"><span>Twist / tilt your wrist</span><span>Rotate the view</span></div>
31
+ <div class="panel-row"><span>Palm flat, facing down / up</span><span>Snap to top / bottom face</span></div>
32
+ <div class="panel-row"><span>Pinch an edge + drag</span><span>Turn that layer</span></div>
33
+ <div class="panel-row"><span>Open palm (show 5)</span><span>Lock — pause rotation</span></div>
34
+ <div class="panel-row"><span>Take both hands away</span><span>Check for solve</span></div>
35
+ </div>
36
+
37
+ <button id="start-btn" class="btn-engage">Engage camera</button>
38
+ <p id="boot-error" class="boot-error" role="alert"></p>
39
+ <p class="boot-footnote">Backend running locally. Click above to start the camera.</p>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- ── LIVE APP STAGE ─────────────────────────────────────────────────────── -->
44
+ <main id="stage" hidden>
45
+ <header class="topbar">
46
+ <div class="brand"><span class="dot"></span> T-PERM / TOUCHLESS RUBIK'S MANIPULATION</div>
47
+ <div class="topbar-actions">
48
+ <button id="legend-toggle" class="btn-ghost">Gestures</button>
49
+ <button id="reset-btn" class="btn-ghost">New scramble</button>
50
+ <button id="quit-btn" class="btn-quit">Stop server</button>
51
+ </div>
52
+ </header>
53
+
54
+ <div class="viewport-wrap">
55
+ <div class="viewport-frame">
56
+ <img id="backend-stream" alt="Live AR cube stream from the Python backend" hidden />
57
+
58
+ <span class="bracket tl"></span><span class="bracket tr"></span>
59
+ <span class="bracket bl"></span><span class="bracket br"></span>
60
+
61
+ <div id="legend-panel" class="legend-panel">
62
+ <div class="panel-row panel-head"><span>GESTURE</span><span>ACTION</span></div>
63
+ <div class="panel-row"><span>Both hands together</span><span>Spawn the cube</span></div>
64
+ <div class="panel-row"><span>Twist / tilt wrist</span><span>Rotate view</span></div>
65
+ <div class="panel-row"><span>Palm down / up</span><span>Snap top / bottom</span></div>
66
+ <div class="panel-row"><span>Pinch edge + drag</span><span>Turn layer</span></div>
67
+ <div class="panel-row"><span>Open palm</span><span>Lock rotation</span></div>
68
+ <div class="panel-row"><span>Hands away</span><span>Check solve</span></div>
69
+ </div>
70
+ </div>
71
+
72
+ <div class="telemetry">
73
+ <div class="tel-item"><span class="tel-label">MODE</span><span class="tel-value" id="tel-mode">PYTHON BACKEND</span></div>
74
+ <div class="tel-item"><span class="tel-label">STATE</span><span class="tel-value" id="tel-state">IDLE</span></div>
75
+ <div class="tel-item"><span class="tel-label">HANDS</span><span class="tel-value" id="tel-hands">0</span></div>
76
+ <div class="tel-item"><span class="tel-label">RENDER FPS</span><span class="tel-value" id="tel-fps">0</span></div>
77
+ <div class="tel-item"><span class="tel-label">TRACK FPS</span><span class="tel-value" id="tel-detfps">0</span></div>
78
+ <div class="tel-item"><span class="tel-label">LAG</span><span class="tel-value" id="tel-lag">0</span></div>
79
+ <div class="tel-item"><span class="tel-label">HANDS TRACKED</span><span class="tel-value" id="tel-numhands">2</span></div>
80
+ </div>
81
+
82
+ </div>
83
+
84
+ <footer class="footbar"><strong>T-PERM</strong> — Touchless Permutation & Execution for Rubik's Manipulation.</footer>
85
+ </main>
86
+
87
+ <div id="quit-screen" hidden>
88
+ <div class="quit-card">
89
+ <h2>Server stopped</h2>
90
+ <p>The camera has been released and the terminal is free.<br />Run <code>npx t-perm</code> again to restart.</p>
91
+ </div>
92
+ </div>
93
+
94
+ <script src="js/app.js"></script>
95
+ </body>
96
+ </html>
@@ -0,0 +1,105 @@
1
+ // app.js — Backend-only mode: displays MJPEG stream from Python Flask server
2
+ // All hand tracking, cube rendering, and gesture processing happens on the backend.
3
+
4
+ // server.py serves this page itself, so same-origin is the normal case and it
5
+ // keeps working when the backend is reached from another device on the LAN.
6
+ // Only fall back to a hardcoded host when opened straight off the filesystem.
7
+ const BACKEND_URL = location.protocol === 'file:' ? 'http://localhost:5000' : '';
8
+
9
+ const els = {
10
+ boot: document.getElementById('boot-screen'),
11
+ stage: document.getElementById('stage'),
12
+ startBtn: document.getElementById('start-btn'),
13
+ bootError: document.getElementById('boot-error'),
14
+ backendStream: document.getElementById('backend-stream'),
15
+ telemetryMode: document.getElementById('tel-mode'),
16
+ telemetryState: document.getElementById('tel-state'),
17
+ telemetryHands: document.getElementById('tel-hands'),
18
+ telemetryFps: document.getElementById('tel-fps'),
19
+ telemetryDetFps: document.getElementById('tel-detfps'),
20
+ telemetryLag: document.getElementById('tel-lag'),
21
+ telemetryNumHands: document.getElementById('tel-numhands'),
22
+ resetBtn: document.getElementById('reset-btn'),
23
+ legendToggle: document.getElementById('legend-toggle'),
24
+ quitBtn: document.getElementById('quit-btn'),
25
+ quitScreen: document.getElementById('quit-screen'),
26
+ legendPanel: document.getElementById('legend-panel'),
27
+ };
28
+
29
+ els.startBtn.addEventListener('click', start);
30
+ els.resetBtn.addEventListener('click', async () => {
31
+ try { await fetch(`${BACKEND_URL}/api/reset`, { method: 'POST' }); } catch (e) {}
32
+ });
33
+ els.legendToggle.addEventListener('click', () => {
34
+ els.legendPanel.classList.toggle('open');
35
+ });
36
+
37
+ let statusTimer = null;
38
+
39
+ // Stop the backend and free the terminal. The request is expected to die
40
+ // mid-flight when the process exits, so a rejected fetch here is success.
41
+ els.quitBtn.addEventListener('click', async () => {
42
+ if (!confirm('Stop the T-PERM server? The camera will be released and the terminal will return to a prompt.')) return;
43
+ els.quitBtn.disabled = true;
44
+ els.quitBtn.textContent = 'Stopping…';
45
+ if (statusTimer) clearInterval(statusTimer);
46
+ els.backendStream.src = ''; // drop the MJPEG stream so it stops retrying
47
+ try {
48
+ await fetch(`${BACKEND_URL}/api/shutdown`, { method: 'POST' });
49
+ } catch (e) { /* connection dropped as the process exited - expected */ }
50
+ els.quitScreen.hidden = false;
51
+ });
52
+
53
+ async function checkBackendServer() {
54
+ try {
55
+ const controller = new AbortController();
56
+ const timeoutId = setTimeout(() => controller.abort(), 2000);
57
+ const res = await fetch(`${BACKEND_URL}/health`, { signal: controller.signal });
58
+ clearTimeout(timeoutId);
59
+ return res.ok;
60
+ } catch (e) {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ async function start() {
66
+ els.startBtn.disabled = true;
67
+ els.startBtn.textContent = 'Connecting to backend\u2026';
68
+ els.bootError.textContent = '';
69
+
70
+ const backendAvailable = await checkBackendServer();
71
+
72
+ if (!backendAvailable) {
73
+ els.startBtn.disabled = false;
74
+ els.startBtn.textContent = 'Engage camera';
75
+ els.bootError.textContent =
76
+ 'Backend server not found. Start the Python backend first: cd backend && python server.py';
77
+ return;
78
+ }
79
+
80
+ if (els.telemetryMode) els.telemetryMode.textContent = 'PYTHON BACKEND';
81
+ els.startBtn.textContent = 'Connecting to stream\u2026';
82
+
83
+ // Display the MJPEG stream from the backend
84
+ els.backendStream.src = `${BACKEND_URL}/video_feed`;
85
+ els.backendStream.hidden = false;
86
+
87
+ els.boot.hidden = true;
88
+ els.stage.hidden = false;
89
+
90
+ // Poll backend status telemetry
91
+ statusTimer = setInterval(async () => {
92
+ try {
93
+ const res = await fetch(`${BACKEND_URL}/api/status`);
94
+ if (res.ok) {
95
+ const data = await res.json();
96
+ if (els.telemetryState) els.telemetryState.textContent = data.state || 'RUNNING';
97
+ if (els.telemetryHands) els.telemetryHands.textContent = data.hands ?? '0';
98
+ if (els.telemetryFps) els.telemetryFps.textContent = data.fps ?? '0';
99
+ if (els.telemetryDetFps) els.telemetryDetFps.textContent = data.det_fps ?? '0';
100
+ if (els.telemetryLag) els.telemetryLag.textContent = `${data.lag_ms ?? 0}ms`;
101
+ if (els.telemetryNumHands) els.telemetryNumHands.textContent = data.num_hands ?? '-';
102
+ }
103
+ } catch (e) {}
104
+ }, 250);
105
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "tperm-visor",
3
+ "version": "1.0.1",
4
+ "description": "T-PERM - gesture-driven AR Rubik's Cube in your browser. Webcam hand tracking via MediaPipe, 3D cube rendered with OpenGL. Requires Python 3.9+.",
5
+ "bin": {
6
+ "tperm-visor": "bin/t-perm.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "backend/*.py",
11
+ "backend/hand_landmarker.task",
12
+ "backend/requirements.txt",
13
+ "backend/cube/*.py",
14
+ "backend/utils/*.py",
15
+ "frontend/",
16
+ "README.md",
17
+ "LICENSE",
18
+ "!backend/test_*.py",
19
+ "!backend/diagnose_tracking.py",
20
+ "!**/__pycache__/**"
21
+ ],
22
+ "engines": {
23
+ "node": ">=14"
24
+ },
25
+ "keywords": [
26
+ "ar",
27
+ "rubiks",
28
+ "rubiks-cube",
29
+ "mediapipe",
30
+ "opencv",
31
+ "gesture",
32
+ "hand-tracking",
33
+ "computer-vision",
34
+ "webcam"
35
+ ],
36
+ "author": "Utkarsh <utkarshsingh72007@gmail.com>",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/4-thkind/T-PERM.git"
41
+ },
42
+ "homepage": "https://github.com/4-thkind/T-PERM#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/4-thkind/T-PERM/issues"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }