Fred808 commited on
Commit
9667dad
·
verified ·
1 Parent(s): 7d9ee79

Update cursor_tracker.py

Browse files
Files changed (1) hide show
  1. cursor_tracker.py +24 -102
cursor_tracker.py CHANGED
@@ -1,90 +1,21 @@
1
- """
2
- Cursor detection and action logging utilities.
3
- """
4
- import cv2
5
- import numpy as np
6
- import json
7
- from pathlib import Path
8
- import os
9
- import smtplib
10
- from email.message import EmailMessage
11
 
12
- def to_rgb(img):
13
- if img is None:
14
- return None
15
- if len(img.shape) == 2:
16
- return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
17
- if img.shape[2] == 4:
18
- return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
19
- return img
20
 
21
- def get_mask_from_alpha(template_img):
22
- if template_img is not None and len(template_img.shape) == 3 and template_img.shape[2] == 4:
23
- # Use alpha channel as mask (nonzero alpha = 255)
24
- return (template_img[:, :, 3] > 0).astype(np.uint8) * 255
25
- return None
26
-
27
- def detect_cursor_in_frame_multi(frame, cursor_templates, threshold=0.8):
28
- """Detect cursor position in a frame using multiple templates. Returns best match above threshold."""
29
- best_pos = None
30
- best_conf = -1
31
- best_template_name = None
32
- frame_rgb = to_rgb(frame)
33
- for template_name, cursor_template in cursor_templates.items():
34
- template_rgb = to_rgb(cursor_template)
35
- mask = get_mask_from_alpha(cursor_template)
36
- if template_rgb is None or frame_rgb is None or template_rgb.shape[2] != frame_rgb.shape[2]:
37
- print(f"[WARN] Skipping template {template_name} due to channel mismatch or load error.")
38
- continue
39
- try:
40
- result = cv2.matchTemplate(frame_rgb, template_rgb, cv2.TM_CCOEFF_NORMED, mask=mask)
41
- except Exception as e:
42
- print(f"[WARN] matchTemplate failed for {template_name}: {e}")
43
- continue
44
- min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
45
- if max_val > best_conf:
46
- best_conf = max_val
47
- if max_val >= threshold:
48
- cursor_w, cursor_h = template_rgb.shape[1], template_rgb.shape[0]
49
- cursor_x = max_loc[0] + cursor_w // 2
50
- cursor_y = max_loc[1] + cursor_h // 2
51
- best_pos = (cursor_x, cursor_y)
52
- best_template_name = template_name
53
- if best_conf >= threshold:
54
- return best_pos, best_conf, best_template_name
55
- return None, best_conf, None
56
-
57
- def ensure_dir(path):
58
- os.makedirs(path, exist_ok=True)
59
-
60
- def send_email_with_attachment(subject, body, to_email, from_email, app_password, attachment_path):
61
- msg = EmailMessage()
62
- msg['Subject'] = subject
63
- msg['From'] = from_email
64
- msg['To'] = to_email
65
- msg.set_content(body)
66
- with open(attachment_path, 'rb') as f:
67
- file_data = f.read()
68
- file_name = Path(attachment_path).name
69
- msg.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_name)
70
- try:
71
- with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
72
- smtp.login(from_email, app_password)
73
- smtp.send_message(msg)
74
- print(f"[SUCCESS] Email sent to {to_email}")
75
- except Exception as e:
76
- print(f"[ERROR] Failed to send email: {e}")
77
-
78
- def track_cursor(frames_dir, cursor_templates_dir, output_json_path, threshold=0.8, start_frame=1, email_results=False):
79
- """Detect cursor in each frame using multiple templates, print status, and write positions to a JSON file."""
80
- frames_dir = Path(frames_dir).resolve()
81
- output_json_path = Path(output_json_path).resolve()
82
  cursor_templates_dir = Path(cursor_templates_dir).resolve()
83
- print(f"[DEBUG] frames_dir: {frames_dir}")
84
- print(f"[DEBUG] cursor_templates_dir: {cursor_templates_dir}")
85
- print(f"[DEBUG] output_json_path: {output_json_path}")
86
- ensure_dir(frames_dir)
87
  ensure_dir(output_json_path.parent)
 
88
  # Load all PNG templates from the cursor_templates_dir
89
  cursor_templates = {}
90
  for template_file in cursor_templates_dir.glob('*.png'):
@@ -95,20 +26,19 @@ def track_cursor(frames_dir, cursor_templates_dir, output_json_path, threshold=0
95
  print(f"[WARN] Could not load template: {template_file}")
96
  if not cursor_templates:
97
  raise FileNotFoundError(f"No cursor templates found in: {cursor_templates_dir}")
 
98
  results = []
99
- for frame_file in sorted(frames_dir.glob('*.png')):
100
- frame_num = int(frame_file.stem)
101
- if frame_num < start_frame:
102
- continue
103
- frame = cv2.imread(str(frame_file), cv2.IMREAD_UNCHANGED)
104
  if frame is None:
105
- print(f"[WARN] Could not load frame: {frame_file}")
106
  continue
 
107
  pos, conf, template_name = detect_cursor_in_frame_multi(frame, cursor_templates, threshold)
108
  if pos is not None:
109
- print(f"{frame_file.name}: Cursor at {pos} (template: {template_name})")
110
  results.append({
111
- 'frame': frame_file.name,
112
  'cursor_active': True,
113
  'x': pos[0],
114
  'y': pos[1],
@@ -116,15 +46,16 @@ def track_cursor(frames_dir, cursor_templates_dir, output_json_path, threshold=0
116
  'template': template_name
117
  })
118
  else:
119
- print(f"{frame_file.name}: Cursor disabled")
120
  results.append({
121
- 'frame': frame_file.name,
122
  'cursor_active': False,
123
  'x': None,
124
  'y': None,
125
  'confidence': conf,
126
  'template': None
127
  })
 
128
  try:
129
  with open(output_json_path, 'w') as f:
130
  json.dump(results, f, indent=2)
@@ -148,12 +79,3 @@ def track_cursor(frames_dir, cursor_templates_dir, output_json_path, threshold=0
148
  except Exception as e:
149
  print(f"[ERROR] Failed to write output JSON: {e}")
150
  raise
151
-
152
- if __name__ == "__main__":
153
- # Pre-set all paths and parameters for direct execution
154
- FRAMES_DIR = 'frames/unzipped/blender_day1'
155
- CURSOR_TEMPLATES_DIR = 'cursors' # Directory containing all cursor PNG templates
156
- OUTPUT_JSON = 'annotations/blender1.json'
157
- THRESHOLD = 0.8
158
- print(f"[INFO] Running with preset values:\n frames_dir={FRAMES_DIR}\n cursor_templates_dir={CURSOR_TEMPLATES_DIR}\n output_json={OUTPUT_JSON}\n threshold={THRESHOLD}\n start_frame=0\n")
159
- track_cursor(FRAMES_DIR, CURSOR_TEMPLATES_DIR, OUTPUT_JSON, THRESHOLD, start_frame=0, email_results=True)
 
1
+ from typing import List, Tuple
2
+ from PIL import Image
3
+ from io import BytesIO
4
+ import requests
 
 
 
 
 
 
5
 
6
+ def track_cursor_from_images(
7
+ images: List[Tuple[str, np.ndarray]],
8
+ cursor_templates_dir: str,
9
+ output_json_path: str,
10
+ threshold=0.8,
11
+ email_results=False
12
+ ):
13
+ """Detect cursor in a list of in-memory images (filename, image)."""
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  cursor_templates_dir = Path(cursor_templates_dir).resolve()
16
+ output_json_path = Path(output_json_path).resolve()
 
 
 
17
  ensure_dir(output_json_path.parent)
18
+
19
  # Load all PNG templates from the cursor_templates_dir
20
  cursor_templates = {}
21
  for template_file in cursor_templates_dir.glob('*.png'):
 
26
  print(f"[WARN] Could not load template: {template_file}")
27
  if not cursor_templates:
28
  raise FileNotFoundError(f"No cursor templates found in: {cursor_templates_dir}")
29
+
30
  results = []
31
+
32
+ for frame_filename, frame in images:
 
 
 
33
  if frame is None:
34
+ print(f"[WARN] Frame {frame_filename} is empty")
35
  continue
36
+
37
  pos, conf, template_name = detect_cursor_in_frame_multi(frame, cursor_templates, threshold)
38
  if pos is not None:
39
+ print(f"{frame_filename}: Cursor at {pos} (template: {template_name})")
40
  results.append({
41
+ 'frame': frame_filename,
42
  'cursor_active': True,
43
  'x': pos[0],
44
  'y': pos[1],
 
46
  'template': template_name
47
  })
48
  else:
49
+ print(f"{frame_filename}: Cursor disabled")
50
  results.append({
51
+ 'frame': frame_filename,
52
  'cursor_active': False,
53
  'x': None,
54
  'y': None,
55
  'confidence': conf,
56
  'template': None
57
  })
58
+
59
  try:
60
  with open(output_json_path, 'w') as f:
61
  json.dump(results, f, indent=2)
 
79
  except Exception as e:
80
  print(f"[ERROR] Failed to write output JSON: {e}")
81
  raise