├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── examples └── track-pupil.py ├── eyerec ├── CMakeLists.txt ├── __init__.py └── _eyerec.pyx ├── pyproject.toml └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib"] 2 | path = lib 3 | url = https://github.com/tcsantini/eyerec.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | 3 | project(eyerec-python) 4 | 5 | add_subdirectory(lib/cpp) 6 | add_subdirectory(eyerec) 7 | -------------------------------------------------------------------------------- /examples/track-pupil.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | import cv2 6 | import eyerec 7 | 8 | 9 | def track_pupil(method, video_file): 10 | tracker = eyerec.PupilTracker(name=method) 11 | cap = cv2.VideoCapture(video_file) 12 | sys.stdout.write(f"x, y, width, height, angle, confidence, runtime_ms,{os.linesep}") 13 | while True: 14 | ret, frame = cap.read() 15 | if not ret: 16 | break 17 | 18 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 19 | timestamp = cap.get(cv2.CAP_PROP_POS_MSEC) 20 | 21 | start = time.perf_counter() 22 | pupil = tracker.detect(timestamp, gray) 23 | end = time.perf_counter() 24 | runtime_ms = 1e3 * (end - start) 25 | for output in [ 26 | pupil['center'][0], 27 | pupil['center'][1], 28 | pupil['size'][0], 29 | pupil['size'][1], 30 | pupil['angle'], 31 | pupil['confidence'], 32 | runtime_ms, 33 | ]: 34 | sys.stdout.write(f"{output:.4f}, ") 35 | sys.stdout.write(os.linesep) 36 | 37 | if pupil['confidence'] > 0.66: 38 | g = 255 * pupil['confidence'] 39 | r = 255 - g 40 | cv2.ellipse(frame, (pupil['center'], pupil['size'], pupil['angle']), (0, g, r), 2) 41 | cv2.imshow('frame', frame) 42 | cv2.waitKey(1) 43 | 44 | 45 | if __name__ == '__main__': 46 | method = sys.argv[1] 47 | video_file = sys.argv[2] 48 | track_pupil(method=method, video_file=video_file) 49 | -------------------------------------------------------------------------------- /eyerec/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 5 | 6 | # For add_cython_target 7 | find_package(Cython REQUIRED) 8 | # For python_extension_module 9 | find_package(PythonExtensions REQUIRED) 10 | 11 | find_package(NumPy REQUIRED) 12 | find_package(OpenCV REQUIRED) 13 | 14 | add_cython_target(_eyerec CXX PY3) 15 | add_library(_eyerec MODULE ${_eyerec}) 16 | python_extension_module(_eyerec) 17 | 18 | target_link_libraries(_eyerec eyerec::eyerec ${OpenCV_LIBS}) 19 | include_directories(${NumPy_INCLUDE_DIRS}) 20 | 21 | install(TARGETS _eyerec LIBRARY DESTINATION eyerec) 22 | -------------------------------------------------------------------------------- /eyerec/__init__.py: -------------------------------------------------------------------------------- 1 | from ._eyerec import PupilTracker 2 | -------------------------------------------------------------------------------- /eyerec/_eyerec.pyx: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | cimport numpy as np 4 | 5 | from libcpp cimport bool 6 | 7 | 8 | cdef extern from "": 9 | int CV_8UC1 10 | 11 | 12 | cdef extern from "" namespace "cv": 13 | cdef cppclass Mat : 14 | Mat() except + 15 | Mat(int height, int width, int type, void* data) except + 16 | unsigned char* data 17 | 18 | cdef cppclass Point_[_Tp]: 19 | Point_() except + 20 | _Tp x 21 | _Tp y 22 | 23 | cdef cppclass Size_[_Tp]: 24 | Size_() except + 25 | _Tp height 26 | _Tp width 27 | 28 | cdef cppclass Rect_[_Tp]: 29 | Rect_(_Tp _x, _Tp y, _Tp width, _Tp height) except + 30 | _Tp height 31 | _Tp width 32 | _Tp x 33 | _Tp y 34 | 35 | ctypedef Point_[float] Point2f 36 | ctypedef Size_[float] Size2f 37 | ctypedef Rect_[int] Rect 38 | 39 | 40 | ctypedef double Timestamp 41 | 42 | 43 | cdef extern from "eyerec/Pupil.hpp": 44 | 45 | cdef struct Pupil: 46 | Point2f center 47 | Size2f size 48 | double angle 49 | double confidence 50 | 51 | 52 | cdef extern from "eyerec/PupilTrackingMethod.hpp": 53 | 54 | cdef struct TrackingParameters: 55 | Rect roi 56 | float userMinPupilDiameterPx 57 | float userMaxPupilDiameterPx 58 | bool provideConfidence 59 | Timestamp maxAge 60 | float minDetectionConfidence 61 | 62 | cdef cppclass PupilTrackingMethod: 63 | Pupil detectAndTrack(const Timestamp& ts, const Mat& frame, TrackingParameters params) 64 | 65 | 66 | cdef extern from "eyerec/PuReST.hpp": 67 | cdef cppclass PuReST: 68 | Pupil detectAndTrack(const Timestamp& ts, const Mat& frame, TrackingParameters params) 69 | 70 | 71 | cdef extern from "eyerec/TrackingByDetection.hpp": 72 | cdef cppclass TrackingByDetection[T]: 73 | Pupil detectAndTrack(const Timestamp& ts, const Mat& frame, TrackingParameters params) 74 | 75 | 76 | cdef extern from "eyerec/PupilDetectionMethod.hpp": 77 | cdef cppclass PuRe: 78 | pass 79 | 80 | 81 | cdef class PupilTracker: 82 | cdef TrackingParameters params 83 | cdef PupilTrackingMethod* pupil_tracking_method 84 | 85 | def __cinit__(self, name: str, *args, **kwargs): 86 | name = name.lower() 87 | if name == "purest": 88 | self.pupil_tracking_method = new PuReST() 89 | elif name == "pure": 90 | self.pupil_tracking_method = new TrackingByDetection[PuRe]() 91 | else: 92 | raise ValueError(f"Unexpected pupil tracker name: {name}") 93 | 94 | def __dealloc__(self): 95 | del self.pupil_tracking_method 96 | 97 | cdef _detect(self, timestamp: float, frame: np.ndarray): 98 | cdef unsigned char[:, ::1] gray = frame 99 | cdef Mat mat_frame = Mat(frame.shape[0], frame.shape[1], CV_8UC1, &gray[0, 0]) 100 | cdef Pupil pupil = self.pupil_tracking_method.detectAndTrack(timestamp, mat_frame, self.params) 101 | # it seems cython can't convert the struct with classes to a dict automatically 102 | return { 103 | "center": (pupil.center.x, pupil.center.y), 104 | "size": (pupil.size.width, pupil.size.height), 105 | "angle": pupil.angle, 106 | "confidence": pupil.confidence, 107 | } 108 | 109 | def detect(self, timestamp: float, frame: np.ndarray) -> Dict[str, Any]: 110 | return self._detect(timestamp, frame) 111 | 112 | def set_roi(self, x: int, y: int, width: int, height: int): 113 | self.params.roi = Rect(x, y, width, height) 114 | 115 | def set_min_pupil_diameter_px(self, diameter: float): 116 | self.params.userMinPupilDiameterPx = diameter 117 | 118 | def set_max_pupil_diameter_px(self, diameter: float): 119 | self.params.userMaxPupilDiameterPx = diameter 120 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "wheel", 5 | "scikit-build", 6 | "cmake", 7 | "ninja", 8 | "cython", 9 | "numpy", 10 | ] 11 | 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | 3 | setup( 4 | author="Thiago C. Santini", 5 | author_email='', 6 | install_requires=["numpy"], 7 | name="eyerec", 8 | packages=["eyerec"], 9 | version="0.0.0", 10 | classifiers=[ 11 | "Development Status :: 2 - Pre-Alpha" 12 | "Intended Audience :: Developers", 13 | "Intended Audience :: Science/Research", 14 | "Programming Language :: Python :: 3", 15 | "Programming Language :: Python :: 3.7", 16 | ], 17 | ) 18 | --------------------------------------------------------------------------------