11"""Base detection interface and abstract classes."""
22
33from abc import ABC , abstractmethod
4+ from typing import Any
45
56import cv2
67import numpy as np
1112class DetectionEngine (ABC ):
1213 """Abstract base class for detection engines."""
1314
14- def __init__ (self , ** kwargs ) :
15+ def __init__ (self , ** kwargs : Any ) -> None :
1516 """Initialize detection engine with configuration."""
1617 self .config = kwargs
1718
@@ -44,7 +45,7 @@ def cleanup(self) -> None:
4445class HeuristicDetector (DetectionEngine ):
4546 """Heuristic-based detection using OpenCV only."""
4647
47- def __init__ (self , ** kwargs ) :
48+ def __init__ (self , ** kwargs : Any ) -> None :
4849 """Initialize heuristic detector."""
4950 super ().__init__ (** kwargs )
5051
@@ -60,11 +61,11 @@ def __init__(self, **kwargs):
6061 self .last_detection_time = 0.0
6162 self .sip_start_time = 0.0
6263 self .sip_in_progress = False
63- self .detection_frames = []
64+ self .detection_frames : list [ float ] = []
6465
6566 # Load face cascade
6667 self .face_cascade = cv2 .CascadeClassifier (
67- str (cv2 .data .haarcascades + "haarcascade_frontalface_default.xml" )
68+ str (cv2 .data .haarcascades + "haarcascade_frontalface_default.xml" ) # type: ignore[attr-defined]
6869 )
6970
7071 # Skin color detection parameters
@@ -100,7 +101,8 @@ def detect(self, frame: np.ndarray) -> DetectionResult | None:
100101 return None
101102
102103 # Calculate head tilt (simplified)
103- head_tilt_angle = self ._calculate_head_tilt (face )
104+ # Convert numpy array to tuple for type checking
105+ head_tilt_angle = self ._calculate_head_tilt ((int (x ), int (y ), int (w ), int (h )))
104106
105107 # Calculate hand-face distance
106108 hand_face_distance = np .sqrt (
@@ -214,7 +216,7 @@ def cleanup(self) -> None:
214216class MediaPipeDetector (DetectionEngine ):
215217 """MediaPipe-based detection (optional)."""
216218
217- def __init__ (self , ** kwargs ) :
219+ def __init__ (self , ** kwargs : Any ) -> None :
218220 """Initialize MediaPipe detector."""
219221 super ().__init__ (** kwargs )
220222 self .mp_hands = None
@@ -250,9 +252,13 @@ def detect(self, frame: np.ndarray) -> DetectionResult | None:
250252 rgb_frame = cv2 .cvtColor (frame , cv2 .COLOR_BGR2RGB )
251253
252254 # Process hands
255+ if self .hands is None :
256+ return None
253257 hand_results = self .hands .process (rgb_frame )
254258
255259 # Process face
260+ if self .face_mesh is None :
261+ return None
256262 face_results = self .face_mesh .process (rgb_frame )
257263
258264 if (
@@ -263,6 +269,8 @@ def detect(self, frame: np.ndarray) -> DetectionResult | None:
263269
264270 # Get hand landmarks
265271 hand_landmarks = hand_results .multi_hand_landmarks [0 ]
272+ if self .mp_hands is None :
273+ return None
266274 wrist = hand_landmarks .landmark [self .mp_hands .HandLandmark .WRIST ]
267275 wrist_pos = (int (wrist .x * frame .shape [1 ]), int (wrist .y * frame .shape [0 ]))
268276
@@ -306,7 +314,7 @@ def detect(self, frame: np.ndarray) -> DetectionResult | None:
306314
307315 return None
308316
309- def _get_mouth_center (self , face_landmarks , frame_shape : tuple ) -> tuple [int , int ]:
317+ def _get_mouth_center (self , face_landmarks : Any , frame_shape : tuple [ int , int ] ) -> tuple [int , int ]:
310318 """Get mouth center from face landmarks."""
311319 # MediaPipe face mesh mouth landmarks (simplified)
312320 mouth_landmarks = [61 , 84 , 17 , 314 , 405 , 320 , 307 , 375 , 321 , 308 , 324 , 318 ]
@@ -327,7 +335,7 @@ def _get_mouth_center(self, face_landmarks, frame_shape: tuple) -> tuple[int, in
327335 return (frame_shape [1 ] // 2 , frame_shape [0 ] // 2 )
328336
329337 def _calculate_head_tilt_mediapipe (
330- self , face_landmarks , frame_shape : tuple
338+ self , face_landmarks : Any , frame_shape : tuple [ int , int ]
331339 ) -> float :
332340 """Calculate head tilt using MediaPipe face landmarks."""
333341 # Use eye landmarks to determine tilt
@@ -338,7 +346,7 @@ def _calculate_head_tilt_mediapipe(
338346 eye_angle = np .arctan2 (right_eye .y - left_eye .y , right_eye .x - left_eye .x )
339347
340348 # Convert to degrees
341- return np .degrees (eye_angle )
349+ return float ( np .degrees (eye_angle ) )
342350
343351 def is_available (self ) -> bool :
344352 """Check if MediaPipe detector is available."""
0 commit comments