zlf18 commited on
Commit
588e3bb
·
verified ·
1 Parent(s): 7f58cb2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +233 -0
app.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Import all the libraries
2
+ import gradio as gr
3
+ import cv2
4
+ import mediapipe as mp
5
+ import numpy as np
6
+ import math
7
+ import os # Import os to check for file
8
+
9
+ # --- This dictionary now points to YOUR uploaded presets ---
10
+ PRELOADED_MODELS = {
11
+ "Preset 1": "preset_1.stl",
12
+ "Preset 2": "preset_2.stl",
13
+ "Preset 3": "preset_3.stl"
14
+ }
15
+
16
+ # 4. Set up our MediaPipe objects
17
+ mp_drawing = mp.solutions.drawing_utils
18
+ mp_hands = mp.solutions.hands
19
+
20
+ # We are detecting ONE hand
21
+ hands = mp_hands.Hands(
22
+ max_num_hands=1,
23
+ min_detection_confidence=0.7,
24
+ min_tracking_confidence=0.5
25
+ )
26
+
27
+ # 5. --- GESTURE RECOGNIZER CLASS (Same as before) ---
28
+ class GestureRecognizer:
29
+ def __init__(self):
30
+ self.PINCH_THRESHOLD = 0.08
31
+
32
+ def _calculate_distance(self, lm1, lm2):
33
+ return math.sqrt((lm1.x - lm2.x)**2 + (lm1.y - lm2.y)**2)
34
+
35
+ def recognize(self, hand_landmarks):
36
+ landmarks = hand_landmarks.landmark
37
+
38
+ wrist_pos = (landmarks[mp_hands.HandLandmark.WRIST].x, landmarks[mp_hands.HandLandmark.WRIST].y)
39
+
40
+ # 1. Check for "PEACE SIGN"
41
+ index_extended = landmarks[mp_hands.HandLandmark.INDEX_FINGER_TIP].y < landmarks[mp_hands.HandLandmark.INDEX_FINGER_PIP].y
42
+ middle_extended = landmarks[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].y < landmarks[mp_hands.HandLandmark.MIDDLE_FINGER_PIP].y
43
+ ring_curled = landmarks[mp_hands.HandLandmark.RING_FINGER_TIP].y > landmarks[mp_hands.HandLandmark.RING_FINGER_PIP].y
44
+ pinky_curled = landmarks[mp_hands.HandLandmark.PINKY_TIP].y > landmarks[mp_hands.HandLandmark.PINKY_PIP].y
45
+
46
+ if index_extended and middle_extended and ring_curled and pinky_curled:
47
+ return "**PEACE**", wrist_pos
48
+
49
+ # 2. Check for "FIST"
50
+ index_curled = landmarks[mp_hands.HandLandmark.INDEX_FINGER_TIP].y > landmarks[mp_hands.HandLandmark.INDEX_FINGER_PIP].y
51
+ middle_curled = landmarks[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].y > landmarks[mp_hands.HandLandmark.MIDDLE_FINGER_PIP].y
52
+ ring_curled = landmarks[mp_hands.HandLandmark.RING_FINGER_TIP].y > landmarks[mp_hands.HandLandmark.RING_FINGER_PIP].y
53
+
54
+ if index_curled and middle_curled and ring_curled:
55
+ return "**FIST**", wrist_pos # Return wrist position
56
+
57
+ # 3. Check for "PINCH"
58
+ thumb_tip = landmarks[mp_hands.HandLandmark.THUMB_TIP]
59
+ index_tip = landmarks[mp_hands.HandLandmark.INDEX_FINGER_TIP]
60
+ pinch_distance = self._calculate_distance(thumb_tip, index_tip)
61
+
62
+ if pinch_distance < self.PINCH_THRESHOLD:
63
+ return "**PINCH**", wrist_pos
64
+
65
+ # 4. If no other gesture, it's "OPEN"
66
+ return "**OPEN**", None
67
+
68
+ # 6. --- Create an instance of our recognizer ---
69
+ recognizer = GestureRecognizer()
70
+
71
+ # 7. --- GESTURE PROCESSING FUNCTION (OPTIMIZED) ---
72
+
73
+ DEFAULT_CAMERA = (0, 90, 150)
74
+ ROTATION_SENSITIVITY = 360
75
+ ZOOM_SENSITIVITY = 75 # Your new faster zoom
76
+
77
+ def process_image(webcam_frame, camera_state, prev_hand_pos_state):
78
+
79
+ gesture_text = "No hand detected"
80
+ new_camera_state = camera_state
81
+ new_prev_hand_pos = None
82
+
83
+ (old_azimuth, old_polar, old_zoom) = camera_state
84
+
85
+ if webcam_frame is None:
86
+ return "Waiting...", DEFAULT_CAMERA, None, gr.update(camera_position=DEFAULT_CAMERA)
87
+
88
+ # --- OPTIMIZATION 1: Pass by reference, don't copy ---
89
+ image = webcam_frame
90
+ image.flags.writeable = False
91
+ results = hands.process(image)
92
+ # No need to set writeable=True, we aren't drawing
93
+
94
+ if results.multi_hand_landmarks:
95
+ hand_landmarks = results.multi_hand_landmarks[0]
96
+
97
+ current_gesture, current_hand_pos = recognizer.recognize(hand_landmarks)
98
+ gesture_text = current_gesture.replace("**", "")
99
+
100
+ if current_gesture == "**PEACE**":
101
+ new_camera_state = DEFAULT_CAMERA
102
+ gesture_text = "Resetting view!"
103
+
104
+ elif current_hand_pos and prev_hand_pos_state:
105
+ delta_x = current_hand_pos[0] - prev_hand_pos_state[0]
106
+ delta_y = current_hand_pos[1] - prev_hand_pos_state[1]
107
+
108
+ if current_gesture == "**PINCH**":
109
+ new_azimuth = old_azimuth + (delta_x * ROTATION_SENSITIVITY)
110
+ new_polar = old_polar - (delta_y * ROTATION_SENSITIVITY)
111
+ new_camera_state = (new_azimuth, new_polar, old_zoom)
112
+ gesture_text = "Rotating..."
113
+
114
+ elif current_gesture == "**FIST**":
115
+ new_zoom = old_zoom + (delta_x * ZOOM_SENSITIVITY)
116
+ new_zoom = max(0.5, min(new_zoom, 400.0))
117
+ new_camera_state = (old_azimuth, old_polar, new_zoom)
118
+ gesture_text = "Zooming..."
119
+
120
+ if current_hand_pos:
121
+ new_prev_hand_pos = current_hand_pos
122
+
123
+ # --- OPTIMIZATION 2: Removed mp_drawing.draw_landmarks() ---
124
+
125
+ else:
126
+ new_prev_hand_pos = None
127
+
128
+ # --- FIX 2: Return 4 values, removing the image ---
129
+ return gesture_text, new_camera_state, new_prev_hand_pos, gr.update(camera_position=new_camera_state)
130
+
131
+ # 8. --- HELPER FUNCTIONS ---
132
+
133
+ def load_preloaded_model(model_name):
134
+ file_path = PRELOADED_MODELS[model_name]
135
+ return gr.update(value=file_path), gr.update(value=DEFAULT_CAMERA)
136
+
137
+ def load_uploaded_model(temp_file):
138
+ return gr.update(value=temp_file.name), gr.update(value=DEFAULT_CAMERA)
139
+
140
+
141
+ # 9. --- Create and launch the Gradio Interface ---
142
+ with gr.Blocks(theme=gr.themes.Glass()) as demo:
143
+
144
+ camera_state = gr.State(value=DEFAULT_CAMERA)
145
+ prev_hand_pos_state = gr.State(value=None)
146
+
147
+ with gr.Row():
148
+ gr.Markdown("# 🖐️ Gesture-Controlled 3D Viewer")
149
+
150
+ with gr.Row():
151
+ # --- UI Column for controls ---
152
+ with gr.Column(scale=1):
153
+ gr.Markdown("### 1. Select a Model")
154
+ with gr.Tabs():
155
+ with gr.Tab("Your Presets"):
156
+ radio_picker = gr.Radio(
157
+ choices=list(PRELOADED_MODELS.keys()),
158
+ label="Select your preset model",
159
+ value="Preset 1"
160
+ )
161
+ with gr.Tab("Upload"):
162
+ file_uploader = gr.File(
163
+ label="Upload .stl, .obj, .glb, or .3mf",
164
+ file_types=['.stl', '.obj', '.glb', '.gltf', '.3mf']
165
+ )
166
+
167
+ gr.Markdown("### 2. Control with Webcam")
168
+ webcam_input = gr.Image(sources=["webcam"], streaming=True, label="Webcam Feed")
169
+
170
+ gr.Markdown("### 3. Gesture Status")
171
+ text_output = gr.Text(label="Status")
172
+
173
+ # --- MAIN COLUMN for 3D viewer ---
174
+ with gr.Column(scale=3):
175
+ default_model_path = PRELOADED_MODELS.get("Preset 1", None)
176
+ # This check is now for the deployment environment
177
+ if not os.path.exists(default_model_path):
178
+ print(f"Warning: Default preset '{default_model_path}' not found.")
179
+ default_model_path = None
180
+
181
+ model_3d = gr.Model3D(
182
+ value=default_model_path,
183
+ label=None,
184
+ camera_position=DEFAULT_CAMERA,
185
+ interactive=False
186
+ )
187
+
188
+ gr.Markdown(
189
+ """
190
+ <div style="width: 100%; text-align: center;">
191
+ <b>Gesture Controls:</b> ✌️ <b>Peace Sign:</b> Reset/Home | 👌 <b>Pinch & Move:</b> Rotate | ✊ <b>Fist & Move Left/Right:</b> Zoom
192
+ </div>
193
+ """
194
+ )
195
+
196
+ # --- OPTIMIZATION 3: Removed webcam_output component ---
197
+
198
+ # --- 10. WIRE UP THE NEW CONTROLS ---
199
+
200
+ # 1. Wire up the Radio buttons
201
+ radio_picker.change(
202
+ fn=load_preloaded_model,
203
+ inputs=radio_picker,
204
+ outputs=[model_3d, camera_state]
205
+ )
206
+
207
+ # 2. Wire up the File uploader
208
+ file_uploader.upload(
209
+ fn=load_uploaded_model,
210
+ inputs=file_uploader,
211
+ outputs=[model_3d, camera_state]
212
+ )
213
+
214
+ # 3. Wire up the webcam gesture controls
215
+ webcam_input.stream(
216
+ fn=process_image,
217
+ inputs=[
218
+ webcam_input,
219
+ camera_state,
220
+ prev_hand_pos_state
221
+ ],
222
+ outputs=[
223
+ text_output,
224
+ camera_state,
225
+ prev_hand_pos_state,
226
+ model_3d
227
+ ]
228
+ )
229
+
230
+ # --- THIS IS THE FINAL, NEW LAUNCH COMMAND ---
231
+ # This makes your app public but requires a password to access.
232
+ # You can change "guest" to any username you want.
233
+ demo.launch(auth=("guest", "Its_zion_18"))