XiaoBai1221 commited on
Commit
ddab8ea
·
0 Parent(s):

🎉 SignView2.0 精簡版部署 (with Git LFS)

Browse files

✨ 核心功能:
- 支援34種手語詞彙的即時辨識
- Gradio 網頁界面,攝像頭即時辨識
- MediaPipe Segmentation 背景分割
- 94.25% 測試準確率的深度學習模型

🔧 技術架構:
- MediaPipe Holistic 關鍵點檢測
- 光流特徵即時計算
- 雙向LSTM + GRU + 多頭注意力機制
- 人體分割自動去除背景噪聲

📦 精簡包含:
- app.py: Gradio 網頁應用程式
- realtime_sign_prediction.py: 核心辨識系統
- tsflow/models/best_model.pt: 訓練好的模型(Git LFS)
- tsflow/results/: 模型配置和測試結果
- requirements.txt: 依賴套件
- README.md: 專案說明文件

⚡ 優化:使用 Git LFS 處理二進制檔案,移除預處理特徵檔案改為即時計算

.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.pt filter=lfs diff=lfs merge=lfs -text
2
+ *.png filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1 @@
 
 
1
+
realtime_sign_prediction.py ADDED
@@ -0,0 +1,1122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ import mediapipe as mp
8
+ import json
9
+ import time
10
+ from collections import deque
11
+ import argparse
12
+
13
+ class FeatureExtractor:
14
+ def __init__(self, use_segmentation=True):
15
+ # Initialize MediaPipe models
16
+ self.mp_holistic = mp.solutions.holistic
17
+ self.mp_drawing = mp.solutions.drawing_utils
18
+ self.mp_drawing_styles = mp.solutions.drawing_styles
19
+ self.mp_selfie_segmentation = mp.solutions.selfie_segmentation
20
+
21
+ # Segmentation settings
22
+ self.use_segmentation = use_segmentation
23
+ self.segmentation = None
24
+ if self.use_segmentation:
25
+ self.segmentation = self.mp_selfie_segmentation.SelfieSegmentation(model_selection=1)
26
+
27
+ # Optical flow parameters
28
+ self.optical_flow_params = dict(
29
+ flow=None,
30
+ pyr_scale=0.5,
31
+ levels=3,
32
+ winsize=15,
33
+ iterations=3,
34
+ poly_n=5,
35
+ poly_sigma=1.2,
36
+ flags=0
37
+ )
38
+
39
+ def extract_pose_keypoints(self, frame, holistic_results):
40
+ """Extract pose keypoints"""
41
+ keypoints = []
42
+
43
+ # Extract hand keypoints
44
+ if holistic_results.left_hand_landmarks:
45
+ for landmark in holistic_results.left_hand_landmarks.landmark:
46
+ keypoints.extend([landmark.x, landmark.y, landmark.z])
47
+ else:
48
+ keypoints.extend([0] * (21 * 3))
49
+
50
+ if holistic_results.right_hand_landmarks:
51
+ for landmark in holistic_results.right_hand_landmarks.landmark:
52
+ keypoints.extend([landmark.x, landmark.y, landmark.z])
53
+ else:
54
+ keypoints.extend([0] * (21 * 3))
55
+
56
+ # Extract pose keypoints
57
+ if holistic_results.pose_landmarks:
58
+ for landmark in holistic_results.pose_landmarks.landmark:
59
+ keypoints.extend([landmark.x, landmark.y, landmark.z])
60
+ else:
61
+ keypoints.extend([0] * (33 * 3))
62
+
63
+ return np.array(keypoints)
64
+
65
+ def create_hand_mask(self, frame, left_hand_landmarks, right_hand_landmarks, pose_landmarks):
66
+ """Create ROI mask for hands and upper body"""
67
+ h, w = frame.shape[:2]
68
+ mask = np.zeros((h, w), dtype=np.uint8)
69
+
70
+ def draw_landmarks_on_mask(landmarks, radius=15):
71
+ if landmarks:
72
+ for landmark in landmarks.landmark:
73
+ x, y = int(landmark.x * w), int(landmark.y * h)
74
+ if 0 <= x < w and 0 <= y < h:
75
+ cv2.circle(mask, (x, y), radius=radius, color=255, thickness=-1)
76
+
77
+ # Draw hand keypoints
78
+ draw_landmarks_on_mask(left_hand_landmarks, radius=20)
79
+ draw_landmarks_on_mask(right_hand_landmarks, radius=20)
80
+
81
+ # Draw upper body keypoints
82
+ if pose_landmarks:
83
+ upper_body_indices = list(range(0, 25))
84
+ for idx in upper_body_indices:
85
+ if idx < len(pose_landmarks.landmark):
86
+ landmark = pose_landmarks.landmark[idx]
87
+ x, y = int(landmark.x * w), int(landmark.y * h)
88
+ if 0 <= x < w and 0 <= y < h:
89
+ cv2.circle(mask, (x, y), radius=10, color=255, thickness=-1)
90
+
91
+ # Dilate mask
92
+ kernel = np.ones((15, 15), np.uint8)
93
+ dilated_mask = cv2.dilate(mask, kernel, iterations=1)
94
+
95
+ return dilated_mask
96
+
97
+ def compute_regional_optical_flow(self, prev_frame, curr_frame, mask, downscale=0.5):
98
+ """Compute optical flow only in masked regions"""
99
+ if downscale < 1.0:
100
+ h, w = prev_frame.shape[:2]
101
+ new_h, new_w = int(h * downscale), int(w * downscale)
102
+ prev_small = cv2.resize(prev_frame, (new_w, new_h))
103
+ curr_small = cv2.resize(curr_frame, (new_w, new_h))
104
+ mask_small = cv2.resize(mask, (new_w, new_h))
105
+ else:
106
+ prev_small = prev_frame
107
+ curr_small = curr_frame
108
+ mask_small = mask
109
+
110
+ # Convert to grayscale
111
+ prev_gray = cv2.cvtColor(prev_small, cv2.COLOR_BGR2GRAY)
112
+ curr_gray = cv2.cvtColor(curr_small, cv2.COLOR_BGR2GRAY)
113
+
114
+ # Compute optical flow
115
+ flow = cv2.calcOpticalFlowFarneback(
116
+ prev_gray, curr_gray,
117
+ self.optical_flow_params['flow'],
118
+ self.optical_flow_params['pyr_scale'],
119
+ self.optical_flow_params['levels'],
120
+ self.optical_flow_params['winsize'],
121
+ self.optical_flow_params['iterations'],
122
+ self.optical_flow_params['poly_n'],
123
+ self.optical_flow_params['poly_sigma'],
124
+ self.optical_flow_params['flags']
125
+ )
126
+
127
+ # Extract flow features from masked region
128
+ bool_mask = mask_small > 0
129
+
130
+ if np.any(bool_mask):
131
+ fx = flow[..., 0][bool_mask]
132
+ fy = flow[..., 1][bool_mask]
133
+
134
+ flow_features = np.array([
135
+ np.mean(fx), np.std(fx),
136
+ np.mean(fy), np.std(fy),
137
+ np.percentile(fx, 25), np.percentile(fx, 75),
138
+ np.percentile(fy, 25), np.percentile(fy, 75),
139
+ np.max(np.abs(fx)), np.max(np.abs(fy))
140
+ ], dtype=np.float16)
141
+ else:
142
+ flow_features = np.zeros(10, dtype=np.float16)
143
+
144
+ return flow_features
145
+
146
+ def apply_segmentation_mask(self, frame):
147
+ """Apply human segmentation to focus on person area"""
148
+ if not self.use_segmentation or self.segmentation is None:
149
+ return frame, None
150
+
151
+ try:
152
+ # Convert BGR to RGB for MediaPipe
153
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
154
+ frame_rgb.flags.writeable = False
155
+
156
+ # Process segmentation
157
+ results = self.segmentation.process(frame_rgb)
158
+ segmentation_mask = results.segmentation_mask
159
+
160
+ if segmentation_mask is not None:
161
+ # Resize mask to match frame size
162
+ h, w = frame.shape[:2]
163
+ mask = cv2.resize(segmentation_mask, (w, h))
164
+
165
+ # Convert to 3-channel mask
166
+ mask_3channel = np.stack((mask,) * 3, axis=-1)
167
+
168
+ # Apply Gaussian blur to smooth edges
169
+ mask_3channel = cv2.GaussianBlur(mask_3channel, (5, 5), 0)
170
+
171
+ # Create segmented frame
172
+ segmented_frame = frame * mask_3channel
173
+
174
+ # Convert binary mask for optical flow processing
175
+ binary_mask = (mask > 0.5).astype(np.uint8) * 255
176
+
177
+ return segmented_frame.astype(np.uint8), binary_mask
178
+ else:
179
+ return frame, None
180
+
181
+ except Exception as e:
182
+ print(f"Segmentation error: {e}")
183
+ return frame, None
184
+
185
+ def create_enhanced_hand_mask(self, frame, left_hand_landmarks, right_hand_landmarks, pose_landmarks, seg_mask=None):
186
+ """Create enhanced ROI mask combining landmarks and segmentation"""
187
+ h, w = frame.shape[:2]
188
+ mask = np.zeros((h, w), dtype=np.uint8)
189
+
190
+ def draw_landmarks_on_mask(landmarks, radius=15):
191
+ if landmarks:
192
+ for landmark in landmarks.landmark:
193
+ x, y = int(landmark.x * w), int(landmark.y * h)
194
+ if 0 <= x < w and 0 <= y < h:
195
+ cv2.circle(mask, (x, y), radius=radius, color=255, thickness=-1)
196
+
197
+ # Draw hand keypoints with larger radius
198
+ draw_landmarks_on_mask(left_hand_landmarks, radius=25)
199
+ draw_landmarks_on_mask(right_hand_landmarks, radius=25)
200
+
201
+ # Draw upper body keypoints
202
+ if pose_landmarks:
203
+ upper_body_indices = list(range(0, 25))
204
+ for idx in upper_body_indices:
205
+ if idx < len(pose_landmarks.landmark):
206
+ landmark = pose_landmarks.landmark[idx]
207
+ x, y = int(landmark.x * w), int(landmark.y * h)
208
+ if 0 <= x < w and 0 <= y < h:
209
+ cv2.circle(mask, (x, y), radius=12, color=255, thickness=-1)
210
+
211
+ # Combine with segmentation mask if available
212
+ if seg_mask is not None:
213
+ seg_mask_resized = cv2.resize(seg_mask, (w, h))
214
+ mask = cv2.bitwise_and(mask, seg_mask_resized)
215
+
216
+ # Dilate mask
217
+ kernel = np.ones((20, 20), np.uint8)
218
+ dilated_mask = cv2.dilate(mask, kernel, iterations=2)
219
+
220
+ return dilated_mask
221
+
222
+ class SignLanguageModel(nn.Module):
223
+ """Sign Language Recognition Model"""
224
+ def __init__(self, input_dim, hidden_dim, num_layers, num_classes, dropout=0.5, flow_dim=10):
225
+ super(SignLanguageModel, self).__init__()
226
+ self.hidden_dim = hidden_dim
227
+ self.num_layers = num_layers
228
+ self.num_classes = num_classes
229
+
230
+ # Keypoint feature projection
231
+ self.keypoint_projection = nn.Sequential(
232
+ nn.Linear(input_dim, hidden_dim),
233
+ nn.BatchNorm1d(hidden_dim),
234
+ nn.ReLU(),
235
+ nn.Dropout(dropout/2),
236
+ nn.Linear(hidden_dim, hidden_dim),
237
+ nn.BatchNorm1d(hidden_dim),
238
+ nn.ReLU(),
239
+ nn.Dropout(dropout/2)
240
+ )
241
+
242
+ # Flow feature projection
243
+ self.flow_projection = nn.Sequential(
244
+ nn.Linear(flow_dim, hidden_dim // 2),
245
+ nn.BatchNorm1d(hidden_dim // 2),
246
+ nn.ReLU(),
247
+ nn.Dropout(dropout/2),
248
+ nn.Linear(hidden_dim // 2, hidden_dim // 2),
249
+ nn.BatchNorm1d(hidden_dim // 2),
250
+ nn.ReLU(),
251
+ nn.Dropout(dropout/2)
252
+ )
253
+
254
+ # Feature fusion
255
+ self.fusion_layer = nn.Sequential(
256
+ nn.Linear(hidden_dim + (hidden_dim // 2), hidden_dim),
257
+ nn.BatchNorm1d(hidden_dim),
258
+ nn.ReLU(),
259
+ nn.Dropout(dropout/2)
260
+ )
261
+
262
+ # Bidirectional LSTM
263
+ self.lstm = nn.LSTM(
264
+ input_size=hidden_dim,
265
+ hidden_size=hidden_dim,
266
+ num_layers=num_layers,
267
+ batch_first=True,
268
+ dropout=dropout if num_layers > 1 else 0,
269
+ bidirectional=True
270
+ )
271
+
272
+ # GRU for additional temporal features
273
+ self.gru = nn.GRU(
274
+ input_size=hidden_dim * 2,
275
+ hidden_size=hidden_dim,
276
+ num_layers=1,
277
+ batch_first=True,
278
+ bidirectional=True
279
+ )
280
+
281
+ # Batch normalization
282
+ self.lstm_bn = nn.BatchNorm1d(hidden_dim * 2)
283
+ self.gru_bn = nn.BatchNorm1d(hidden_dim * 2)
284
+
285
+ # Multi-head attention
286
+ self.multihead_attn = nn.MultiheadAttention(
287
+ embed_dim=hidden_dim * 2,
288
+ num_heads=4,
289
+ dropout=dropout,
290
+ batch_first=True
291
+ )
292
+
293
+ # Attention mechanism
294
+ self.attention = nn.Sequential(
295
+ nn.Linear(hidden_dim * 2, hidden_dim),
296
+ nn.Tanh(),
297
+ nn.Linear(hidden_dim, 1),
298
+ nn.Softmax(dim=1)
299
+ )
300
+
301
+ # Classifier
302
+ self.classifier = nn.Sequential(
303
+ nn.Linear(hidden_dim * 4, hidden_dim * 2),
304
+ nn.BatchNorm1d(hidden_dim * 2),
305
+ nn.ReLU(),
306
+ nn.Dropout(dropout),
307
+ nn.Linear(hidden_dim * 2, hidden_dim),
308
+ nn.BatchNorm1d(hidden_dim),
309
+ nn.ReLU(),
310
+ nn.Dropout(dropout/2),
311
+ nn.Linear(hidden_dim, num_classes)
312
+ )
313
+
314
+ self._init_weights()
315
+
316
+ def _init_weights(self):
317
+ """Initialize model weights"""
318
+ for m in self.modules():
319
+ if isinstance(m, nn.Linear):
320
+ nn.init.xavier_uniform_(m.weight)
321
+ if m.bias is not None:
322
+ nn.init.zeros_(m.bias)
323
+ elif isinstance(m, (nn.LSTM, nn.GRU)):
324
+ for name, param in m.named_parameters():
325
+ if 'weight' in name:
326
+ nn.init.orthogonal_(param)
327
+ elif 'bias' in name:
328
+ nn.init.zeros_(param)
329
+
330
+ def forward(self, keypoints, flow=None):
331
+ """Forward pass"""
332
+ batch_size, seq_len, _ = keypoints.size()
333
+
334
+ # Process keypoint features
335
+ kp_reshaped = keypoints.reshape(-1, keypoints.size(-1))
336
+
337
+ # First layer
338
+ kp_projected = self.keypoint_projection[0](kp_reshaped)
339
+ kp_projected = kp_projected.reshape(batch_size, seq_len, -1)
340
+ kp_projected = kp_projected.transpose(1, 2)
341
+ kp_projected = self.keypoint_projection[1](kp_projected)
342
+ kp_projected = kp_projected.transpose(1, 2)
343
+ kp_projected = self.keypoint_projection[2](kp_projected)
344
+ kp_projected = self.keypoint_projection[3](kp_projected)
345
+
346
+ # Second layer
347
+ kp_projected_reshaped = kp_projected.reshape(-1, kp_projected.size(-1))
348
+ kp_projected = self.keypoint_projection[4](kp_projected_reshaped)
349
+ kp_projected = kp_projected.reshape(batch_size, seq_len, -1)
350
+ kp_projected = kp_projected.transpose(1, 2)
351
+ kp_projected = self.keypoint_projection[5](kp_projected)
352
+ kp_projected = kp_projected.transpose(1, 2)
353
+ kp_projected = self.keypoint_projection[6](kp_projected)
354
+ kp_projected = self.keypoint_projection[7](kp_projected)
355
+
356
+ # Process flow features if provided
357
+ if flow is not None:
358
+ flow_reshaped = flow.reshape(-1, flow.size(-1))
359
+
360
+ # First layer
361
+ flow_projected = self.flow_projection[0](flow_reshaped)
362
+ flow_projected = flow_projected.reshape(batch_size, seq_len, -1)
363
+ flow_projected = flow_projected.transpose(1, 2)
364
+ flow_projected = self.flow_projection[1](flow_projected)
365
+ flow_projected = flow_projected.transpose(1, 2)
366
+ flow_projected = self.flow_projection[2](flow_projected)
367
+ flow_projected = self.flow_projection[3](flow_projected)
368
+
369
+ # Second layer
370
+ flow_projected_reshaped = flow_projected.reshape(-1, flow_projected.size(-1))
371
+ flow_projected = self.flow_projection[4](flow_projected_reshaped)
372
+ flow_projected = flow_projected.reshape(batch_size, seq_len, -1)
373
+ flow_projected = flow_projected.transpose(1, 2)
374
+ flow_projected = self.flow_projection[5](flow_projected)
375
+ flow_projected = flow_projected.transpose(1, 2)
376
+ flow_projected = self.flow_projection[6](flow_projected)
377
+ flow_projected = self.flow_projection[7](flow_projected)
378
+
379
+ # Feature fusion
380
+ combined_features = torch.cat([kp_projected, flow_projected], dim=2)
381
+
382
+ combined_reshaped = combined_features.reshape(-1, combined_features.size(-1))
383
+ fused_features = self.fusion_layer[0](combined_reshaped)
384
+ fused_features = fused_features.reshape(batch_size, seq_len, -1)
385
+ fused_features = fused_features.transpose(1, 2)
386
+ fused_features = self.fusion_layer[1](fused_features)
387
+ fused_features = fused_features.transpose(1, 2)
388
+ fused_features = self.fusion_layer[2](fused_features)
389
+ fused_features = self.fusion_layer[3](fused_features)
390
+
391
+ x_projected = fused_features
392
+ else:
393
+ x_projected = kp_projected
394
+
395
+ # Residual connection
396
+ x_residual = x_projected
397
+
398
+ # LSTM processing
399
+ lstm_out, _ = self.lstm(x_projected)
400
+
401
+ # Residual connection
402
+ x_residual_expanded = torch.cat([x_residual, x_residual], dim=2)
403
+ lstm_out_with_residual = lstm_out + x_residual_expanded
404
+
405
+ # BatchNorm for LSTM output
406
+ lstm_out_bn = lstm_out_with_residual.transpose(1, 2)
407
+ lstm_out_bn = self.lstm_bn(lstm_out_bn)
408
+ lstm_out = lstm_out_bn.transpose(1, 2)
409
+
410
+ # GRU processing
411
+ gru_out, _ = self.gru(lstm_out)
412
+
413
+ # BatchNorm for GRU output
414
+ gru_out_bn = gru_out.transpose(1, 2)
415
+ gru_out_bn = self.gru_bn(gru_out_bn)
416
+ gru_out = gru_out_bn.transpose(1, 2)
417
+
418
+ # Multi-head attention
419
+ attn_output, _ = self.multihead_attn(lstm_out, lstm_out, lstm_out)
420
+
421
+ # Traditional attention
422
+ attention_weights = self.attention(gru_out)
423
+ context_gru = torch.bmm(gru_out.transpose(1, 2), attention_weights)
424
+ context_gru = context_gru.squeeze(-1)
425
+
426
+ attention_weights_attn = self.attention(attn_output)
427
+ context_attn = torch.bmm(attn_output.transpose(1, 2), attention_weights_attn)
428
+ context_attn = context_attn.squeeze(-1)
429
+
430
+ # Combine contexts
431
+ combined_context = torch.cat([context_gru, context_attn], dim=1)
432
+
433
+ # Final classification
434
+ output = self.classifier(combined_context)
435
+
436
+ return output
437
+
438
+ class RealtimeSignPredictor:
439
+ def __init__(self, model_path, config_path, sequence_length=50, confidence_threshold=0.5, use_segmentation=True):
440
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
441
+ self.sequence_length = sequence_length
442
+ self.confidence_threshold = confidence_threshold
443
+
444
+ # Load configuration and label mapping
445
+ with open(config_path, 'r') as f:
446
+ config = json.load(f)
447
+
448
+ self.label_mapping = config['label_mapping']
449
+ self.idx_to_label = {int(k): v for k, v in self.label_mapping.items()}
450
+
451
+ # Initialize model
452
+ self.model = SignLanguageModel(
453
+ input_dim=225, # keypoint dimension
454
+ hidden_dim=256,
455
+ num_layers=2,
456
+ num_classes=len(self.label_mapping),
457
+ dropout=0.5,
458
+ flow_dim=10
459
+ )
460
+
461
+ # Load trained weights
462
+ checkpoint = torch.load(model_path, map_location=self.device)
463
+ if 'model_state_dict' in checkpoint:
464
+ self.model.load_state_dict(checkpoint['model_state_dict'])
465
+ else:
466
+ self.model.load_state_dict(checkpoint)
467
+
468
+ self.model.to(self.device)
469
+ self.model.eval()
470
+
471
+ # Initialize feature extractor with segmentation
472
+ self.feature_extractor = FeatureExtractor(use_segmentation=use_segmentation)
473
+
474
+ # Initialize sequences for storing features
475
+ self.keypoint_sequence = deque(maxlen=sequence_length)
476
+ self.flow_sequence = deque(maxlen=sequence_length)
477
+
478
+ # Variables for optical flow
479
+ self.prev_frame = None
480
+ self.prev_mask = None
481
+
482
+ print(f"Model loaded successfully. Using device: {self.device}")
483
+ print(f"Recognized classes: {list(self.idx_to_label.values())}")
484
+
485
+ def _linear_interpolate_sequence(self, data, target_length):
486
+ """Linear interpolation to adjust sequence length"""
487
+ if len(data) == target_length:
488
+ return np.array(data)
489
+
490
+ data = np.array(data)
491
+ original_length = len(data)
492
+ feature_dim = data.shape[1]
493
+
494
+ interpolated_data = np.zeros((target_length, feature_dim))
495
+
496
+ for dim in range(feature_dim):
497
+ original_indices = np.linspace(0, original_length - 1, original_length)
498
+ target_indices = np.linspace(0, original_length - 1, target_length)
499
+ interpolated_data[:, dim] = np.interp(target_indices, original_indices, data[:, dim])
500
+
501
+ return interpolated_data
502
+
503
+ def process_frame(self, frame):
504
+ """Process a single frame and extract features with segmentation"""
505
+ # Apply segmentation mask first
506
+ segmented_frame, seg_mask = self.feature_extractor.apply_segmentation_mask(frame)
507
+
508
+ # Convert to RGB for MediaPipe
509
+ frame_rgb = cv2.cvtColor(segmented_frame, cv2.COLOR_BGR2RGB)
510
+ frame_rgb.flags.writeable = False
511
+
512
+ # Process with MediaPipe
513
+ with self.feature_extractor.mp_holistic.Holistic(
514
+ min_detection_confidence=0.5,
515
+ min_tracking_confidence=0.5,
516
+ model_complexity=1) as holistic:
517
+
518
+ results = holistic.process(frame_rgb)
519
+
520
+ frame_rgb.flags.writeable = True
521
+
522
+ # Extract keypoints
523
+ keypoints = self.feature_extractor.extract_pose_keypoints(segmented_frame, results)
524
+
525
+ # Create enhanced hand mask with segmentation
526
+ hand_mask = self.feature_extractor.create_enhanced_hand_mask(
527
+ segmented_frame,
528
+ results.left_hand_landmarks,
529
+ results.right_hand_landmarks,
530
+ results.pose_landmarks,
531
+ seg_mask
532
+ )
533
+
534
+ # Calculate optical flow on segmented frame
535
+ flow_features = np.zeros(10, dtype=np.float16)
536
+ if self.prev_frame is not None and self.prev_mask is not None:
537
+ flow_features = self.feature_extractor.compute_regional_optical_flow(
538
+ self.prev_frame, segmented_frame, hand_mask, downscale=0.5
539
+ )
540
+
541
+ # Update previous frame and mask
542
+ self.prev_frame = segmented_frame.copy()
543
+ self.prev_mask = hand_mask
544
+
545
+ # Add to sequences
546
+ self.keypoint_sequence.append(keypoints)
547
+ self.flow_sequence.append(flow_features)
548
+
549
+ return results, keypoints, flow_features
550
+
551
+ def predict(self):
552
+ """Make prediction based on current sequence"""
553
+ if len(self.keypoint_sequence) < self.sequence_length:
554
+ return None, 0.0
555
+
556
+ # Convert sequences to arrays and interpolate
557
+ keypoints_array = self._linear_interpolate_sequence(
558
+ list(self.keypoint_sequence), self.sequence_length
559
+ )
560
+ flow_array = self._linear_interpolate_sequence(
561
+ list(self.flow_sequence), self.sequence_length
562
+ )
563
+
564
+ # Convert to tensors
565
+ keypoints_tensor = torch.FloatTensor(keypoints_array).unsqueeze(0).to(self.device)
566
+ flow_tensor = torch.FloatTensor(flow_array).unsqueeze(0).to(self.device)
567
+
568
+ # Make prediction
569
+ with torch.no_grad():
570
+ outputs = self.model(keypoints_tensor, flow_tensor)
571
+ probabilities = F.softmax(outputs, dim=1)
572
+
573
+ max_prob, max_idx = torch.max(probabilities, 1)
574
+ predicted_label = self.idx_to_label[max_idx.item()]
575
+ confidence = max_prob.item()
576
+
577
+ return predicted_label, confidence
578
+
579
+ def get_top_predictions(self, top_k=3):
580
+ """Get top-k predictions"""
581
+ if len(self.keypoint_sequence) < self.sequence_length:
582
+ return []
583
+
584
+ # Convert sequences to arrays and interpolate
585
+ keypoints_array = self._linear_interpolate_sequence(
586
+ list(self.keypoint_sequence), self.sequence_length
587
+ )
588
+ flow_array = self._linear_interpolate_sequence(
589
+ list(self.flow_sequence), self.sequence_length
590
+ )
591
+
592
+ # Convert to tensors
593
+ keypoints_tensor = torch.FloatTensor(keypoints_array).unsqueeze(0).to(self.device)
594
+ flow_tensor = torch.FloatTensor(flow_array).unsqueeze(0).to(self.device)
595
+
596
+ # Make prediction
597
+ with torch.no_grad():
598
+ outputs = self.model(keypoints_tensor, flow_tensor)
599
+ probabilities = F.softmax(outputs, dim=1)
600
+
601
+ top_probs, top_indices = torch.topk(probabilities, k=min(top_k, len(self.idx_to_label)))
602
+
603
+ predictions = []
604
+ for i in range(top_indices.size(1)):
605
+ idx = top_indices[0, i].item()
606
+ prob = top_probs[0, i].item()
607
+ label = self.idx_to_label[idx]
608
+ predictions.append((label, prob))
609
+
610
+ return predictions
611
+
612
+ def draw_landmarks(self, frame, results):
613
+ """Draw MediaPipe landmarks on frame"""
614
+ if results.left_hand_landmarks:
615
+ self.feature_extractor.mp_drawing.draw_landmarks(
616
+ frame, results.left_hand_landmarks,
617
+ self.feature_extractor.mp_holistic.HAND_CONNECTIONS,
618
+ self.feature_extractor.mp_drawing_styles.get_default_hand_landmarks_style(),
619
+ self.feature_extractor.mp_drawing_styles.get_default_hand_connections_style()
620
+ )
621
+
622
+ if results.right_hand_landmarks:
623
+ self.feature_extractor.mp_drawing.draw_landmarks(
624
+ frame, results.right_hand_landmarks,
625
+ self.feature_extractor.mp_holistic.HAND_CONNECTIONS,
626
+ self.feature_extractor.mp_drawing_styles.get_default_hand_landmarks_style(),
627
+ self.feature_extractor.mp_drawing_styles.get_default_hand_connections_style()
628
+ )
629
+
630
+ if results.pose_landmarks:
631
+ self.feature_extractor.mp_drawing.draw_landmarks(
632
+ frame, results.pose_landmarks,
633
+ self.feature_extractor.mp_holistic.POSE_CONNECTIONS,
634
+ self.feature_extractor.mp_drawing_styles.get_default_pose_landmarks_style()
635
+ )
636
+
637
+ return frame
638
+
639
+ class SingleSignPredictor:
640
+ def __init__(self, model_path, config_path, sequence_length=50, recording_duration=4.0, use_segmentation=True):
641
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
642
+ self.sequence_length = sequence_length
643
+ self.recording_duration = recording_duration # seconds to record each sign
644
+
645
+ # Load configuration and label mapping
646
+ with open(config_path, 'r') as f:
647
+ config = json.load(f)
648
+
649
+ self.label_mapping = config['label_mapping']
650
+ self.idx_to_label = {int(k): v for k, v in self.label_mapping.items()}
651
+
652
+ # Initialize model
653
+ self.model = SignLanguageModel(
654
+ input_dim=225, # keypoint dimension
655
+ hidden_dim=256,
656
+ num_layers=2,
657
+ num_classes=len(self.label_mapping),
658
+ dropout=0.5,
659
+ flow_dim=10
660
+ )
661
+
662
+ # Load trained weights
663
+ checkpoint = torch.load(model_path, map_location=self.device)
664
+ if 'model_state_dict' in checkpoint:
665
+ self.model.load_state_dict(checkpoint['model_state_dict'])
666
+ else:
667
+ self.model.load_state_dict(checkpoint)
668
+
669
+ self.model.to(self.device)
670
+ self.model.eval()
671
+
672
+ # Initialize feature extractor with segmentation
673
+ self.feature_extractor = FeatureExtractor(use_segmentation=use_segmentation)
674
+
675
+ # Recording state
676
+ self.is_recording = False
677
+ self.recording_start_time = None
678
+ self.recorded_keypoints = []
679
+ self.recorded_flow = []
680
+ self.prev_frame = None
681
+ self.prev_mask = None
682
+
683
+ # Results
684
+ self.last_prediction = None
685
+ self.last_confidence = 0.0
686
+ self.last_top_predictions = []
687
+
688
+ print(f"Model loaded successfully. Using device: {self.device}")
689
+ print(f"Recording duration: {self.recording_duration} seconds")
690
+ print(f"Recognized classes: {list(self.idx_to_label.values())}")
691
+
692
+ def _linear_interpolate_sequence(self, data, target_length):
693
+ """Linear interpolation to adjust sequence length"""
694
+ if len(data) == target_length:
695
+ return np.array(data)
696
+
697
+ data = np.array(data)
698
+ original_length = len(data)
699
+ feature_dim = data.shape[1]
700
+
701
+ interpolated_data = np.zeros((target_length, feature_dim))
702
+
703
+ for dim in range(feature_dim):
704
+ original_indices = np.linspace(0, original_length - 1, original_length)
705
+ target_indices = np.linspace(0, original_length - 1, target_length)
706
+ interpolated_data[:, dim] = np.interp(target_indices, original_indices, data[:, dim])
707
+
708
+ return interpolated_data
709
+
710
+ def start_recording(self):
711
+ """Start recording a new sign"""
712
+ self.is_recording = True
713
+ self.recording_start_time = time.time()
714
+ self.recorded_keypoints = []
715
+ self.recorded_flow = []
716
+ self.prev_frame = None
717
+ self.prev_mask = None
718
+ print("Recording started...")
719
+
720
+ def stop_recording(self):
721
+ """Stop recording and make prediction"""
722
+ if not self.is_recording:
723
+ return
724
+
725
+ self.is_recording = False
726
+ print(f"Recording stopped. Collected {len(self.recorded_keypoints)} frames")
727
+
728
+ if len(self.recorded_keypoints) < 10: # Need minimum frames
729
+ print("Not enough frames for prediction")
730
+ self.last_prediction = "Not enough data"
731
+ self.last_confidence = 0.0
732
+ self.last_top_predictions = []
733
+ return
734
+
735
+ # Interpolate to target length
736
+ keypoints_array = self._linear_interpolate_sequence(
737
+ self.recorded_keypoints, self.sequence_length
738
+ )
739
+ flow_array = self._linear_interpolate_sequence(
740
+ self.recorded_flow, self.sequence_length
741
+ )
742
+
743
+ # Convert to tensors
744
+ keypoints_tensor = torch.FloatTensor(keypoints_array).unsqueeze(0).to(self.device)
745
+ flow_tensor = torch.FloatTensor(flow_array).unsqueeze(0).to(self.device)
746
+
747
+ # Make prediction
748
+ with torch.no_grad():
749
+ outputs = self.model(keypoints_tensor, flow_tensor)
750
+ probabilities = F.softmax(outputs, dim=1)
751
+
752
+ # Get top-5 predictions
753
+ top_probs, top_indices = torch.topk(probabilities, k=min(5, len(self.idx_to_label)))
754
+
755
+ predictions = []
756
+ for i in range(top_indices.size(1)):
757
+ idx = top_indices[0, i].item()
758
+ prob = top_probs[0, i].item()
759
+ label = self.idx_to_label[idx]
760
+ predictions.append((label, prob))
761
+
762
+ # Store results
763
+ self.last_prediction = predictions[0][0]
764
+ self.last_confidence = predictions[0][1]
765
+ self.last_top_predictions = predictions
766
+
767
+ print(f"Prediction: {self.last_prediction} (confidence: {self.last_confidence:.3f})")
768
+
769
+ def process_frame(self, frame):
770
+ """Process a single frame with segmentation"""
771
+ # Apply segmentation mask first
772
+ segmented_frame, seg_mask = self.feature_extractor.apply_segmentation_mask(frame)
773
+
774
+ # Convert to RGB for MediaPipe
775
+ frame_rgb = cv2.cvtColor(segmented_frame, cv2.COLOR_BGR2RGB)
776
+ frame_rgb.flags.writeable = False
777
+
778
+ # Process with MediaPipe
779
+ with self.feature_extractor.mp_holistic.Holistic(
780
+ min_detection_confidence=0.5,
781
+ min_tracking_confidence=0.5,
782
+ model_complexity=1) as holistic:
783
+
784
+ results = holistic.process(frame_rgb)
785
+
786
+ frame_rgb.flags.writeable = True
787
+
788
+ # Extract keypoints
789
+ keypoints = self.feature_extractor.extract_pose_keypoints(segmented_frame, results)
790
+
791
+ # Create enhanced hand mask with segmentation
792
+ hand_mask = self.feature_extractor.create_enhanced_hand_mask(
793
+ segmented_frame,
794
+ results.left_hand_landmarks,
795
+ results.right_hand_landmarks,
796
+ results.pose_landmarks,
797
+ seg_mask
798
+ )
799
+
800
+ # Calculate optical flow on segmented frame
801
+ flow_features = np.zeros(10, dtype=np.float16)
802
+ if self.prev_frame is not None and self.prev_mask is not None:
803
+ flow_features = self.feature_extractor.compute_regional_optical_flow(
804
+ self.prev_frame, segmented_frame, hand_mask, downscale=0.5
805
+ )
806
+
807
+ # If recording, store the features
808
+ if self.is_recording:
809
+ self.recorded_keypoints.append(keypoints)
810
+ self.recorded_flow.append(flow_features)
811
+
812
+ # Check if recording duration is reached
813
+ if time.time() - self.recording_start_time >= self.recording_duration:
814
+ self.stop_recording()
815
+
816
+ # Update previous frame and mask
817
+ self.prev_frame = segmented_frame.copy()
818
+ self.prev_mask = hand_mask
819
+
820
+ return results
821
+
822
+ def draw_landmarks(self, frame, results):
823
+ """Draw MediaPipe landmarks on frame"""
824
+ if results.left_hand_landmarks:
825
+ self.feature_extractor.mp_drawing.draw_landmarks(
826
+ frame, results.left_hand_landmarks,
827
+ self.feature_extractor.mp_holistic.HAND_CONNECTIONS,
828
+ self.feature_extractor.mp_drawing_styles.get_default_hand_landmarks_style(),
829
+ self.feature_extractor.mp_drawing_styles.get_default_hand_connections_style()
830
+ )
831
+
832
+ if results.right_hand_landmarks:
833
+ self.feature_extractor.mp_drawing.draw_landmarks(
834
+ frame, results.right_hand_landmarks,
835
+ self.feature_extractor.mp_holistic.HAND_CONNECTIONS,
836
+ self.feature_extractor.mp_drawing_styles.get_default_hand_landmarks_style(),
837
+ self.feature_extractor.mp_drawing_styles.get_default_hand_connections_style()
838
+ )
839
+
840
+ if results.pose_landmarks:
841
+ self.feature_extractor.mp_drawing.draw_landmarks(
842
+ frame, results.pose_landmarks,
843
+ self.feature_extractor.mp_holistic.POSE_CONNECTIONS,
844
+ self.feature_extractor.mp_drawing_styles.get_default_pose_landmarks_style()
845
+ )
846
+
847
+ return frame
848
+
849
+ def main():
850
+ parser = argparse.ArgumentParser(description='Sign Language Recognition - Choose Mode')
851
+ parser.add_argument('--model', default='tsflow/models/best_model.pt',
852
+ help='Path to trained model')
853
+ parser.add_argument('--config', default='tsflow/results/test_results.json',
854
+ help='Path to config file with label mappings')
855
+ parser.add_argument('--camera', type=int, default=0,
856
+ help='Camera index')
857
+ parser.add_argument('--sequence_length', type=int, default=50,
858
+ help='Sequence length for prediction')
859
+ parser.add_argument('--confidence_threshold', type=float, default=0.5,
860
+ help='Confidence threshold for predictions')
861
+ parser.add_argument('--mode', choices=['realtime', 'single'], default='single',
862
+ help='Recognition mode: realtime (continuous) or single (one-by-one)')
863
+ parser.add_argument('--recording_duration', type=float, default=4.0,
864
+ help='Duration to record each sign in single mode (seconds)')
865
+ parser.add_argument('--use_segmentation', action='store_true', default=True,
866
+ help='Enable human segmentation for background removal')
867
+ parser.add_argument('--no_segmentation', action='store_true', default=False,
868
+ help='Disable human segmentation')
869
+
870
+ args = parser.parse_args()
871
+
872
+ # Check if model and config files exist
873
+ if not os.path.exists(args.model):
874
+ print(f"Model file not found: {args.model}")
875
+ return
876
+
877
+ if not os.path.exists(args.config):
878
+ print(f"Config file not found: {args.config}")
879
+ return
880
+
881
+ # Determine segmentation setting
882
+ use_segmentation = args.use_segmentation and not args.no_segmentation
883
+
884
+ if args.mode == 'single':
885
+ # Single sign mode
886
+ predictor = SingleSignPredictor(
887
+ model_path=args.model,
888
+ config_path=args.config,
889
+ sequence_length=args.sequence_length,
890
+ recording_duration=args.recording_duration,
891
+ use_segmentation=use_segmentation
892
+ )
893
+
894
+ # Initialize camera
895
+ cap = cv2.VideoCapture(args.camera)
896
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
897
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
898
+ cap.set(cv2.CAP_PROP_FPS, 30)
899
+
900
+ if not cap.isOpened():
901
+ print(f"Cannot open camera {args.camera}")
902
+ return
903
+
904
+ print("\n" + "="*60)
905
+ print("Single Sign Language Recognition")
906
+ print("="*60)
907
+ print("Controls:")
908
+ print(" SPACE: Start/Stop recording a sign")
909
+ print(" 'c': Clear last prediction")
910
+ print(" 'q': Quit")
911
+ print("="*60)
912
+
913
+ # FPS calculation
914
+ fps_counter = 0
915
+ fps_start_time = time.time()
916
+ current_fps = 0
917
+
918
+ while True:
919
+ ret, frame = cap.read()
920
+ if not ret:
921
+ print("Failed to read frame from camera")
922
+ break
923
+
924
+ # Mirror frame horizontally
925
+ frame = cv2.flip(frame, 1)
926
+
927
+ # Process frame
928
+ results = predictor.process_frame(frame)
929
+
930
+ # Draw landmarks
931
+ frame = predictor.draw_landmarks(frame, results)
932
+
933
+ # Calculate FPS
934
+ fps_counter += 1
935
+ if fps_counter % 30 == 0:
936
+ fps_end_time = time.time()
937
+ current_fps = 30 / (fps_end_time - fps_start_time)
938
+ fps_start_time = fps_end_time
939
+
940
+ # Draw UI
941
+ h, w, _ = frame.shape
942
+
943
+ # Main info panel
944
+ cv2.rectangle(frame, (10, 10), (w-10, 200), (0, 0, 0), -1)
945
+ cv2.rectangle(frame, (10, 10), (w-10, 200), (255, 255, 255), 2)
946
+
947
+ # FPS
948
+ cv2.putText(frame, f"FPS: {current_fps:.1f}", (20, 35),
949
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
950
+
951
+ # Recording status
952
+ if predictor.is_recording:
953
+ elapsed = time.time() - predictor.recording_start_time
954
+ remaining = max(0, args.recording_duration - elapsed)
955
+ progress = elapsed / args.recording_duration
956
+
957
+ # Recording indicator
958
+ cv2.putText(frame, "RECORDING", (20, 65),
959
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
960
+ cv2.putText(frame, f"Time: {remaining:.1f}s", (20, 90),
961
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
962
+
963
+ # Progress bar
964
+ bar_width = w - 40
965
+ bar_height = 15
966
+ cv2.rectangle(frame, (20, 100), (20 + bar_width, 100 + bar_height), (100, 100, 100), -1)
967
+ cv2.rectangle(frame, (20, 100), (20 + int(bar_width * progress), 100 + bar_height), (0, 0, 255), -1)
968
+
969
+ # Recording circle (blinking effect)
970
+ if int(elapsed * 4) % 2 == 0: # Blink every 0.25 seconds
971
+ cv2.circle(frame, (w - 40, 40), 15, (0, 0, 255), -1)
972
+ else:
973
+ cv2.putText(frame, "READY - Press SPACE to record", (20, 65),
974
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
975
+
976
+ # Show last prediction if available
977
+ if predictor.last_prediction and predictor.last_prediction != "Not enough data":
978
+ y_offset = 100
979
+ cv2.putText(frame, "Last Prediction:", (20, y_offset),
980
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
981
+
982
+ # Top prediction
983
+ cv2.putText(frame, f"1. {predictor.last_prediction}: {predictor.last_confidence:.3f}",
984
+ (20, y_offset + 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
985
+
986
+ # Top 5 predictions
987
+ for i, (label, conf) in enumerate(predictor.last_top_predictions[1:4], 2):
988
+ cv2.putText(frame, f"{i}. {label}: {conf:.3f}",
989
+ (20, y_offset + 25 * i), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
990
+
991
+ # Instructions
992
+ cv2.putText(frame, "SPACE: Record | C: Clear | Q: Quit", (20, h-20),
993
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
994
+
995
+ # Show frame
996
+ cv2.imshow('Single Sign Language Recognition', frame)
997
+
998
+ # Handle key presses
999
+ key = cv2.waitKey(1) & 0xFF
1000
+ if key == ord('q'):
1001
+ break
1002
+ elif key == ord(' '): # Space bar
1003
+ if not predictor.is_recording:
1004
+ predictor.start_recording()
1005
+ else:
1006
+ predictor.stop_recording()
1007
+ elif key == ord('c'):
1008
+ predictor.last_prediction = None
1009
+ predictor.last_confidence = 0.0
1010
+ predictor.last_top_predictions = []
1011
+ print("Prediction cleared")
1012
+
1013
+ else:
1014
+ # Realtime mode
1015
+ predictor = RealtimeSignPredictor(
1016
+ model_path=args.model,
1017
+ config_path=args.config,
1018
+ sequence_length=args.sequence_length,
1019
+ confidence_threshold=args.confidence_threshold,
1020
+ use_segmentation=use_segmentation
1021
+ )
1022
+
1023
+ # Initialize camera
1024
+ cap = cv2.VideoCapture(args.camera)
1025
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
1026
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
1027
+ cap.set(cv2.CAP_PROP_FPS, 30)
1028
+
1029
+ if not cap.isOpened():
1030
+ print(f"Cannot open camera {args.camera}")
1031
+ return
1032
+
1033
+ print("Starting real-time sign language recognition...")
1034
+ print("Press 'q' to quit, 'r' to reset sequence")
1035
+
1036
+ # FPS calculation
1037
+ fps_counter = 0
1038
+ fps_start_time = time.time()
1039
+ current_fps = 0
1040
+
1041
+ while True:
1042
+ ret, frame = cap.read()
1043
+ if not ret:
1044
+ print("Failed to read frame from camera")
1045
+ break
1046
+
1047
+ # Mirror frame horizontally
1048
+ frame = cv2.flip(frame, 1)
1049
+
1050
+ # Process frame
1051
+ results, keypoints, flow_features = predictor.process_frame(frame)
1052
+
1053
+ # Draw landmarks
1054
+ frame = predictor.draw_landmarks(frame, results)
1055
+
1056
+ # Get predictions
1057
+ top_predictions = predictor.get_top_predictions(top_k=3)
1058
+
1059
+ # Calculate FPS
1060
+ fps_counter += 1
1061
+ if fps_counter % 30 == 0:
1062
+ fps_end_time = time.time()
1063
+ current_fps = 30 / (fps_end_time - fps_start_time)
1064
+ fps_start_time = fps_end_time
1065
+
1066
+ # Draw information on frame
1067
+ h, w, _ = frame.shape
1068
+
1069
+ # Background for text
1070
+ cv2.rectangle(frame, (10, 10), (w-10, 150), (0, 0, 0), -1)
1071
+ cv2.rectangle(frame, (10, 10), (w-10, 150), (255, 255, 255), 2)
1072
+
1073
+ # FPS
1074
+ cv2.putText(frame, f"FPS: {current_fps:.1f}", (20, 35),
1075
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
1076
+
1077
+ # Sequence progress
1078
+ progress = len(predictor.keypoint_sequence) / args.sequence_length
1079
+ cv2.putText(frame, f"Sequence: {len(predictor.keypoint_sequence)}/{args.sequence_length}",
1080
+ (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
1081
+
1082
+ # Progress bar
1083
+ bar_width = w - 40
1084
+ bar_height = 10
1085
+ cv2.rectangle(frame, (20, 70), (20 + bar_width, 70 + bar_height), (100, 100, 100), -1)
1086
+ cv2.rectangle(frame, (20, 70), (20 + int(bar_width * progress), 70 + bar_height), (0, 255, 0), -1)
1087
+
1088
+ # Predictions
1089
+ y_offset = 100
1090
+ if top_predictions:
1091
+ for i, (label, confidence) in enumerate(top_predictions):
1092
+ color = (0, 255, 0) if confidence > args.confidence_threshold else (0, 255, 255)
1093
+ text = f"{i+1}. {label}: {confidence:.2f}"
1094
+ cv2.putText(frame, text, (20, y_offset + i * 25),
1095
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
1096
+ else:
1097
+ cv2.putText(frame, "Collecting frames...", (20, y_offset),
1098
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
1099
+
1100
+ # Instructions
1101
+ cv2.putText(frame, "Press 'q' to quit, 'r' to reset", (20, h-20),
1102
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
1103
+
1104
+ # Show frame
1105
+ cv2.imshow('Real-time Sign Language Recognition', frame)
1106
+
1107
+ # Handle key presses
1108
+ key = cv2.waitKey(1) & 0xFF
1109
+ if key == ord('q'):
1110
+ break
1111
+ elif key == ord('r'):
1112
+ predictor.keypoint_sequence.clear()
1113
+ predictor.flow_sequence.clear()
1114
+ print("Sequence reset")
1115
+
1116
+ # Cleanup
1117
+ cap.release()
1118
+ cv2.destroyAllWindows()
1119
+ print("Recognition stopped")
1120
+
1121
+ if __name__ == "__main__":
1122
+ main()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ torch>=2.0.0
3
+ torchvision>=0.15.0
4
+ opencv-python>=4.8.0
5
+ mediapipe>=0.10.0
6
+ numpy>=1.24.0
7
+ Pillow>=9.5.0
8
+ scipy>=1.10.0
tsflow/confusion_matrix.png ADDED

Git LFS Details

  • SHA256: 4dcc29ca29a409f5477aaad67c5ef1a85c6bcbfc480c2c69373b4020685bd1fb
  • Pointer size: 130 Bytes
  • Size of remote file: 96.9 kB
tsflow/models/best_model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a7cf5aaac8703304bb97e782400defb59701dc81bcb543700492c6ddb7a4836a
3
+ size 70972747
tsflow/models/training_curves.png ADDED

Git LFS Details

  • SHA256: 1e91159e1e26c210e7d36f24ee07b7e422557a1a74bd559bbfcd58459d3a7c3b
  • Pointer size: 131 Bytes
  • Size of remote file: 109 kB
tsflow/results/test_results.json ADDED
@@ -0,0 +1,1929 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "test_loss": 0.1214595541292638,
3
+ "test_accuracy": 0.9425414364640884,
4
+ "test_f1": 0.9424214749151464,
5
+ "class_weights": [
6
+ 0.9964749813079834,
7
+ 0.9817668795585632,
8
+ 0.9781574606895447,
9
+ 1.0154917240142822,
10
+ 0.9964749813079834,
11
+ 1.0516159534454346,
12
+ 0.9817668795585632,
13
+ 1.0352483987808228,
14
+ 0.9890662431716919,
15
+ 1.0154917240142822,
16
+ 1.0002211332321167,
17
+ 0.9927567839622498,
18
+ 1.0077985525131226,
19
+ 0.9817668795585632,
20
+ 0.9854030609130859,
21
+ 0.9927567839622498,
22
+ 0.9605011940002441,
23
+ 1.0233031511306763,
24
+ 1.0272541046142578,
25
+ 0.9927567839622498,
26
+ 1.0516159534454346,
27
+ 0.9890662431716919,
28
+ 1.0116305351257324,
29
+ 0.9781574606895447,
30
+ 1.0077985525131226,
31
+ 0.9781574606895447,
32
+ 0.9745744466781616,
33
+ 0.9817668795585632,
34
+ 1.0039955377578735,
35
+ 1.0039955377578735,
36
+ 0.9817668795585632,
37
+ 1.039292335510254,
38
+ 1.0474756956100464,
39
+ 0.9639812707901001
40
+ ],
41
+ "label_mapping": {
42
+ "0": "again",
43
+ "1": "all",
44
+ "2": "apple",
45
+ "3": "bad",
46
+ "4": "bathroom",
47
+ "5": "beautiful",
48
+ "6": "bird",
49
+ "7": "black",
50
+ "8": "blue",
51
+ "9": "book",
52
+ "10": "bored",
53
+ "11": "boy",
54
+ "12": "brother",
55
+ "13": "brown",
56
+ "14": "but",
57
+ "15": "computer",
58
+ "16": "cousin",
59
+ "17": "dance",
60
+ "18": "day",
61
+ "19": "deaf",
62
+ "20": "doctor",
63
+ "21": "dog",
64
+ "22": "draw",
65
+ "23": "drink",
66
+ "24": "eat",
67
+ "25": "english",
68
+ "26": "family",
69
+ "27": "father",
70
+ "28": "fine",
71
+ "29": "finish",
72
+ "30": "fish",
73
+ "31": "forget",
74
+ "32": "friend",
75
+ "33": "girl"
76
+ },
77
+ "config": {
78
+ "dataset_dir": "/kaggle/input/baidts/videos_normalized",
79
+ "train_split": 0.8,
80
+ "val_split": 0.1,
81
+ "frame_sampling": 2,
82
+ "downscale_factor": 0.5,
83
+ "max_videos_per_batch": 15,
84
+ "use_regional_flow": true,
85
+ "augmentation": {
86
+ "enabled": true,
87
+ "types": [
88
+ "flip",
89
+ "rotate",
90
+ "shift",
91
+ "multi"
92
+ ],
93
+ "probability": 0.7,
94
+ "balance_classes": true
95
+ },
96
+ "hidden_dim": 256,
97
+ "num_layers": 2,
98
+ "dropout": 0.5,
99
+ "batch_size": 16,
100
+ "num_epochs": 100,
101
+ "learning_rate": 0.001,
102
+ "device": "cuda",
103
+ "random_seed": 42,
104
+ "save_dir": "models",
105
+ "patience": 15,
106
+ "use_mixup": true,
107
+ "mixup_alpha": 0.2,
108
+ "gradient_clip": 1.0,
109
+ "use_focal_loss": true,
110
+ "focal_loss_gamma": 2.0,
111
+ "use_weight_decay": 0.0001,
112
+ "use_cosine_scheduler": true,
113
+ "warmup_epochs": 5
114
+ },
115
+ "predictions": [
116
+ 11,
117
+ 8,
118
+ 16,
119
+ 1,
120
+ 20,
121
+ 2,
122
+ 2,
123
+ 19,
124
+ 12,
125
+ 20,
126
+ 3,
127
+ 28,
128
+ 16,
129
+ 8,
130
+ 3,
131
+ 11,
132
+ 28,
133
+ 5,
134
+ 8,
135
+ 21,
136
+ 30,
137
+ 10,
138
+ 3,
139
+ 20,
140
+ 28,
141
+ 2,
142
+ 33,
143
+ 6,
144
+ 18,
145
+ 27,
146
+ 3,
147
+ 23,
148
+ 10,
149
+ 16,
150
+ 21,
151
+ 8,
152
+ 27,
153
+ 16,
154
+ 25,
155
+ 1,
156
+ 28,
157
+ 1,
158
+ 31,
159
+ 18,
160
+ 8,
161
+ 24,
162
+ 10,
163
+ 2,
164
+ 16,
165
+ 10,
166
+ 12,
167
+ 26,
168
+ 1,
169
+ 24,
170
+ 18,
171
+ 16,
172
+ 14,
173
+ 3,
174
+ 26,
175
+ 30,
176
+ 5,
177
+ 17,
178
+ 22,
179
+ 19,
180
+ 22,
181
+ 0,
182
+ 11,
183
+ 12,
184
+ 9,
185
+ 31,
186
+ 4,
187
+ 19,
188
+ 31,
189
+ 19,
190
+ 6,
191
+ 13,
192
+ 24,
193
+ 15,
194
+ 7,
195
+ 0,
196
+ 15,
197
+ 21,
198
+ 25,
199
+ 2,
200
+ 26,
201
+ 13,
202
+ 32,
203
+ 29,
204
+ 2,
205
+ 5,
206
+ 15,
207
+ 0,
208
+ 12,
209
+ 27,
210
+ 0,
211
+ 11,
212
+ 25,
213
+ 16,
214
+ 10,
215
+ 25,
216
+ 27,
217
+ 17,
218
+ 13,
219
+ 30,
220
+ 32,
221
+ 29,
222
+ 13,
223
+ 0,
224
+ 17,
225
+ 1,
226
+ 32,
227
+ 5,
228
+ 24,
229
+ 30,
230
+ 6,
231
+ 10,
232
+ 22,
233
+ 2,
234
+ 13,
235
+ 10,
236
+ 7,
237
+ 12,
238
+ 2,
239
+ 15,
240
+ 23,
241
+ 15,
242
+ 24,
243
+ 0,
244
+ 1,
245
+ 7,
246
+ 28,
247
+ 0,
248
+ 1,
249
+ 4,
250
+ 14,
251
+ 8,
252
+ 16,
253
+ 3,
254
+ 5,
255
+ 2,
256
+ 17,
257
+ 25,
258
+ 13,
259
+ 6,
260
+ 29,
261
+ 6,
262
+ 32,
263
+ 29,
264
+ 31,
265
+ 22,
266
+ 18,
267
+ 26,
268
+ 15,
269
+ 7,
270
+ 13,
271
+ 33,
272
+ 23,
273
+ 11,
274
+ 18,
275
+ 28,
276
+ 5,
277
+ 2,
278
+ 2,
279
+ 26,
280
+ 23,
281
+ 17,
282
+ 10,
283
+ 9,
284
+ 27,
285
+ 6,
286
+ 25,
287
+ 30,
288
+ 33,
289
+ 30,
290
+ 11,
291
+ 4,
292
+ 32,
293
+ 30,
294
+ 5,
295
+ 1,
296
+ 6,
297
+ 17,
298
+ 27,
299
+ 1,
300
+ 18,
301
+ 27,
302
+ 16,
303
+ 11,
304
+ 17,
305
+ 7,
306
+ 23,
307
+ 24,
308
+ 28,
309
+ 2,
310
+ 18,
311
+ 26,
312
+ 27,
313
+ 5,
314
+ 33,
315
+ 9,
316
+ 31,
317
+ 2,
318
+ 16,
319
+ 26,
320
+ 9,
321
+ 29,
322
+ 20,
323
+ 26,
324
+ 17,
325
+ 29,
326
+ 18,
327
+ 26,
328
+ 7,
329
+ 4,
330
+ 14,
331
+ 17,
332
+ 0,
333
+ 19,
334
+ 26,
335
+ 24,
336
+ 7,
337
+ 15,
338
+ 5,
339
+ 3,
340
+ 7,
341
+ 7,
342
+ 11,
343
+ 22,
344
+ 31,
345
+ 15,
346
+ 8,
347
+ 0,
348
+ 17,
349
+ 12,
350
+ 33,
351
+ 6,
352
+ 3,
353
+ 30,
354
+ 21,
355
+ 25,
356
+ 22,
357
+ 3,
358
+ 22,
359
+ 19,
360
+ 24,
361
+ 8,
362
+ 10,
363
+ 2,
364
+ 30,
365
+ 11,
366
+ 10,
367
+ 25,
368
+ 18,
369
+ 27,
370
+ 32,
371
+ 13,
372
+ 23,
373
+ 4,
374
+ 25,
375
+ 0,
376
+ 6,
377
+ 13,
378
+ 10,
379
+ 26,
380
+ 14,
381
+ 20,
382
+ 12,
383
+ 30,
384
+ 8,
385
+ 32,
386
+ 11,
387
+ 28,
388
+ 20,
389
+ 11,
390
+ 8,
391
+ 10,
392
+ 9,
393
+ 15,
394
+ 26,
395
+ 25,
396
+ 3,
397
+ 10,
398
+ 10,
399
+ 19,
400
+ 29,
401
+ 22,
402
+ 24,
403
+ 15,
404
+ 31,
405
+ 16,
406
+ 4,
407
+ 28,
408
+ 28,
409
+ 33,
410
+ 5,
411
+ 23,
412
+ 27,
413
+ 32,
414
+ 1,
415
+ 11,
416
+ 15,
417
+ 15,
418
+ 32,
419
+ 19,
420
+ 7,
421
+ 33,
422
+ 32,
423
+ 11,
424
+ 33,
425
+ 20,
426
+ 25,
427
+ 14,
428
+ 9,
429
+ 13,
430
+ 28,
431
+ 23,
432
+ 11,
433
+ 13,
434
+ 23,
435
+ 1,
436
+ 32,
437
+ 11,
438
+ 31,
439
+ 30,
440
+ 30,
441
+ 22,
442
+ 8,
443
+ 16,
444
+ 16,
445
+ 19,
446
+ 17,
447
+ 21,
448
+ 11,
449
+ 26,
450
+ 13,
451
+ 30,
452
+ 30,
453
+ 5,
454
+ 1,
455
+ 29,
456
+ 20,
457
+ 28,
458
+ 23,
459
+ 11,
460
+ 8,
461
+ 30,
462
+ 15,
463
+ 6,
464
+ 27,
465
+ 14,
466
+ 15,
467
+ 7,
468
+ 26,
469
+ 20,
470
+ 27,
471
+ 30,
472
+ 22,
473
+ 32,
474
+ 23,
475
+ 8,
476
+ 29,
477
+ 15,
478
+ 31,
479
+ 33,
480
+ 9,
481
+ 5,
482
+ 9,
483
+ 31,
484
+ 29,
485
+ 12,
486
+ 8,
487
+ 18,
488
+ 3,
489
+ 29,
490
+ 10,
491
+ 27,
492
+ 6,
493
+ 15,
494
+ 7,
495
+ 26,
496
+ 1,
497
+ 2,
498
+ 17,
499
+ 28,
500
+ 27,
501
+ 21,
502
+ 27,
503
+ 24,
504
+ 30,
505
+ 6,
506
+ 16,
507
+ 8,
508
+ 24,
509
+ 4,
510
+ 23,
511
+ 9,
512
+ 31,
513
+ 7,
514
+ 12,
515
+ 0,
516
+ 4,
517
+ 12,
518
+ 1,
519
+ 15,
520
+ 8,
521
+ 32,
522
+ 9,
523
+ 2,
524
+ 31,
525
+ 13,
526
+ 21,
527
+ 14,
528
+ 29,
529
+ 28,
530
+ 25,
531
+ 6,
532
+ 1,
533
+ 3,
534
+ 20,
535
+ 8,
536
+ 30,
537
+ 30,
538
+ 17,
539
+ 4,
540
+ 0,
541
+ 3,
542
+ 25,
543
+ 2,
544
+ 33,
545
+ 30,
546
+ 23,
547
+ 12,
548
+ 17,
549
+ 0,
550
+ 25,
551
+ 25,
552
+ 27,
553
+ 3,
554
+ 31,
555
+ 28,
556
+ 20,
557
+ 7,
558
+ 0,
559
+ 18,
560
+ 14,
561
+ 2,
562
+ 15,
563
+ 28,
564
+ 4,
565
+ 32,
566
+ 22,
567
+ 31,
568
+ 22,
569
+ 3,
570
+ 19,
571
+ 27,
572
+ 14,
573
+ 11,
574
+ 9,
575
+ 21,
576
+ 13,
577
+ 1,
578
+ 18,
579
+ 33,
580
+ 29,
581
+ 13,
582
+ 28,
583
+ 21,
584
+ 14,
585
+ 16,
586
+ 22,
587
+ 15,
588
+ 11,
589
+ 13,
590
+ 5,
591
+ 29,
592
+ 12,
593
+ 12,
594
+ 5,
595
+ 13,
596
+ 10,
597
+ 26,
598
+ 28,
599
+ 33,
600
+ 9,
601
+ 27,
602
+ 25,
603
+ 27,
604
+ 30,
605
+ 27,
606
+ 29,
607
+ 24,
608
+ 18,
609
+ 22,
610
+ 26,
611
+ 4,
612
+ 33,
613
+ 5,
614
+ 22,
615
+ 9,
616
+ 7,
617
+ 7,
618
+ 9,
619
+ 8,
620
+ 33,
621
+ 33,
622
+ 14,
623
+ 23,
624
+ 27,
625
+ 29,
626
+ 5,
627
+ 22,
628
+ 32,
629
+ 22,
630
+ 29,
631
+ 2,
632
+ 14,
633
+ 31,
634
+ 3,
635
+ 14,
636
+ 20,
637
+ 17,
638
+ 12,
639
+ 11,
640
+ 22,
641
+ 12,
642
+ 17,
643
+ 3,
644
+ 18,
645
+ 27,
646
+ 28,
647
+ 17,
648
+ 18,
649
+ 28,
650
+ 14,
651
+ 16,
652
+ 16,
653
+ 30,
654
+ 21,
655
+ 13,
656
+ 10,
657
+ 8,
658
+ 10,
659
+ 33,
660
+ 32,
661
+ 8,
662
+ 14,
663
+ 15,
664
+ 19,
665
+ 23,
666
+ 29,
667
+ 4,
668
+ 22,
669
+ 12,
670
+ 30,
671
+ 31,
672
+ 29,
673
+ 29,
674
+ 27,
675
+ 28,
676
+ 31,
677
+ 11,
678
+ 12,
679
+ 5,
680
+ 31,
681
+ 21,
682
+ 16,
683
+ 26,
684
+ 33,
685
+ 17,
686
+ 20,
687
+ 32,
688
+ 18,
689
+ 11,
690
+ 13,
691
+ 23,
692
+ 0,
693
+ 26,
694
+ 6,
695
+ 12,
696
+ 25,
697
+ 31,
698
+ 27,
699
+ 31,
700
+ 4,
701
+ 21,
702
+ 33,
703
+ 0,
704
+ 2,
705
+ 23,
706
+ 28,
707
+ 33,
708
+ 20,
709
+ 11,
710
+ 21,
711
+ 14,
712
+ 18,
713
+ 9,
714
+ 2,
715
+ 0,
716
+ 23,
717
+ 9,
718
+ 6,
719
+ 19,
720
+ 32,
721
+ 9,
722
+ 32,
723
+ 10,
724
+ 25,
725
+ 12,
726
+ 24,
727
+ 28,
728
+ 20,
729
+ 12,
730
+ 1,
731
+ 24,
732
+ 29,
733
+ 1,
734
+ 11,
735
+ 16,
736
+ 23,
737
+ 33,
738
+ 14,
739
+ 14,
740
+ 27,
741
+ 28,
742
+ 21,
743
+ 8,
744
+ 18,
745
+ 32,
746
+ 2,
747
+ 27,
748
+ 23,
749
+ 21,
750
+ 14,
751
+ 19,
752
+ 11,
753
+ 21,
754
+ 25,
755
+ 16,
756
+ 26,
757
+ 4,
758
+ 10,
759
+ 33,
760
+ 6,
761
+ 2,
762
+ 10,
763
+ 33,
764
+ 13,
765
+ 26,
766
+ 9,
767
+ 30,
768
+ 1,
769
+ 30,
770
+ 19,
771
+ 10,
772
+ 4,
773
+ 5,
774
+ 19,
775
+ 23,
776
+ 21,
777
+ 6,
778
+ 10,
779
+ 21,
780
+ 20,
781
+ 32,
782
+ 12,
783
+ 2,
784
+ 4,
785
+ 16,
786
+ 15,
787
+ 24,
788
+ 21,
789
+ 22,
790
+ 24,
791
+ 14,
792
+ 33,
793
+ 21,
794
+ 11,
795
+ 32,
796
+ 32,
797
+ 1,
798
+ 23,
799
+ 26,
800
+ 9,
801
+ 21,
802
+ 19,
803
+ 6,
804
+ 21,
805
+ 0,
806
+ 2,
807
+ 24,
808
+ 26,
809
+ 3,
810
+ 13,
811
+ 3,
812
+ 17,
813
+ 2,
814
+ 8,
815
+ 31,
816
+ 20,
817
+ 32,
818
+ 9,
819
+ 32,
820
+ 4,
821
+ 8,
822
+ 21,
823
+ 4,
824
+ 12,
825
+ 25,
826
+ 23,
827
+ 5,
828
+ 9,
829
+ 21,
830
+ 3,
831
+ 25,
832
+ 21,
833
+ 27,
834
+ 33,
835
+ 33,
836
+ 31,
837
+ 10,
838
+ 15,
839
+ 29,
840
+ 22,
841
+ 5,
842
+ 24,
843
+ 9,
844
+ 15,
845
+ 33,
846
+ 33,
847
+ 33,
848
+ 17,
849
+ 1,
850
+ 8,
851
+ 14,
852
+ 0,
853
+ 7,
854
+ 20,
855
+ 14,
856
+ 8,
857
+ 1,
858
+ 21,
859
+ 17,
860
+ 4,
861
+ 5,
862
+ 21,
863
+ 3,
864
+ 19,
865
+ 18,
866
+ 9,
867
+ 20,
868
+ 14,
869
+ 4,
870
+ 10,
871
+ 7,
872
+ 18,
873
+ 6,
874
+ 1,
875
+ 12,
876
+ 32,
877
+ 28,
878
+ 22,
879
+ 4,
880
+ 10,
881
+ 2,
882
+ 8,
883
+ 16,
884
+ 18,
885
+ 23,
886
+ 7,
887
+ 19,
888
+ 25,
889
+ 13,
890
+ 1,
891
+ 4,
892
+ 21,
893
+ 16,
894
+ 15,
895
+ 3,
896
+ 20,
897
+ 20,
898
+ 15,
899
+ 13,
900
+ 16,
901
+ 33,
902
+ 33,
903
+ 26,
904
+ 31,
905
+ 14,
906
+ 2,
907
+ 9,
908
+ 30,
909
+ 18,
910
+ 12,
911
+ 18,
912
+ 20,
913
+ 7,
914
+ 1,
915
+ 20,
916
+ 10,
917
+ 24,
918
+ 7,
919
+ 8,
920
+ 3,
921
+ 20,
922
+ 2,
923
+ 0,
924
+ 0,
925
+ 16,
926
+ 18,
927
+ 5,
928
+ 20,
929
+ 10,
930
+ 9,
931
+ 6,
932
+ 5,
933
+ 23,
934
+ 26,
935
+ 12,
936
+ 17,
937
+ 17,
938
+ 2,
939
+ 19,
940
+ 19,
941
+ 33,
942
+ 4,
943
+ 11,
944
+ 13,
945
+ 24,
946
+ 0,
947
+ 16,
948
+ 20,
949
+ 11,
950
+ 14,
951
+ 4,
952
+ 18,
953
+ 6,
954
+ 3,
955
+ 15,
956
+ 1,
957
+ 28,
958
+ 0,
959
+ 8,
960
+ 1,
961
+ 29,
962
+ 5,
963
+ 7,
964
+ 21,
965
+ 15,
966
+ 19,
967
+ 6,
968
+ 22,
969
+ 22,
970
+ 21,
971
+ 9,
972
+ 18,
973
+ 13,
974
+ 28,
975
+ 18,
976
+ 14,
977
+ 2,
978
+ 7,
979
+ 13,
980
+ 14,
981
+ 26,
982
+ 31,
983
+ 33,
984
+ 5,
985
+ 20,
986
+ 1,
987
+ 8,
988
+ 12,
989
+ 14,
990
+ 13,
991
+ 29,
992
+ 9,
993
+ 23,
994
+ 10,
995
+ 0,
996
+ 4,
997
+ 24,
998
+ 27,
999
+ 0,
1000
+ 7,
1001
+ 0,
1002
+ 28,
1003
+ 13,
1004
+ 17,
1005
+ 21,
1006
+ 12,
1007
+ 9,
1008
+ 29,
1009
+ 29,
1010
+ 10,
1011
+ 8,
1012
+ 2,
1013
+ 33,
1014
+ 9,
1015
+ 17,
1016
+ 17,
1017
+ 3,
1018
+ 25,
1019
+ 3,
1020
+ 31
1021
+ ],
1022
+ "ground_truth": [
1023
+ 11,
1024
+ 8,
1025
+ 16,
1026
+ 1,
1027
+ 20,
1028
+ 2,
1029
+ 6,
1030
+ 19,
1031
+ 12,
1032
+ 20,
1033
+ 3,
1034
+ 28,
1035
+ 16,
1036
+ 8,
1037
+ 3,
1038
+ 11,
1039
+ 28,
1040
+ 5,
1041
+ 8,
1042
+ 21,
1043
+ 3,
1044
+ 10,
1045
+ 3,
1046
+ 20,
1047
+ 28,
1048
+ 2,
1049
+ 33,
1050
+ 6,
1051
+ 18,
1052
+ 27,
1053
+ 3,
1054
+ 23,
1055
+ 10,
1056
+ 16,
1057
+ 21,
1058
+ 8,
1059
+ 27,
1060
+ 31,
1061
+ 25,
1062
+ 1,
1063
+ 28,
1064
+ 1,
1065
+ 31,
1066
+ 18,
1067
+ 8,
1068
+ 24,
1069
+ 10,
1070
+ 2,
1071
+ 16,
1072
+ 10,
1073
+ 12,
1074
+ 26,
1075
+ 1,
1076
+ 24,
1077
+ 18,
1078
+ 16,
1079
+ 14,
1080
+ 3,
1081
+ 26,
1082
+ 30,
1083
+ 5,
1084
+ 17,
1085
+ 22,
1086
+ 19,
1087
+ 22,
1088
+ 0,
1089
+ 11,
1090
+ 12,
1091
+ 9,
1092
+ 31,
1093
+ 4,
1094
+ 19,
1095
+ 31,
1096
+ 19,
1097
+ 6,
1098
+ 13,
1099
+ 24,
1100
+ 15,
1101
+ 7,
1102
+ 0,
1103
+ 15,
1104
+ 21,
1105
+ 25,
1106
+ 6,
1107
+ 26,
1108
+ 13,
1109
+ 32,
1110
+ 29,
1111
+ 2,
1112
+ 5,
1113
+ 15,
1114
+ 0,
1115
+ 12,
1116
+ 27,
1117
+ 0,
1118
+ 11,
1119
+ 25,
1120
+ 16,
1121
+ 10,
1122
+ 25,
1123
+ 27,
1124
+ 17,
1125
+ 13,
1126
+ 30,
1127
+ 32,
1128
+ 29,
1129
+ 13,
1130
+ 0,
1131
+ 17,
1132
+ 1,
1133
+ 32,
1134
+ 5,
1135
+ 24,
1136
+ 30,
1137
+ 6,
1138
+ 10,
1139
+ 22,
1140
+ 2,
1141
+ 16,
1142
+ 10,
1143
+ 7,
1144
+ 12,
1145
+ 10,
1146
+ 15,
1147
+ 23,
1148
+ 15,
1149
+ 24,
1150
+ 0,
1151
+ 1,
1152
+ 7,
1153
+ 28,
1154
+ 0,
1155
+ 1,
1156
+ 4,
1157
+ 14,
1158
+ 24,
1159
+ 16,
1160
+ 3,
1161
+ 5,
1162
+ 2,
1163
+ 17,
1164
+ 25,
1165
+ 13,
1166
+ 6,
1167
+ 29,
1168
+ 6,
1169
+ 32,
1170
+ 29,
1171
+ 31,
1172
+ 22,
1173
+ 18,
1174
+ 26,
1175
+ 15,
1176
+ 7,
1177
+ 13,
1178
+ 33,
1179
+ 23,
1180
+ 11,
1181
+ 18,
1182
+ 28,
1183
+ 5,
1184
+ 2,
1185
+ 19,
1186
+ 26,
1187
+ 23,
1188
+ 17,
1189
+ 10,
1190
+ 9,
1191
+ 27,
1192
+ 6,
1193
+ 25,
1194
+ 30,
1195
+ 33,
1196
+ 30,
1197
+ 11,
1198
+ 4,
1199
+ 32,
1200
+ 24,
1201
+ 5,
1202
+ 1,
1203
+ 6,
1204
+ 17,
1205
+ 27,
1206
+ 1,
1207
+ 18,
1208
+ 27,
1209
+ 16,
1210
+ 11,
1211
+ 25,
1212
+ 7,
1213
+ 23,
1214
+ 24,
1215
+ 28,
1216
+ 2,
1217
+ 18,
1218
+ 26,
1219
+ 27,
1220
+ 5,
1221
+ 33,
1222
+ 9,
1223
+ 31,
1224
+ 2,
1225
+ 11,
1226
+ 26,
1227
+ 9,
1228
+ 29,
1229
+ 20,
1230
+ 26,
1231
+ 17,
1232
+ 29,
1233
+ 18,
1234
+ 26,
1235
+ 7,
1236
+ 4,
1237
+ 14,
1238
+ 17,
1239
+ 0,
1240
+ 19,
1241
+ 26,
1242
+ 24,
1243
+ 7,
1244
+ 15,
1245
+ 5,
1246
+ 3,
1247
+ 7,
1248
+ 2,
1249
+ 11,
1250
+ 22,
1251
+ 31,
1252
+ 15,
1253
+ 8,
1254
+ 0,
1255
+ 17,
1256
+ 31,
1257
+ 4,
1258
+ 6,
1259
+ 3,
1260
+ 30,
1261
+ 21,
1262
+ 25,
1263
+ 22,
1264
+ 3,
1265
+ 22,
1266
+ 19,
1267
+ 24,
1268
+ 8,
1269
+ 10,
1270
+ 2,
1271
+ 30,
1272
+ 11,
1273
+ 10,
1274
+ 25,
1275
+ 18,
1276
+ 27,
1277
+ 32,
1278
+ 13,
1279
+ 23,
1280
+ 4,
1281
+ 25,
1282
+ 0,
1283
+ 6,
1284
+ 13,
1285
+ 10,
1286
+ 26,
1287
+ 14,
1288
+ 20,
1289
+ 12,
1290
+ 30,
1291
+ 8,
1292
+ 32,
1293
+ 27,
1294
+ 28,
1295
+ 20,
1296
+ 11,
1297
+ 8,
1298
+ 10,
1299
+ 9,
1300
+ 15,
1301
+ 26,
1302
+ 25,
1303
+ 3,
1304
+ 10,
1305
+ 10,
1306
+ 19,
1307
+ 29,
1308
+ 22,
1309
+ 24,
1310
+ 15,
1311
+ 31,
1312
+ 16,
1313
+ 4,
1314
+ 28,
1315
+ 28,
1316
+ 33,
1317
+ 5,
1318
+ 23,
1319
+ 27,
1320
+ 32,
1321
+ 1,
1322
+ 11,
1323
+ 15,
1324
+ 15,
1325
+ 25,
1326
+ 19,
1327
+ 7,
1328
+ 33,
1329
+ 32,
1330
+ 11,
1331
+ 33,
1332
+ 20,
1333
+ 25,
1334
+ 14,
1335
+ 9,
1336
+ 13,
1337
+ 28,
1338
+ 23,
1339
+ 11,
1340
+ 13,
1341
+ 23,
1342
+ 1,
1343
+ 32,
1344
+ 11,
1345
+ 31,
1346
+ 30,
1347
+ 30,
1348
+ 22,
1349
+ 8,
1350
+ 16,
1351
+ 16,
1352
+ 19,
1353
+ 17,
1354
+ 21,
1355
+ 11,
1356
+ 26,
1357
+ 13,
1358
+ 30,
1359
+ 30,
1360
+ 5,
1361
+ 1,
1362
+ 29,
1363
+ 20,
1364
+ 28,
1365
+ 23,
1366
+ 11,
1367
+ 8,
1368
+ 30,
1369
+ 15,
1370
+ 6,
1371
+ 27,
1372
+ 14,
1373
+ 15,
1374
+ 7,
1375
+ 26,
1376
+ 20,
1377
+ 27,
1378
+ 30,
1379
+ 22,
1380
+ 32,
1381
+ 23,
1382
+ 8,
1383
+ 29,
1384
+ 15,
1385
+ 31,
1386
+ 33,
1387
+ 9,
1388
+ 7,
1389
+ 9,
1390
+ 31,
1391
+ 29,
1392
+ 12,
1393
+ 8,
1394
+ 18,
1395
+ 3,
1396
+ 29,
1397
+ 10,
1398
+ 24,
1399
+ 6,
1400
+ 15,
1401
+ 7,
1402
+ 26,
1403
+ 1,
1404
+ 2,
1405
+ 17,
1406
+ 17,
1407
+ 27,
1408
+ 21,
1409
+ 27,
1410
+ 24,
1411
+ 30,
1412
+ 6,
1413
+ 16,
1414
+ 8,
1415
+ 24,
1416
+ 4,
1417
+ 23,
1418
+ 9,
1419
+ 31,
1420
+ 7,
1421
+ 12,
1422
+ 0,
1423
+ 4,
1424
+ 12,
1425
+ 1,
1426
+ 15,
1427
+ 8,
1428
+ 32,
1429
+ 9,
1430
+ 2,
1431
+ 31,
1432
+ 13,
1433
+ 21,
1434
+ 14,
1435
+ 29,
1436
+ 28,
1437
+ 25,
1438
+ 6,
1439
+ 1,
1440
+ 3,
1441
+ 20,
1442
+ 8,
1443
+ 30,
1444
+ 30,
1445
+ 17,
1446
+ 4,
1447
+ 0,
1448
+ 3,
1449
+ 25,
1450
+ 2,
1451
+ 33,
1452
+ 30,
1453
+ 23,
1454
+ 12,
1455
+ 17,
1456
+ 0,
1457
+ 25,
1458
+ 25,
1459
+ 27,
1460
+ 3,
1461
+ 31,
1462
+ 28,
1463
+ 20,
1464
+ 7,
1465
+ 0,
1466
+ 18,
1467
+ 14,
1468
+ 2,
1469
+ 15,
1470
+ 13,
1471
+ 4,
1472
+ 32,
1473
+ 22,
1474
+ 31,
1475
+ 22,
1476
+ 3,
1477
+ 19,
1478
+ 27,
1479
+ 14,
1480
+ 11,
1481
+ 9,
1482
+ 21,
1483
+ 13,
1484
+ 1,
1485
+ 18,
1486
+ 33,
1487
+ 29,
1488
+ 13,
1489
+ 28,
1490
+ 21,
1491
+ 14,
1492
+ 16,
1493
+ 22,
1494
+ 15,
1495
+ 11,
1496
+ 13,
1497
+ 5,
1498
+ 29,
1499
+ 12,
1500
+ 12,
1501
+ 5,
1502
+ 13,
1503
+ 10,
1504
+ 26,
1505
+ 28,
1506
+ 33,
1507
+ 22,
1508
+ 27,
1509
+ 25,
1510
+ 27,
1511
+ 30,
1512
+ 27,
1513
+ 29,
1514
+ 24,
1515
+ 18,
1516
+ 22,
1517
+ 26,
1518
+ 4,
1519
+ 33,
1520
+ 5,
1521
+ 22,
1522
+ 9,
1523
+ 7,
1524
+ 7,
1525
+ 9,
1526
+ 8,
1527
+ 6,
1528
+ 33,
1529
+ 14,
1530
+ 23,
1531
+ 27,
1532
+ 29,
1533
+ 5,
1534
+ 22,
1535
+ 32,
1536
+ 22,
1537
+ 29,
1538
+ 2,
1539
+ 14,
1540
+ 31,
1541
+ 3,
1542
+ 14,
1543
+ 20,
1544
+ 17,
1545
+ 12,
1546
+ 11,
1547
+ 22,
1548
+ 12,
1549
+ 17,
1550
+ 3,
1551
+ 18,
1552
+ 27,
1553
+ 28,
1554
+ 17,
1555
+ 18,
1556
+ 28,
1557
+ 14,
1558
+ 16,
1559
+ 16,
1560
+ 30,
1561
+ 21,
1562
+ 13,
1563
+ 10,
1564
+ 8,
1565
+ 10,
1566
+ 2,
1567
+ 32,
1568
+ 8,
1569
+ 14,
1570
+ 15,
1571
+ 19,
1572
+ 23,
1573
+ 29,
1574
+ 4,
1575
+ 22,
1576
+ 12,
1577
+ 30,
1578
+ 31,
1579
+ 29,
1580
+ 29,
1581
+ 27,
1582
+ 28,
1583
+ 31,
1584
+ 11,
1585
+ 12,
1586
+ 5,
1587
+ 31,
1588
+ 21,
1589
+ 16,
1590
+ 26,
1591
+ 33,
1592
+ 17,
1593
+ 20,
1594
+ 32,
1595
+ 18,
1596
+ 11,
1597
+ 13,
1598
+ 23,
1599
+ 0,
1600
+ 26,
1601
+ 6,
1602
+ 12,
1603
+ 25,
1604
+ 31,
1605
+ 16,
1606
+ 31,
1607
+ 4,
1608
+ 21,
1609
+ 33,
1610
+ 0,
1611
+ 2,
1612
+ 23,
1613
+ 28,
1614
+ 33,
1615
+ 20,
1616
+ 11,
1617
+ 21,
1618
+ 14,
1619
+ 18,
1620
+ 9,
1621
+ 2,
1622
+ 0,
1623
+ 23,
1624
+ 20,
1625
+ 6,
1626
+ 19,
1627
+ 32,
1628
+ 9,
1629
+ 32,
1630
+ 33,
1631
+ 25,
1632
+ 12,
1633
+ 24,
1634
+ 28,
1635
+ 20,
1636
+ 12,
1637
+ 1,
1638
+ 24,
1639
+ 29,
1640
+ 1,
1641
+ 11,
1642
+ 16,
1643
+ 23,
1644
+ 33,
1645
+ 14,
1646
+ 14,
1647
+ 27,
1648
+ 28,
1649
+ 21,
1650
+ 8,
1651
+ 18,
1652
+ 32,
1653
+ 2,
1654
+ 27,
1655
+ 23,
1656
+ 21,
1657
+ 14,
1658
+ 19,
1659
+ 11,
1660
+ 21,
1661
+ 25,
1662
+ 16,
1663
+ 26,
1664
+ 4,
1665
+ 10,
1666
+ 33,
1667
+ 6,
1668
+ 2,
1669
+ 10,
1670
+ 33,
1671
+ 13,
1672
+ 26,
1673
+ 9,
1674
+ 30,
1675
+ 1,
1676
+ 30,
1677
+ 19,
1678
+ 10,
1679
+ 4,
1680
+ 5,
1681
+ 19,
1682
+ 23,
1683
+ 21,
1684
+ 6,
1685
+ 19,
1686
+ 30,
1687
+ 6,
1688
+ 32,
1689
+ 12,
1690
+ 2,
1691
+ 4,
1692
+ 16,
1693
+ 15,
1694
+ 24,
1695
+ 21,
1696
+ 22,
1697
+ 24,
1698
+ 14,
1699
+ 33,
1700
+ 30,
1701
+ 11,
1702
+ 32,
1703
+ 32,
1704
+ 1,
1705
+ 23,
1706
+ 26,
1707
+ 9,
1708
+ 21,
1709
+ 19,
1710
+ 6,
1711
+ 21,
1712
+ 0,
1713
+ 2,
1714
+ 24,
1715
+ 26,
1716
+ 3,
1717
+ 13,
1718
+ 3,
1719
+ 17,
1720
+ 23,
1721
+ 8,
1722
+ 7,
1723
+ 0,
1724
+ 32,
1725
+ 22,
1726
+ 32,
1727
+ 4,
1728
+ 8,
1729
+ 21,
1730
+ 4,
1731
+ 12,
1732
+ 25,
1733
+ 23,
1734
+ 5,
1735
+ 9,
1736
+ 29,
1737
+ 3,
1738
+ 25,
1739
+ 21,
1740
+ 27,
1741
+ 33,
1742
+ 4,
1743
+ 31,
1744
+ 10,
1745
+ 15,
1746
+ 29,
1747
+ 22,
1748
+ 5,
1749
+ 24,
1750
+ 9,
1751
+ 15,
1752
+ 33,
1753
+ 19,
1754
+ 33,
1755
+ 17,
1756
+ 1,
1757
+ 26,
1758
+ 14,
1759
+ 0,
1760
+ 7,
1761
+ 20,
1762
+ 14,
1763
+ 8,
1764
+ 1,
1765
+ 21,
1766
+ 17,
1767
+ 4,
1768
+ 5,
1769
+ 30,
1770
+ 3,
1771
+ 19,
1772
+ 18,
1773
+ 9,
1774
+ 20,
1775
+ 14,
1776
+ 4,
1777
+ 10,
1778
+ 7,
1779
+ 18,
1780
+ 6,
1781
+ 1,
1782
+ 12,
1783
+ 32,
1784
+ 28,
1785
+ 22,
1786
+ 4,
1787
+ 10,
1788
+ 24,
1789
+ 8,
1790
+ 16,
1791
+ 25,
1792
+ 23,
1793
+ 7,
1794
+ 19,
1795
+ 25,
1796
+ 13,
1797
+ 1,
1798
+ 4,
1799
+ 21,
1800
+ 16,
1801
+ 15,
1802
+ 16,
1803
+ 20,
1804
+ 25,
1805
+ 15,
1806
+ 13,
1807
+ 16,
1808
+ 33,
1809
+ 5,
1810
+ 26,
1811
+ 31,
1812
+ 14,
1813
+ 2,
1814
+ 9,
1815
+ 30,
1816
+ 18,
1817
+ 12,
1818
+ 18,
1819
+ 20,
1820
+ 7,
1821
+ 1,
1822
+ 20,
1823
+ 10,
1824
+ 24,
1825
+ 7,
1826
+ 8,
1827
+ 3,
1828
+ 20,
1829
+ 2,
1830
+ 0,
1831
+ 0,
1832
+ 16,
1833
+ 18,
1834
+ 5,
1835
+ 20,
1836
+ 19,
1837
+ 9,
1838
+ 6,
1839
+ 5,
1840
+ 23,
1841
+ 26,
1842
+ 12,
1843
+ 17,
1844
+ 17,
1845
+ 2,
1846
+ 19,
1847
+ 19,
1848
+ 33,
1849
+ 4,
1850
+ 7,
1851
+ 13,
1852
+ 24,
1853
+ 0,
1854
+ 16,
1855
+ 20,
1856
+ 11,
1857
+ 14,
1858
+ 4,
1859
+ 18,
1860
+ 6,
1861
+ 3,
1862
+ 15,
1863
+ 28,
1864
+ 28,
1865
+ 0,
1866
+ 24,
1867
+ 1,
1868
+ 29,
1869
+ 5,
1870
+ 7,
1871
+ 21,
1872
+ 15,
1873
+ 19,
1874
+ 6,
1875
+ 22,
1876
+ 22,
1877
+ 21,
1878
+ 9,
1879
+ 18,
1880
+ 13,
1881
+ 28,
1882
+ 18,
1883
+ 14,
1884
+ 6,
1885
+ 7,
1886
+ 13,
1887
+ 14,
1888
+ 26,
1889
+ 31,
1890
+ 33,
1891
+ 5,
1892
+ 20,
1893
+ 1,
1894
+ 8,
1895
+ 12,
1896
+ 19,
1897
+ 13,
1898
+ 29,
1899
+ 9,
1900
+ 23,
1901
+ 19,
1902
+ 0,
1903
+ 4,
1904
+ 24,
1905
+ 27,
1906
+ 0,
1907
+ 7,
1908
+ 0,
1909
+ 28,
1910
+ 16,
1911
+ 17,
1912
+ 21,
1913
+ 12,
1914
+ 9,
1915
+ 29,
1916
+ 29,
1917
+ 10,
1918
+ 8,
1919
+ 2,
1920
+ 33,
1921
+ 9,
1922
+ 17,
1923
+ 17,
1924
+ 3,
1925
+ 25,
1926
+ 3,
1927
+ 31
1928
+ ]
1929
+ }
tsflow/roc_curves.png ADDED

Git LFS Details

  • SHA256: 4ea780fad5adc840077c82120766b89a5b781b6a70df296ff25af1a7c23cad20
  • Pointer size: 131 Bytes
  • Size of remote file: 139 kB