
    Nj                     `    d Z ddlmZ ddlZddlZddlmZ ddlZdZ	dZ
dZdZd	Z G d
 d      Zy)u  Gaze / face-direction detector using MediaPipe Face Mesh + iris tracking.

Runs the webcam in a daemon background thread and exposes a single method:

    is_looking() -> bool

Returns True when Ahmed's face appears directed at the camera (or when no
face is detected — defaulting to "assume he's talking to Jarvis").
Returns False only when a face IS detected but the gaze is clearly directed
away from the camera.

Design constraints
- CPU-light: processes every Nth frame only (SAMPLE_EVERY = 5, ~6 fps at
  30 fps input) and runs at half resolution by default.
- Camera unavailable: silently no-ops; is_looking() always returns True.
- Thread-safe: the flag is a plain bool protected by threading.Lock.
- No GUI windows are opened (headless OpenCV).

Gaze estimation approach (no iris model required)
  Face Mesh gives 478 landmarks including the iris centres (landmarks 468
  and 473 for left / right eye respectively) and the corners of each eye.
  We project the iris centre relative to the eye-corner span to get a
  normalised horizontal offset in [-1, 1]. Values outside a threshold mean
  the person is looking left/right.  We also check the vertical tilt of the
  nose bridge vs eye line to catch strong downward or upward gaze.

  The thresholds are deliberately loose: the intent is to ignore Ahmed
  only when he is clearly staring at a second screen or looking down at his
  phone — not to react to small natural eye movements during conversation.
    )annotationsN)Optionalg333333?g?   i  ih  c                  f    e Zd ZdZdddZddZddZddZedd       Z	ddZ
ddZedd	       Zy
)GazeDetectorzContinuously sample the webcam and track whether the user is facing it.

    Usage::

        gd = GazeDetector()
        gd.start()
        ...
        if gd.is_looking():
            process_speech(text)
        ...
        gd.stop()
    c                |    || _         d| _        t        j                         | _        d| _        d | _        d| _        y )NTF)_camera_index_looking	threadingLock_lock_running_thread
_available)selfcamera_indexs     A/Users/ahmed/devFolder/Ultron/claude-voice/voice/gaze_detector.py__init__zGazeDetector.__init__C   s4    )^^%
37    c                    | j                   | j                   j                         ryd| _        t        j                  | j
                  dd      | _         | j                   j                          y)z5Start the background detection thread (non-blocking).NTzgaze-detector)targetnamedaemon)r   is_aliver   r   Thread_runstartr   s    r   r   zGazeDetector.startO   sT    <<#(=(=(? ''99?4
 	r   c                    d| _         y)z%Signal the background thread to stop.FN)r   r   s    r   stopzGazeDetector.stopY   s	    r   c                ^    | j                   5  | j                  cddd       S # 1 sw Y   yxY w)z8Return True if the user appears to be facing the camera.N)r   r
   r   s    r   
is_lookingzGazeDetector.is_looking]   s    ZZ== ZZs   #,c                    | j                   S )z-True once the camera was successfully opened.)r   r   s    r   	availablezGazeDetector.availableb   s     r   c                    	 | j                          y # t        $ rG}t        d|d       | j                  5  d| _        d d d        n# 1 sw Y   nxY wY d }~y Y d }~y d }~ww xY w)Nz$       [gaze-detector] fatal error: z2; gaze check disabled (defaulting to looking=True)T)
_run_inner	Exceptionprintr   r
   )r   excs     r   r   zGazeDetector._runk   sZ    	%OO 	%8 @E E F $ 	%s*    	A#AA	AA	AA#c                V   	 dd l }dd l}|j                  j
                  }|j                  | j                        }|j                         st        d       y d| _	        t        d       |j                  dddd	d	
      }d}	 | j                  r|j                         \  }}	|st        j                  d       8|dz  }|t        z  dk7  rJ|j!                  |	t"        t$        f      }
|j'                  |
|j(                        }|j+                  |      }|j,                  s| j.                  5  d| _        d d d        |j,                  d   j2                  }| j5                  |      }| j.                  5  || _        d d d        | j                  r|j7                          |j9                          t        d       y # t        $ r}t        d| d       Y d }~y d }~ww xY w# 1 sw Y   xY w# 1 sw Y   txY w# |j7                          |j9                          t        d       w xY w)Nr   z%       [gaze-detector] import error: z,; install mediapipe + opencv-python-headlessuH          [gaze-detector] camera unavailable — defaulting to looking=TrueTu=          [gaze-detector] camera opened — iris tracking activeF         ?)static_image_modemax_num_facesrefine_landmarksmin_detection_confidencemin_tracking_confidenceg?z       [gaze-detector] stopped)cv2	mediapipeImportErrorr(   	solutions	face_meshVideoCapturer	   isOpenedr   FaceMeshr   readtimesleep_SAMPLE_EVERYresize_PROC_WIDTH_PROC_HEIGHTcvtColorCOLOR_BGR2RGBprocessmulti_face_landmarksr   r
   landmark_classify_gazecloserelease)r   r2   mpr)   mp_face_meshcapr6   	frame_idxretframesmallrgbresultslmlookings                  r   r&   zGazeDetector._run_innert   s   	" ||--t112||~ / 0MN ))#!%($' * 
	 		4-- XXZ
UJJsOQ	},1 

5;*EFll5#*;*;<#++C033(, $11!4==--b1ZZ$+DM  3 --8 OOKKM23q  	9# ?? ? @	Z $  Z OOKKM23sT   F> B+G; -G#5?G; 4G/<G; >	G GG #G,(G; /G84G; ;-H(c                N    d fd} |d      } |d      } |d      }t         j                  j                  ||z
        }|dkD  r|d   |d   z
  |z  dz
  }nd} |d	      } |d
      } |d      }	t         j                  j                  ||z
        }
|
dkD  r|	d   |d   z
  |
z  dz
  }nd}t        ||z   dz        } |d      d    |d
      d   z   dz  } |d      d   } |d      d   } |d      d   }||z
  }|dkD  r	||z
  |z  }nd}|t        k  }|t
        k  }|xr |S )a  Return True if the face landmarks indicate the user is looking at
        the camera (or at least not clearly looking away).

        Landmark indices (MediaPipe Face Mesh with refine_landmarks=True):
          33   left eye outer corner
         133   left eye inner corner
         362   right eye inner corner
         263   right eye outer corner
         468   left iris centre
         473   right iris centre
          1    nose tip
        152    chin
          10   forehead centre
        c                d    |    }t        j                  |j                  |j                  g      S )N)nparrayxy)idxrR   	landmarkss     r   ptz'GazeDetector._classify_gaze.<locals>.pt   s'    3B88RTT244L))r   !      i  gư>r   r,   g        ij  i  i  g       @r+      
   )rZ   int)rV   linalgnormabs_HORIZONTAL_THRESH_VERTICAL_THRESH)r[   r\   
left_outer
left_inner	left_iris	left_spanleft_offsetright_innerright_outer
right_iris
right_spanright_offseth_offset	eye_mid_y
nose_tip_ychin_y
forehead_yface_heightv_ratiolooking_horizontallylooking_verticallys   `                    r   rF   zGazeDetector._classify_gaze   sh   "	*
 f
g
g	iinnZ*%<=	t$Q<*Q-79DsJKK ggg
iinn[;%>?
&qMKN:jH3NLLl2c9: "vay2c71:-4	eAh
gajfQi
z)!I-<GG'+==&*::#:(::r   N)r   )r   ra   returnNone)rz   r{   )rz   bool)__name__
__module____qualname____doc__r   r   r    r"   propertyr$   r   r&   staticmethodrF    r   r   r   r   5   sN     !
  %<4D 8; 8;r   r   )r   
__future__r   r   r;   typingr   numpyrV   re   rf   r=   r?   r@   r   r   r   r   <module>r      sF   > #        z; z;r   