From 988b64810b280ef5c9d80f2006f354ab0840116a Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Sun, 27 Aug 2023 14:10:35 -0300 Subject: [PATCH 01/25] coloquei os argumentos em cli e escrevi isso na doc --- README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++-- main.py | 91 +++++++++++++++++++++++++++---------------------------- 2 files changed, 126 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index cec0933..cf4d93f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A combinação que teve o melhor resultado foi analisar o rosto com o mediapipe Com a imagem correspondente a cada olho foi-se feite uma analise de histograma das cores da imagem, apos alguns filtros serem aplicados, para tentar analisar onde estavam a iris e a pupila. O resultado foi que a pupia é razoavelmente fácil e consistente de se obter, contudo, a iris não. Por conta disso será adicionada mais uma etapa no processo de analise, com um outro algorítimo da mediapipe, o qual extrai a posição da iris de um rosto. +## Pré-requisitos +- Python 3.9 +- pipenv ## Como rodar Eu utilizo o gerenciador de pacotes pipenv, caso não o tenha instalado execute `pip install pipenv` @@ -14,14 +17,90 @@ Para rodar o programa: - `pipenv shell` - `pipenv install` -- `python eye_tracker.py` +- `python main.py [argumentos]` ## Criar o executavel ``` -$ pipenv run pyinstaller --onefile --paths C:\Users\Rodrigo\.virtualenvs\eye-tracking-opencv-2R_2QBtr ./eye_tracker.py --add-data 'C:\Users\Rodrigo\.virtualenvs\eye-tracking-opencv-2R_2QBtr\Lib\site-packages\mediapipe\modules:mediapipe/modules' --add-data 'C:\Users\Rodrigo\.virtualenvs\eye-tracking-opencv-2R_2QBtr\Lib\site-packages\mediapipe\python\solutions:mediapipe/solutions' -``` +pipenv run pyinstaller --onefile --paths [seu_caminho] ./main.py --add-data '[seu_caminho]\Lib\site-packages\mediapipe\modules:mediapipe/modules' --add-data '[seu_caminho]\Lib\site-packages\mediapipe\python\solutions:mediapipe/solutions' +``` +Substitua [seu_caminho] pelo caminho apropriado do seu ambiente. ## Regras PROIBIDO colocar pastas que contenham o caractere '.' no nome dentro do diretório /raw para o processamento em lote + +## Documentação de Argumentos CLI + +### Descrição +O script aceita vários argumentos de linha de comando para configurar suas opções globais. Abaixo estão as opções disponíveis e suas descrições. + +### Argumentos + +#### -showprocess +Define se o processo será mostrado. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### drawbb +Define se a bounding box será desenhada. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -drawir +Define se a íris será desenhada. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -drawpu +Define se a pupila será desenhada. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -drawpp +Define se as posições passadas serão desenhadas. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: s + +#### -drawmp +Define se os pontos da máscara serão desenhados. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -drawgz +Define se o olhar será desenhado. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -showwarn +Define se os avisos serão mostrados. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: s + +#### -multicore +Define se o processamento multicore será usado. + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -overwrite +Define se os arquivos existentes serão sobrescritos. Se -multicore for s, o valor padrão é s. Caso contrário, o valor padrão é n. + +- Valores aceitos: s (sim) ou n (não) + +#### -path +Define o caminho para os arquivos. + +- Valor padrão: ""./vds" +##### Exemplo de Uso + +`python script_name.py -showprocess s -drawbb n -path "./meus_arquivos/"` +>Nota: Se um argumento inválido for fornecido para uma opção, o script imprimirá uma mensagem de erro e encerrará sua execução. \ No newline at end of file diff --git a/main.py b/main.py index fdbd1e2..466c53f 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ +import sys import os import cv2 import numpy as np @@ -15,7 +16,6 @@ from Face import Face global_options = { - 'save_video': None, 'show_process': None, 'draw_bb': None, 'draw_iris': None, @@ -25,7 +25,8 @@ 'show_warnings': None, 'use_multicore': None, 'overwrite': None, - 'show_gaze': None + 'draw_gaze': None, + 'path': None } def getVideoProperties(path): @@ -43,11 +44,11 @@ def handle_directory(path): # print(name) if (name == "auxiliary"): return - nprc_path = './vds/prc'+path.split("raw")[1].split(".")[0] + nprc_path = global_options['path'] + '/processed' try: os.mkdir(nprc_path) except: - if (show_warnings == 's'): + if (global_options['show_warnings']): print("Diretorio ja existe") if (not global_options['overwrite']): return None, None @@ -109,7 +110,7 @@ def process_video(path): frame, positions_data, 100) if (global_options['draw_mask_points']): frame = draw_face_mesh_points(image=frame, lms=face_info.lms_2d) - if (global_options['show_gaze']): + if (global_options['draw_gaze']): frame = draw_gaze(frame, face_info.gaze_vector, face_info.nose_2d) if (global_options['show_process']): @@ -128,60 +129,56 @@ def process_video(path): def find_videos(): - for root, dirs, files in os.walk('./vds/raw'): - nprc_path = './vds/prc'+root.split("raw")[1] - try: - os.mkdir(nprc_path) - except: - None + for root, dirs, files in os.walk(global_options['path']): + for file in files: - if (file.split(".")[1] == "avi" or file.split(".")[1] == "mp4"): + if ((file.split(".")[1] == "avi" or file.split(".")[1] == "mp4") and (file.split(".")[0] == "record")): process_video(root + '/' + file) -if __name__ == "__main__": - - - print("As respostas a seguir serão usadas em todos os videos do processamento em lote!") - - - - - show_process = str(input("(Isso pode implicar em perda de performance) Deseja mostrar o processo frame a frame? s/n ")).lower() - global_options['show_process'] = show_process == 's' - - draw_bb = str(input("Deseja desenhar uma borda ao redor do rosto? s/n ")).lower() - global_options['draw_bb'] = draw_bb == 's' +def find_argument_by_option(option, arguments, default): + for i in range(0, len(arguments)): + if (arguments[i] == option): + ans = arguments[i+1] + # check if is valid + if (ans == 's' or ans == 'n'): + return ans == 's' + else: + print(f'Argumento {ans} invalido para a opção {option}. Abortando...') + exit() + return default == 's' + +def get_path_argument(arguments, default): + for i in range(0, len(arguments)): + if (arguments[i] == '-path'): + return arguments[i+1] + return default - draw_iris = str(input("Deseja desenhar os circulos da iris? s/n ")).lower() - global_options['draw_iris'] = draw_iris == 's' - - draw_pupil = str(input("Deseja desenhar os circulos das pupilas? s/n ")).lower() - global_options['draw_pupil'] = draw_pupil == 's' - - draw_past_pos = str(input("Deseja desenhar a linha com o rastreio das posições passadas? s/n ")).lower() - global_options['draw_past_pos'] = draw_past_pos == 's' - - draw_mask_points = str(input("Deseja desenhar os pontos da mascara no rosto? s/n ")).lower() - global_options['draw_mask_points'] = draw_mask_points == 's' +if __name__ == "__main__": - show_gaze = str(input("Deseja mostrar o ponto de foco do olhar? s/n ")).lower() - global_options['show_gaze'] = show_gaze == 's' + # as options serão passadas por argumentos + arguments = sys.argv[1:] + + global_options['show_process'] = find_argument_by_option(option = '-showprocess', arguments = arguments, default = 'n') + global_options['draw_bb'] = find_argument_by_option(option = '-drawbb', arguments = arguments, default = 'n') + global_options['draw_iris'] = find_argument_by_option(option = '-drawir', arguments = arguments, default = 'n') + global_options['draw_pupil'] = find_argument_by_option(option = '-drawpu', arguments = arguments, default = 'n') + global_options['draw_past_pos'] = find_argument_by_option(option = '-drawpp', arguments = arguments, default = 's') + global_options['draw_mask_points'] = find_argument_by_option(option = '-drawmp', arguments = arguments, default = 'n') + global_options['draw_gaze'] = find_argument_by_option(option = '-drawgz', arguments = arguments, default = 'n') + global_options['show_warnings'] = find_argument_by_option(option = '-showwarn', arguments = arguments, default = 's') + global_options['use_multicore'] = find_argument_by_option(option = '-multicore', arguments = arguments, default = 'n') - show_warnings = str(input("Deseja que o mostre os avisos? s/n ")).lower() - global_options['show_warnings'] = show_warnings == 's' - + if (global_options['use_multicore']): + global_options['overwrite'] = find_argument_by_option(option = '-overwrite', arguments = arguments, default='s') + else: + global_options['overwrite'] = find_argument_by_option(option = '-overwrite', arguments = arguments, default='n') - use_multicore = str(input("Deseja executar o programa em multicore? s/n ")).lower() - global_options['use_multicore'] = use_multicore == 's' + global_options['path'] = get_path_argument(arguments, default = './vds') if (not global_options['use_multicore']) : - overwrite = str(input("Caso haja sobreposição dos processamentos, deseja sobrescrever o arquivo? s/n ")).lower() - global_options['overwrite'] = overwrite == 's' find_videos() else: - global_options['overwrite'] = False - processes = [] ncpu = int(os.cpu_count()/2) print(f'Inicializando processamento multicore com {ncpu} nucleos') From 1b131da93655e00705c7c6844d894cb78389f28d Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Sun, 27 Aug 2023 14:16:01 -0300 Subject: [PATCH 02/25] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf4d93f..6dbae19 100644 --- a/README.md +++ b/README.md @@ -102,5 +102,5 @@ Define o caminho para os arquivos. - Valor padrão: ""./vds" ##### Exemplo de Uso -`python script_name.py -showprocess s -drawbb n -path "./meus_arquivos/"` +`python main.py -showprocess s -drawbb n -path "./meus_arquivos"` >Nota: Se um argumento inválido for fornecido para uma opção, o script imprimirá uma mensagem de erro e encerrará sua execução. \ No newline at end of file From e20d3a2cfaddf343434be3c07597b53ce794e95a Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Sun, 27 Aug 2023 17:42:28 -0300 Subject: [PATCH 03/25] . --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dbae19..72064e1 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Define se o processo será mostrado. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n -#### drawbb +#### -drawbb Define se a bounding box será desenhada. - Valores aceitos: s (sim) ou n (não) From 1bd78c39afb0cd908642f4c0de1d74adb1ceb062 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Wed, 18 Oct 2023 19:12:16 -0300 Subject: [PATCH 04/25] =?UTF-8?q?teste=20depois=20da=20renbomea=C3=A7?= =?UTF-8?q?=C3=A3o=20da=20org?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Face.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Face.py b/Face.py index b58c7b6..603952c 100644 --- a/Face.py +++ b/Face.py @@ -8,7 +8,6 @@ from gaze_module import GazeEstimator from definitions import * - class Face(): def __init__(self, image, logging = False): self.image = image.copy() From ae60b6c10be32128c88bf0759ca8b575240be1ce Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 19 Oct 2023 16:53:46 -0300 Subject: [PATCH 05/25] corrigido para funcionar com processamento em lote --- main.py | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 466c53f..78842b1 100644 --- a/main.py +++ b/main.py @@ -38,13 +38,20 @@ def getVideoProperties(path): return vfps, vLength, camera, height, width def handle_directory(path): + original_path = path name = path.split('/') name = name[len(name)-1].split('.') name = name[0] # print(name) if (name == "auxiliary"): return - nprc_path = global_options['path'] + '/processed' + nprc_path = global_options['path'] + '/processed' + + if(global_options['show_warnings']): + print(f'original path: {original_path}\npath: {path}\nname: {name}\n') + + if(global_options['path'].split('/')[-1] != original_path.split('/')[-2]): + nprc_path += '/' + original_path.split('/')[-2] try: os.mkdir(nprc_path) except: @@ -54,11 +61,11 @@ def handle_directory(path): return None, None path = nprc_path + "/" name = nprc_path + '/video.avi' + if (global_options['show_warnings']): + print(f'prc path: {path}\nprc name: {name}\n') return path, name def process_video(path): - - vfps, vLength, camera, height, width = getVideoProperties(path) path, name = handle_directory(path) if(path is None): @@ -129,10 +136,12 @@ def process_video(path): def find_videos(): - for root, dirs, files in os.walk(global_options['path']): - + for root, dirs, files in os.walk(global_options['path']): + root = root.replace('\\', '/') for file in files: if ((file.split(".")[1] == "avi" or file.split(".")[1] == "mp4") and (file.split(".")[0] == "record")): + if(global_options['show_warnings']): + print(f'root: {root}\ndirs: {dirs}\nfile: {file}\n') process_video(root + '/' + file) @@ -151,7 +160,8 @@ def find_argument_by_option(option, arguments, default): def get_path_argument(arguments, default): for i in range(0, len(arguments)): if (arguments[i] == '-path'): - return arguments[i+1] + # troca \ por / para evitar problemas com o windows + return arguments[i+1].replace('\\', '/') return default if __name__ == "__main__": @@ -159,6 +169,25 @@ def get_path_argument(arguments, default): # as options serão passadas por argumentos arguments = sys.argv[1:] + # mostra os argumentos disponiveis e seus defauts e o que fazem (descrição breve) + if (len(arguments) == 0): + print(''' + Argumentos disponiveis: + -showprocess s/n (default: n) -> mostra o video sendo processado + -drawbb s/n (default: n) -> desenha o bounding box da face + -drawir s/n (default: n) -> desenha os circulos da iris + -drawpu s/n (default: n) -> desenha os circulos da pupila + -drawpp s/n (default: s) -> desenha as ultimas posicoes da pupila + -drawmp s/n (default: n) -> desenha os pontos da malha da face + -drawgz s/n (default: n) -> desenha o vetor de olhar + -showwarn s/n (default: s) -> mostra avisos + -multicore s/n (default: n) -> usa processamento multicore + -overwrite s/n (default: s) -> sobrescreve os arquivos ja processados + -path (default: ./vds) -> caminho para a pasta com os videos + ''') + + exit() + global_options['show_process'] = find_argument_by_option(option = '-showprocess', arguments = arguments, default = 'n') global_options['draw_bb'] = find_argument_by_option(option = '-drawbb', arguments = arguments, default = 'n') global_options['draw_iris'] = find_argument_by_option(option = '-drawir', arguments = arguments, default = 'n') From 876c809c992e741f579489930673dcedfb80f4ba Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 19 Oct 2023 19:16:32 -0300 Subject: [PATCH 06/25] =?UTF-8?q?corrigi=20o=20multicore=20e=20corrigi=20o?= =?UTF-8?q?=20uso=20de=20memoria=20(eu=20passo=20o=20frame=20no=20detect?= =?UTF-8?q?=20e=20fa=C3=A7o=20s=C3=B3=20uma=20instancia=C3=A7=C3=A3o=20do?= =?UTF-8?q?=20modelo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Face.py | 7 ++++--- main.py | 58 +++++++++++++++++++++++++++++++++++++++++------------ options.txt | 10 +++++++++ path.txt | 1 + 4 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 options.txt create mode 100644 path.txt diff --git a/Face.py b/Face.py index 603952c..afd0c62 100644 --- a/Face.py +++ b/Face.py @@ -9,8 +9,8 @@ from definitions import * class Face(): - def __init__(self, image, logging = False): - self.image = image.copy() + def __init__(self, logging = False): + self.image = None self._face_mesh_detector = FaceMeshDetector() self._gaze_estimator = None self._eye_module = None @@ -41,7 +41,8 @@ def get_position_data_as_dict(self): } def init_eye_module(self): self._eye_module = EyeModule(self.image, self.lms_2d) - def detect_face(self): + def detect_face(self, image): + self.image = image self.lms_3d = self._face_mesh_detector.findFaceMesh(self.image) if self.lms_3d is None: if self.logging: diff --git a/main.py b/main.py index 78842b1..f0dd9f4 100644 --- a/main.py +++ b/main.py @@ -39,8 +39,9 @@ def getVideoProperties(path): def handle_directory(path): original_path = path - name = path.split('/') - name = name[len(name)-1].split('.') + name_with_ext = path.split('/') + name = name_with_ext[len(name_with_ext)-1].split('.') + name_with_ext = name_with_ext[-1] name = name[0] # print(name) if (name == "auxiliary"): @@ -48,12 +49,18 @@ def handle_directory(path): nprc_path = global_options['path'] + '/processed' if(global_options['show_warnings']): - print(f'original path: {original_path}\npath: {path}\nname: {name}\n') + print(f'original path: {original_path}\nbase new path: {path}\nname: {name}\n') if(global_options['path'].split('/')[-1] != original_path.split('/')[-2]): - nprc_path += '/' + original_path.split('/')[-2] + print(original_path.split(name_with_ext)) + print(original_path.split(global_options['path'])) + diff = original_path.split(name_with_ext)[0].split(global_options['path'])[1] + nprc_path += diff[:-1] + + print(f'new path: {nprc_path}\n') try: - os.mkdir(nprc_path) + # mkdir que cria pasta e subpastas se necessário + os.makedirs(nprc_path) except: if (global_options['show_warnings']): print("Diretorio ja existe") @@ -62,7 +69,7 @@ def handle_directory(path): path = nprc_path + "/" name = nprc_path + '/video.avi' if (global_options['show_warnings']): - print(f'prc path: {path}\nprc name: {name}\n') + print(f'prc path: {path}\nprc name: {name}\n\n') return path, name def process_video(path): @@ -79,15 +86,14 @@ def process_video(path): (width, height)) positions_data = PositionsModule() + + face_info = Face(logging = global_options['show_warnings']) for i in tqdm(range(0, vLength+1)): ret, frame = camera.read() frame_data = FaceDataModule(i, height, width) if ret: - - - face_info = Face(frame, logging = global_options['show_warnings']) - face_info.detect_face() + face_info.detect_face(frame) if(face_info.lms_3d is not None): face_info.init_eye_module() @@ -133,9 +139,21 @@ def process_video(path): camera.release() cv2.destroyAllWindows() positions_data.save_data(path+"/positions.csv") - + del positions_data + +def verify_globals(): + # if it is running on multicore, the globals wont be shared, so we need to load it from the file + if (global_options['path'] is None): + print("Carregando opções do arquivo para o multicore") + with open('options.txt', 'r') as f: + for line in f: + line = line.split() + global_options[line[0]] = line[1] == 's' + with open('path.txt', 'r') as f: + global_options['path'] = f.readline() def find_videos(): + verify_globals() for root, dirs, files in os.walk(global_options['path']): root = root.replace('\\', '/') for file in files: @@ -199,12 +217,26 @@ def get_path_argument(arguments, default): global_options['use_multicore'] = find_argument_by_option(option = '-multicore', arguments = arguments, default = 'n') if (global_options['use_multicore']): - global_options['overwrite'] = find_argument_by_option(option = '-overwrite', arguments = arguments, default='s') - else: global_options['overwrite'] = find_argument_by_option(option = '-overwrite', arguments = arguments, default='n') + else: + global_options['overwrite'] = find_argument_by_option(option = '-overwrite', arguments = arguments, default='s') global_options['path'] = get_path_argument(arguments, default = './vds') + # save options on temp file + with open('options.txt', 'w') as f: + for key in global_options: + if key !='path': + f.write(f'{key} {"s" if global_options[key] else "n"}\n') + with open('path.txt', 'w') as f: + f.write(global_options['path']) + + try: + os.mkdir(global_options['path'] + '/processed') + except: + if (global_options['show_warnings']): + print("Um processamento ja foi feito nessa pasta. Refazendo...") + if (not global_options['use_multicore']) : find_videos() else: diff --git a/options.txt b/options.txt new file mode 100644 index 0000000..5de3b12 --- /dev/null +++ b/options.txt @@ -0,0 +1,10 @@ +show_process n +draw_bb n +draw_iris s +draw_pupil s +draw_past_pos s +draw_mask_points s +show_warnings n +use_multicore s +overwrite n +draw_gaze s diff --git a/path.txt b/path.txt new file mode 100644 index 0000000..0ad45cf --- /dev/null +++ b/path.txt @@ -0,0 +1 @@ +D:/Projects_Datasets/EyeTracker/dataset/recordings \ No newline at end of file From 1bfc53a4ab3cb1aeb8fe59244f21e164ad8165c5 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 19 Oct 2023 19:21:21 -0300 Subject: [PATCH 07/25] =?UTF-8?q?corre=C3=A7=C3=A3o=20de=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index f0dd9f4..f28dd5c 100644 --- a/main.py +++ b/main.py @@ -52,12 +52,13 @@ def handle_directory(path): print(f'original path: {original_path}\nbase new path: {path}\nname: {name}\n') if(global_options['path'].split('/')[-1] != original_path.split('/')[-2]): - print(original_path.split(name_with_ext)) - print(original_path.split(global_options['path'])) + # print(original_path.split(name_with_ext)) + # print(original_path.split(global_options['path'])) diff = original_path.split(name_with_ext)[0].split(global_options['path'])[1] nprc_path += diff[:-1] - print(f'new path: {nprc_path}\n') + if(global_options['show_warnings']): + print(f'new path: {nprc_path}\n') try: # mkdir que cria pasta e subpastas se necessário os.makedirs(nprc_path) @@ -79,7 +80,8 @@ def process_video(path): if(global_options['show_warnings']): print("Abortando processamento, pois o diretorio ja existe") return - print("\nProcessando: " + path) + if(global_options['show_warnings']): + print("\nProcessando: " + path) fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter(name, fourcc, vfps, @@ -251,3 +253,7 @@ def get_path_argument(arguments, default): for process in processes: process.join() + + # move os arquivos de opções para a pasta processed + os.rename('options.txt', global_options['path'] + '/processed/options.txt') + os.rename('path.txt', global_options['path'] + '/processed/path.txt') \ No newline at end of file From 4f82bf7ca902df8f8db18712bf3aa991fb8106a9 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 19 Oct 2023 20:16:59 -0300 Subject: [PATCH 08/25] AJUSTES --- main.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index f28dd5c..9159a05 100644 --- a/main.py +++ b/main.py @@ -84,8 +84,8 @@ def process_video(path): print("\nProcessando: " + path) fourcc = cv2.VideoWriter_fourcc(*'XVID') - out = cv2.VideoWriter(name, fourcc, vfps, - (width, height)) + out = cv2.VideoWriter(filename=name, fourcc=fourcc, fps=vfps, + frameSize=(width, height), isColor=True) positions_data = PositionsModule() @@ -141,7 +141,7 @@ def process_video(path): camera.release() cv2.destroyAllWindows() positions_data.save_data(path+"/positions.csv") - del positions_data + def verify_globals(): # if it is running on multicore, the globals wont be shared, so we need to load it from the file @@ -225,7 +225,14 @@ def get_path_argument(arguments, default): global_options['path'] = get_path_argument(arguments, default = './vds') - # save options on temp file + + try: + os.mkdir(global_options['path'] + '/processed') + except: + if (global_options['show_warnings']): + print("Um processamento ja foi feito nessa pasta. Refazendo...") + + # save options on temp file with open('options.txt', 'w') as f: for key in global_options: if key !='path': @@ -233,11 +240,13 @@ def get_path_argument(arguments, default): with open('path.txt', 'w') as f: f.write(global_options['path']) - try: - os.mkdir(global_options['path'] + '/processed') - except: - if (global_options['show_warnings']): - print("Um processamento ja foi feito nessa pasta. Refazendo...") + # escreve uma copia em processed + with open(global_options['path'] + '/processed/options.txt', 'w') as f: + for key in global_options: + if key !='path': + f.write(f'{key} {"s" if global_options[key] else "n"}\n') + with open(global_options['path'] + '/processed/path.txt', 'w') as f: + f.write(global_options['path']) if (not global_options['use_multicore']) : find_videos() @@ -254,6 +263,4 @@ def get_path_argument(arguments, default): for process in processes: process.join() - # move os arquivos de opções para a pasta processed - os.rename('options.txt', global_options['path'] + '/processed/options.txt') - os.rename('path.txt', global_options['path'] + '/processed/path.txt') \ No newline at end of file + \ No newline at end of file From b83575cc87adf0f135645250e69d5633a28e4c14 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 19 Oct 2023 20:56:31 -0300 Subject: [PATCH 09/25] =?UTF-8?q?agora=20ele=20salva=20uma=20tela=20preta?= =?UTF-8?q?=20nos=20frames=20que=20n=C3=A3o=20conseguiu=20ler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 26 +++++++++++++++++++++++--- options.txt | 6 +++--- path.txt | 2 +- stats.py | 21 +++++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 stats.py diff --git a/main.py b/main.py index 9159a05..13cf9a1 100644 --- a/main.py +++ b/main.py @@ -90,11 +90,14 @@ def process_video(path): positions_data = PositionsModule() face_info = Face(logging = global_options['show_warnings']) - for i in tqdm(range(0, vLength+1)): + face_not_found_counter = 0 + frame_not_found_counter = 0 + for i in tqdm(range(0, vLength)): ret, frame = camera.read() frame_data = FaceDataModule(i, height, width) if ret: + original_frame = frame.copy() face_info.detect_face(frame) if(face_info.lms_3d is not None): @@ -127,21 +130,38 @@ def process_video(path): frame = draw_face_mesh_points(image=frame, lms=face_info.lms_2d) if (global_options['draw_gaze']): frame = draw_gaze(frame, face_info.gaze_vector, face_info.nose_2d) + else: + face_not_found_counter += 1 + # verifica se o frame é uma imagem valida + if (frame is None): + out.write(original_frame) + else: + out.write(frame) if (global_options['show_process']): cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break #print("\nframe shape ",frame.shape) + else: + frame_not_found_counter += 1 + if (global_options['show_warnings']): + print("Frame nao encontrado, salvando com uma imagem preta") + out.write(np.zeros((height, width, 3), np.uint8)) + # independente de ter encontrado a face ou nao, salva os dados positions_data.add_positions(frame_data) - out.write(frame) + + + out.release() camera.release() cv2.destroyAllWindows() positions_data.save_data(path+"/positions.csv") - + if (global_options['show_warnings']): + print(f'Frames com faces nao encontradas: {face_not_found_counter}') + print(f'Frames nao encontrados: {frame_not_found_counter}') def verify_globals(): # if it is running on multicore, the globals wont be shared, so we need to load it from the file diff --git a/options.txt b/options.txt index 5de3b12..a75cd1e 100644 --- a/options.txt +++ b/options.txt @@ -4,7 +4,7 @@ draw_iris s draw_pupil s draw_past_pos s draw_mask_points s -show_warnings n -use_multicore s -overwrite n +show_warnings s +use_multicore n +overwrite s draw_gaze s diff --git a/path.txt b/path.txt index 0ad45cf..6fd9bd6 100644 --- a/path.txt +++ b/path.txt @@ -1 +1 @@ -D:/Projects_Datasets/EyeTracker/dataset/recordings \ No newline at end of file +D:/Projects_Datasets/EyeTracker/dataset/recordings/2/18 \ No newline at end of file diff --git a/stats.py b/stats.py new file mode 100644 index 0000000..d5d4062 --- /dev/null +++ b/stats.py @@ -0,0 +1,21 @@ +import cv2 + +# mostra o fps, bitrate e a quantidade de frames de um video e todos as possiveis propriedades dele +video_path = "D:/Projects_Datasets/EyeTracker/dataset/recordings/processed/2/18/video.avi" +print(f'video_path: {video_path}') +cap = cv2.VideoCapture(video_path) +fps = cap.get(cv2.CAP_PROP_FPS) +total_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) +bitrate = cap.get(cv2.CAP_PROP_BITRATE) +print("fps: ", fps) +print("total_frames: ", total_frames) +print("bitrate: ", bitrate) +print("width: ", cap.get(cv2.CAP_PROP_FRAME_WIDTH)) +print("height: ", cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) +print("exposure: ", cap.get(cv2.CAP_PROP_EXPOSURE)) +print("convert_rgb: ", cap.get(cv2.CAP_PROP_CONVERT_RGB)) +print("rectification: ", cap.get(cv2.CAP_PROP_RECTIFICATION)) +print("iso_speed: ", cap.get(cv2.CAP_PROP_ISO_SPEED)) +print("buffersize: ", cap.get(cv2.CAP_PROP_BUFFERSIZE)) +print("mode: ", cap.get(cv2.CAP_PROP_MODE)) + From 3df005177b88043f56f0645baa251ecb220e0cca Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 11 Apr 2024 14:45:25 -0300 Subject: [PATCH 10/25] =?UTF-8?q?troquei=20os=20nomes=20para=20ser=20da=20?= =?UTF-8?q?orienta=C3=A7=C3=A3o=20do=20rosto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Face.py | 14 +++++++------- drawing_utils.py | 4 ++-- gaze_module.py | 4 ++-- main.py | 12 ++++++------ options.txt | 2 +- positions_module.py | 14 +++++++------- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Face.py b/Face.py index afd0c62..1e99f28 100644 --- a/Face.py +++ b/Face.py @@ -5,14 +5,14 @@ from face_mesh_module import FaceMeshDetector from eye_feature_detector_module import EyeModule -from gaze_module import GazeEstimator +from gaze_module import HeadOrientationEstimator from definitions import * class Face(): def __init__(self, logging = False): self.image = None self._face_mesh_detector = FaceMeshDetector() - self._gaze_estimator = None + self._head_orientation_estimator = None self._eye_module = None self.logging = logging self.lms_3d = None @@ -23,13 +23,13 @@ def __init__(self, logging = False): self.right_pupil = None self.face_border = None self.nose_2d = None - self.gaze_vector = None + self.head_orientation_vector = None - def detect_gaze(self): - self._gaze_estimator = GazeEstimator( self.lms_3d, self.image.shape[0], self.image.shape[1]) - gaze_vector, nose_2d = self._gaze_estimator.get_gaze_vector() + def detect_head_orientation(self): + self._head_orientation_estimator = HeadOrientationEstimator( self.lms_3d, self.image.shape[0], self.image.shape[1]) + head_orientation_vector, nose_2d = self._head_orientation_estimator.get_head_orientation_vector() self.nose_2d = nose_2d - self.gaze_vector = gaze_vector + self.head_orientation_vector = head_orientation_vector def get_position_data_as_dict(self): return { diff --git a/drawing_utils.py b/drawing_utils.py index 90620c0..1ffe7cf 100644 --- a/drawing_utils.py +++ b/drawing_utils.py @@ -5,8 +5,8 @@ from definitions import * -def draw_gaze(image, gaze_vector, nose_2d): - x,y,z = gaze_vector +def draw_head_orientation(image, head_orientation_vector, nose_2d): + x,y,z = head_orientation_vector p1 = (int(nose_2d[0]), int(nose_2d[1])) p2 = (int(nose_2d[0] + y*10), int(nose_2d[1] - x*10)) diff --git a/gaze_module.py b/gaze_module.py index 5162c66..ff35e15 100644 --- a/gaze_module.py +++ b/gaze_module.py @@ -3,7 +3,7 @@ import numpy as np import time -class GazeEstimator(): +class HeadOrientationEstimator(): def __init__(self, lms_3d, img_h, img_w): self.face_3d = [] self.face_2d = [] @@ -11,7 +11,7 @@ def __init__(self, lms_3d, img_h, img_w): self.img_h = img_h self.img_w = img_w - def get_gaze_vector(self): + def get_head_orientation_vector(self): for idx, lm in enumerate(self.lms_3d): if idx == 33 or idx == 263 or idx == 1 or idx == 61 or idx == 291 or idx == 199: if idx == 1: diff --git a/main.py b/main.py index 13cf9a1..88c56e9 100644 --- a/main.py +++ b/main.py @@ -25,7 +25,7 @@ 'show_warnings': None, 'use_multicore': None, 'overwrite': None, - 'draw_gaze': None, + 'draw_head_orientation': None, 'path': None } @@ -105,7 +105,7 @@ def process_video(path): face_info.detect_iris() face_info.detect_pupil() - face_info.detect_gaze() + face_info.detect_head_orientation() # salva os dados da face face_data_dict = face_info.get_position_data_as_dict() @@ -113,7 +113,7 @@ def process_video(path): for key in face_data_dict: frame_data.add_position_data(face_data_dict[key],key) - frame_data.add_gaze_data(face_info.gaze_vector) + frame_data.add_head_orientation_data(face_info.head_orientation_vector) # desenha os dados da face if (global_options['draw_bb']): @@ -128,8 +128,8 @@ def process_video(path): frame, positions_data, 100) if (global_options['draw_mask_points']): frame = draw_face_mesh_points(image=frame, lms=face_info.lms_2d) - if (global_options['draw_gaze']): - frame = draw_gaze(frame, face_info.gaze_vector, face_info.nose_2d) + if (global_options['draw_head_orientation']): + frame = draw_head_orientation(frame, face_info.head_orientation_vector, face_info.nose_2d) else: face_not_found_counter += 1 @@ -234,7 +234,7 @@ def get_path_argument(arguments, default): global_options['draw_pupil'] = find_argument_by_option(option = '-drawpu', arguments = arguments, default = 'n') global_options['draw_past_pos'] = find_argument_by_option(option = '-drawpp', arguments = arguments, default = 's') global_options['draw_mask_points'] = find_argument_by_option(option = '-drawmp', arguments = arguments, default = 'n') - global_options['draw_gaze'] = find_argument_by_option(option = '-drawgz', arguments = arguments, default = 'n') + global_options['draw_head_orientation'] = find_argument_by_option(option = '-drawgz', arguments = arguments, default = 'n') global_options['show_warnings'] = find_argument_by_option(option = '-showwarn', arguments = arguments, default = 's') global_options['use_multicore'] = find_argument_by_option(option = '-multicore', arguments = arguments, default = 'n') diff --git a/options.txt b/options.txt index a75cd1e..1d858e7 100644 --- a/options.txt +++ b/options.txt @@ -7,4 +7,4 @@ draw_mask_points s show_warnings s use_multicore n overwrite s -draw_gaze s +draw_head_orientation s diff --git a/positions_module.py b/positions_module.py index c3c151b..f41eb88 100644 --- a/positions_module.py +++ b/positions_module.py @@ -53,9 +53,9 @@ def save_data(self, path): 'right_pupil_y': [ data._right_pupil['y'] if data._right_pupil is not None else "" for data in self._data], 'right_pupil_r': [ data._right_pupil['r'] if data._right_pupil is not None else "" for data in self._data], - 'gaze_x': [ data._gaze['x'] if data._gaze is not None else "" for data in self._data], - 'gaze_y': [ data._gaze['y'] if data._gaze is not None else "" for data in self._data], - 'gaze_z': [ data._gaze['z'] if data._gaze is not None else "" for data in self._data], + 'head_orientation_x': [ data._head_orientation['x'] if data._head_orientation is not None else "" for data in self._data], + 'head_orientation_y': [ data._head_orientation['y'] if data._head_orientation is not None else "" for data in self._data], + 'head_orientation_z': [ data._head_orientation['z'] if data._head_orientation is not None else "" for data in self._data], } @@ -69,7 +69,7 @@ def __init__(self, frame,height, width): self._right_iris = None self._left_pupil = None self._right_pupil = None - self._gaze = None + self._head_orientation = None self._frame = frame self._height = height self._width = width @@ -85,16 +85,16 @@ def print_data(self): print(self._right_pupil) print("\n-------------------------------------") - def add_gaze_data(self, data): + def add_head_orientation_data(self, data): if(data is not None): x,y,z = data - self._gaze = { + self._head_orientation = { "x": x, "y": y, "z": z } else: - self._gaze = { + self._head_orientation = { "x": None, "y": None, "z": None From 010ca92d97d78acbf6433fb91e1838f6e35b0522 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 11 Apr 2024 15:21:52 -0300 Subject: [PATCH 11/25] tentei adicionar o gaze do olho --- Face.py | 11 +- Pipfile.lock | 483 -------------------------------------- gaze_module.py | 99 ++++++++ main.py | 1 + teste_eye_gaze/gaze.py | 99 ++++++++ teste_eye_gaze/helpers.py | 2 + teste_eye_gaze/main.py | 41 ++++ 7 files changed, 251 insertions(+), 485 deletions(-) delete mode 100644 Pipfile.lock create mode 100644 teste_eye_gaze/gaze.py create mode 100644 teste_eye_gaze/helpers.py create mode 100644 teste_eye_gaze/main.py diff --git a/Face.py b/Face.py index 1e99f28..fd52e9e 100644 --- a/Face.py +++ b/Face.py @@ -5,14 +5,14 @@ from face_mesh_module import FaceMeshDetector from eye_feature_detector_module import EyeModule -from gaze_module import HeadOrientationEstimator +from gaze_module import HeadOrientationEstimator, EyeGazeEstimator from definitions import * - class Face(): def __init__(self, logging = False): self.image = None self._face_mesh_detector = FaceMeshDetector() self._head_orientation_estimator = None + self._eye_gaze_estimator = None self._eye_module = None self.logging = logging self.lms_3d = None @@ -24,12 +24,19 @@ def __init__(self, logging = False): self.face_border = None self.nose_2d = None self.head_orientation_vector = None + self.left_eye_gaze = None + self.right_eye_gaze = None def detect_head_orientation(self): self._head_orientation_estimator = HeadOrientationEstimator( self.lms_3d, self.image.shape[0], self.image.shape[1]) head_orientation_vector, nose_2d = self._head_orientation_estimator.get_head_orientation_vector() self.nose_2d = nose_2d self.head_orientation_vector = head_orientation_vector + + def detect_eyes_gaze(self): + self._eye_gaze_estimator = EyeGazeEstimator(self.lms_3d, self.image.shape) + left_eye_gaze, right_eye_gaze = self._eye_gaze_estimator.get_eye_gaze() + def get_position_data_as_dict(self): return { diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index a906202..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,483 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "09da273f2b9750e8276b384d90369dce023f495ccfc232d58163f100d312b3c4" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "absl-py": { - "hashes": [ - "sha256:3aa39f898329c2156ff525dfa69ce709e42d77aab18bf4917719d6f260aa6a08", - "sha256:db97287655e30336938f8058d2c81ed2be6af1d9b6ebbcd8df1080a6c7fcd24e" - ], - "markers": "python_version >= '3.6'", - "version": "==1.1.0" - }, - "attrs": { - "hashes": [ - "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", - "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.4.0" - }, - "colorama": { - "hashes": [ - "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", - "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" - ], - "markers": "platform_system == 'Windows'", - "version": "==0.4.5" - }, - "cycler": { - "hashes": [ - "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3", - "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f" - ], - "markers": "python_version >= '3.6'", - "version": "==0.11.0" - }, - "fonttools": { - "hashes": [ - "sha256:5fd7828b9b173acc095af41740bd42cb54e38a955df83d8e2d20286fdf5725aa", - "sha256:73d3fab85790f076d56db431bfdf9ce51b566816ff74d51e050e11ab1ffa8f8b" - ], - "markers": "python_version >= '3.7'", - "version": "==4.34.0" - }, - "kiwisolver": { - "hashes": [ - "sha256:007799c7fa934646318fc128b033bb6e6baabe7fbad521bfb2279aac26225cd7", - "sha256:130c6c35eded399d3967cf8a542c20b671f5ba85bd6f210f8b939f868360e9eb", - "sha256:1858ad3cb686eccc7c6b7c5eac846a1cfd45aacb5811b2cf575e80b208f5622a", - "sha256:1ae7aa0784aeadfbd693c27993727792fbe1455b84d49970bad5886b42976b18", - "sha256:1d2c744aeedce22c122bb42d176b4aa6d063202a05a4abdacb3e413c214b3694", - "sha256:21a3a98f0a21fc602663ca9bce2b12a4114891bdeba2dea1e9ad84db59892fca", - "sha256:22ccba48abae827a0f952a78a7b1a7ff01866131e5bbe1f826ce9bda406bf051", - "sha256:26b5a70bdab09e6a2f40babc4f8f992e3771751e144bda1938084c70d3001c09", - "sha256:2d76780d9c65c7529cedd49fa4802d713e60798d8dc3b0d5b12a0a8f38cca51c", - "sha256:325fa1b15098e44fe4590a6c5c09a212ca10c6ebb5d96f7447d675f6c8340e4e", - "sha256:3a297d77b3d6979693f5948df02b89431ae3645ec95865e351fb45578031bdae", - "sha256:3b1dcbc49923ac3c973184a82832e1f018dec643b1e054867d04a3a22255ec6a", - "sha256:40240da438c0ebfe2aa76dd04b844effac6679423df61adbe3437d32f23468d9", - "sha256:46c6e5018ba31d5ee7582f323d8661498a154dea1117486a571db4c244531f24", - "sha256:46fb56fde006b7ef5f8eaa3698299b0ea47444238b869ff3ced1426aa9fedcb5", - "sha256:4dc350cb65fe4e3f737d50f0465fa6ea0dcae0e5722b7edf5d5b0a0e3cd2c3c7", - "sha256:51078855a16b7a4984ed2067b54e35803d18bca9861cb60c60f6234b50869a56", - "sha256:547111ef7cf13d73546c2de97ce434935626c897bdec96a578ca100b5fcd694b", - "sha256:5fb73cc8a34baba1dfa546ae83b9c248ef6150c238b06fc53d2773685b67ec67", - "sha256:654280c5f41831ddcc5a331c0e3ce2e480bbc3d7c93c18ecf6236313aae2d61a", - "sha256:6b3136eecf7e1b4a4d23e4b19d6c4e7a8e0b42d55f30444e3c529700cdacaa0d", - "sha256:7118ca592d25b2957ff7b662bc0fe4f4c2b5d5b27814b9b1bc9f2fb249a970e7", - "sha256:71af5b43e4fa286a35110fc5bb740fdeae2b36ca79fbcf0a54237485baeee8be", - "sha256:747190fcdadc377263223f8f72b038381b3b549a8a3df5baf4d067da4749b046", - "sha256:8395064d63b26947fa2c9faeea9c3eee35e52148c5339c37987e1d96fbf009b3", - "sha256:84f85adfebd7d3c3db649efdf73659e1677a2cf3fa6e2556a3f373578af14bf7", - "sha256:86bcf0009f2012847a688f2f4f9b16203ca4c835979a02549aa0595d9f457cc8", - "sha256:ab8a15c2750ae8d53e31f77a94f846d0a00772240f1c12817411fa2344351f86", - "sha256:af24b21c2283ca69c416a8a42cde9764dc36c63d3389645d28c69b0e93db3cd7", - "sha256:afe173ac2646c2636305ab820cc0380b22a00a7bca4290452e7166b4f4fa49d0", - "sha256:b9eb88593159a53a5ee0b0159daee531ff7dd9c87fa78f5d807ca059c7eb1b2b", - "sha256:c16635f8dddbeb1b827977d0b00d07b644b040aeb9ff8607a9fc0997afa3e567", - "sha256:ca3eefb02ef17257fae8b8555c85e7c1efdfd777f671384b0e4ef27409b02720", - "sha256:caa59e2cae0e23b1e225447d7a9ddb0f982f42a6a22d497a484dfe62a06f7c0e", - "sha256:cb55258931448d61e2d50187de4ee66fc9d9f34908b524949b8b2b93d0c57136", - "sha256:d248c46c0aa406695bda2abf99632db991f8b3a6d46018721a2892312a99f069", - "sha256:d2578e5149ff49878934debfacf5c743fab49eca5ecdb983d0b218e1e554c498", - "sha256:dd22085446f3eca990d12a0878eeb5199dc9553b2e71716bfe7bed9915a472ab", - "sha256:e7cf940af5fee00a92e281eb157abe8770227a5255207818ea9a34e54a29f5b2", - "sha256:f70f3d028794e31cf9d1a822914efc935aadb2438ec4e8d4871d95eb1ce032d6", - "sha256:fd2842a0faed9ab9aba0922c951906132d9384be89690570f0ed18cd4f20e658", - "sha256:fd628e63ffdba0112e3ddf1b1e9f3db29dd8262345138e08f4938acbc6d0805a", - "sha256:ffd7cf165ff71afb202b3f36daafbf298932bee325aac9f58e1c9cd55838bef0" - ], - "markers": "python_version >= '3.7'", - "version": "==1.4.3" - }, - "matplotlib": { - "hashes": [ - "sha256:03bbb3f5f78836855e127b5dab228d99551ad0642918ccbf3067fcd52ac7ac5e", - "sha256:24173c23d1bcbaed5bf47b8785d27933a1ac26a5d772200a0f3e0e38f471b001", - "sha256:2a0967d4156adbd0d46db06bc1a877f0370bce28d10206a5071f9ecd6dc60b79", - "sha256:2e8bda1088b941ead50caabd682601bece983cadb2283cafff56e8fcddbf7d7f", - "sha256:31fbc2af27ebb820763f077ec7adc79b5a031c2f3f7af446bd7909674cd59460", - "sha256:364e6bca34edc10a96aa3b1d7cd76eb2eea19a4097198c1b19e89bee47ed5781", - "sha256:3d8e129af95b156b41cb3be0d9a7512cc6d73e2b2109f82108f566dbabdbf377", - "sha256:44c6436868186564450df8fd2fc20ed9daaef5caad699aa04069e87099f9b5a8", - "sha256:48cf850ce14fa18067f2d9e0d646763681948487a8080ec0af2686468b4607a2", - "sha256:49a5938ed6ef9dda560f26ea930a2baae11ea99e1c2080c8714341ecfda72a89", - "sha256:4a05f2b37222319753a5d43c0a4fd97ed4ff15ab502113e3f2625c26728040cf", - "sha256:4a44cdfdb9d1b2f18b1e7d315eb3843abb097869cd1ef89cfce6a488cd1b5182", - "sha256:4fa28ca76ac5c2b2d54bc058b3dad8e22ee85d26d1ee1b116a6fd4d2277b6a04", - "sha256:5844cea45d804174bf0fac219b4ab50774e504bef477fc10f8f730ce2d623441", - "sha256:5a32ea6e12e80dedaca2d4795d9ed40f97bfa56e6011e14f31502fdd528b9c89", - "sha256:6c623b355d605a81c661546af7f24414165a8a2022cddbe7380a31a4170fa2e9", - "sha256:751d3815b555dcd6187ad35b21736dc12ce6925fc3fa363bbc6dc0f86f16484f", - "sha256:75c406c527a3aa07638689586343f4b344fcc7ab1f79c396699eb550cd2b91f7", - "sha256:77157be0fc4469cbfb901270c205e7d8adb3607af23cef8bd11419600647ceed", - "sha256:7d7705022df2c42bb02937a2a824f4ec3cca915700dd80dc23916af47ff05f1a", - "sha256:7f409716119fa39b03da3d9602bd9b41142fab7a0568758cd136cd80b1bf36c8", - "sha256:9480842d5aadb6e754f0b8f4ebeb73065ac8be1855baa93cd082e46e770591e9", - "sha256:9776e1a10636ee5f06ca8efe0122c6de57ffe7e8c843e0fb6e001e9d9256ec95", - "sha256:a91426ae910819383d337ba0dc7971c7cefdaa38599868476d94389a329e599b", - "sha256:b4fedaa5a9aa9ce14001541812849ed1713112651295fdddd640ea6620e6cf98", - "sha256:b6c63cd01cad0ea8704f1fd586e9dc5777ccedcd42f63cbbaa3eae8dd41172a1", - "sha256:b8d3f4e71e26307e8c120b72c16671d70c5cd08ae412355c11254aa8254fb87f", - "sha256:c4b82c2ae6d305fcbeb0eb9c93df2602ebd2f174f6e8c8a5d92f9445baa0c1d3", - "sha256:c772264631e5ae61f0bd41313bbe48e1b9bcc95b974033e1118c9caa1a84d5c6", - "sha256:c87973ddec10812bddc6c286b88fdd654a666080fbe846a1f7a3b4ba7b11ab78", - "sha256:e2b696699386766ef171a259d72b203a3c75d99d03ec383b97fc2054f52e15cf", - "sha256:ea75df8e567743207e2b479ba3d8843537be1c146d4b1e3e395319a4e1a77fe9", - "sha256:ebc27ad11df3c1661f4677a7762e57a8a91dd41b466c3605e90717c9a5f90c82", - "sha256:ee0b8e586ac07f83bb2950717e66cb305e2859baf6f00a9c39cc576e0ce9629c", - "sha256:ee175a571e692fc8ae8e41ac353c0e07259113f4cb063b0ec769eff9717e84bb" - ], - "markers": "python_version >= '3.7'", - "version": "==3.5.2" - }, - "mediapipe": { - "hashes": [ - "sha256:08626723288191079bf2059abf9bd07956afe43b7f188b451c658d36703d41ba", - "sha256:469c02465e5c43a1a738cf6094192022e4542724476ac4e19f34ea37f8b99160", - "sha256:48ac5b1d3bcd7b019e782a04a17401ec42a0e38075b8b392bae759bf2037e26f", - "sha256:4e066ab51cb3ad18e4c76cf1de272f2593e9c266479f461c013fd1196e4279e0", - "sha256:60f491638e97b2255c743bc07a182c343c1a90f2cd6613fe0cd4235f07ee44d8", - "sha256:696bcdcfdcb16b9f4c36227fc5187ddf4128f193bc3acd8e449ff89e954086ec", - "sha256:6d60e0a13659caf2699d2551f25c70dab2da95b57c0c21e4f73cdb225139aeb3", - "sha256:6eb42fa1f843e6c0d6e8fb8e6dc6b7713f6b7fde08df2742ca6df9d78cfe66fa", - "sha256:8ded8b564eb064957ed81b1e0787d5e93a909eb4201656550ead15948d4a57d5", - "sha256:996906048c9df74608bc833a6fab2bf34207b0d74d060dc7c10af3c0d6995a47", - "sha256:b2a3d29c9ad7b7650f41ed64038ea89b51e1d634328ad97d8b2ea0809143f3cd", - "sha256:c716d66959426fcbcb27f0df750ffcb5f33129fb4125cd3b932404d6f4a8a8c1" - ], - "index": "pypi", - "version": "==0.8.10.1" - }, - "numpy": { - "hashes": [ - "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450", - "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0", - "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160", - "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171", - "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a", - "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38", - "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10", - "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5", - "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f", - "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860", - "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd", - "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d", - "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05", - "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66", - "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187", - "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95", - "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3", - "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e", - "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f", - "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07", - "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc", - "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379" - ], - "index": "pypi", - "version": "==1.23.0" - }, - "opencv-contrib-python": { - "hashes": [ - "sha256:1dcdd0c68ae8495668f8a3360f7da5a24bc490320f21fc819bf60e197bf5bd7e", - "sha256:243411366820ba73f185867d0d9f9b8d42296070068222d5e3e40ecdcbbb3b39", - "sha256:256efeb263999f3fb2427095392c1cb442d655508cd0a42864a383d55f3e6d43", - "sha256:658ee41d6026614c7d4398fe7144b181e543d011ea190a0b80ec7a7d74ac744d", - "sha256:695911db4079216ffa5c3683885ccdfaba3059d279f9b96c14f9b8ec05570820", - "sha256:a338e1fb67fb25b30634cb34cd79328d4431f838964cfd7689f254012dd98ed9", - "sha256:d72fca3b0fb412cee3829cd5a2d30558953a6c378afccee3f4646daa9b12ab33" - ], - "markers": "python_version >= '3.6'", - "version": "==4.6.0.66" - }, - "opencv-python": { - "hashes": [ - "sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440", - "sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875", - "sha256:6e32af22e3202748bd233ed8f538741876191863882eba44e332d1a34993165b", - "sha256:c5bfae41ad4031e66bb10ec4a0a2ffd3e514d092652781e8b1ac98d1b59f1158", - "sha256:dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de", - "sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae", - "sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5" - ], - "index": "pypi", - "version": "==4.6.0.66" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, - "pandas": { - "hashes": [ - "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6", - "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d", - "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5", - "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76", - "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81", - "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1", - "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c", - "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf", - "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d", - "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e", - "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0", - "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230", - "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e", - "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84", - "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92", - "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f", - "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4", - "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640", - "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318", - "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef", - "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112" - ], - "index": "pypi", - "version": "==1.4.3" - }, - "pillow": { - "hashes": [ - "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", - "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", - "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", - "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", - "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", - "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", - "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", - "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", - "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", - "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", - "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", - "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", - "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", - "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", - "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", - "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", - "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", - "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", - "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", - "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", - "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", - "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", - "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", - "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", - "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", - "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", - "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", - "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", - "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", - "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", - "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", - "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", - "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", - "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", - "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", - "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", - "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", - "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", - "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", - "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", - "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", - "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", - "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", - "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", - "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", - "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", - "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", - "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", - "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", - "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", - "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", - "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", - "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", - "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", - "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", - "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", - "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", - "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" - ], - "markers": "python_version >= '3.7'", - "version": "==9.2.0" - }, - "protobuf": { - "hashes": [ - "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf", - "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f", - "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f", - "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7", - "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996", - "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067", - "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c", - "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7", - "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9", - "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c", - "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739", - "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91", - "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c", - "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153", - "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9", - "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388", - "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e", - "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab", - "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde", - "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531", - "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8", - "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7", - "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20", - "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3" - ], - "markers": "python_version >= '3.7'", - "version": "==3.20.1" - }, - "pyparsing": { - "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" - }, - "python-dateutil": { - "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" - }, - "pytz": { - "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" - ], - "version": "==2022.1" - }, - "scipy": { - "hashes": [ - "sha256:02b567e722d62bddd4ac253dafb01ce7ed8742cf8031aea030a41414b86c1125", - "sha256:1166514aa3bbf04cb5941027c6e294a000bba0cf00f5cdac6c77f2dad479b434", - "sha256:1da52b45ce1a24a4a22db6c157c38b39885a990a566748fc904ec9f03ed8c6ba", - "sha256:23b22fbeef3807966ea42d8163322366dd89da9bebdc075da7034cee3a1441ca", - "sha256:28d2cab0c6ac5aa131cc5071a3a1d8e1366dad82288d9ec2ca44df78fb50e649", - "sha256:2ef0fbc8bcf102c1998c1f16f15befe7cffba90895d6e84861cd6c6a33fb54f6", - "sha256:3b69b90c9419884efeffaac2c38376d6ef566e6e730a231e15722b0ab58f0328", - "sha256:4b93ec6f4c3c4d041b26b5f179a6aab8f5045423117ae7a45ba9710301d7e462", - "sha256:4e53a55f6a4f22de01ffe1d2f016e30adedb67a699a310cdcac312806807ca81", - "sha256:6311e3ae9cc75f77c33076cb2794fb0606f14c8f1b1c9ff8ce6005ba2c283621", - "sha256:65b77f20202599c51eb2771d11a6b899b97989159b7975e9b5259594f1d35ef4", - "sha256:6cc6b33139eb63f30725d5f7fa175763dc2df6a8f38ddf8df971f7c345b652dc", - "sha256:70de2f11bf64ca9921fda018864c78af7147025e467ce9f4a11bc877266900a6", - "sha256:70ebc84134cf0c504ce6a5f12d6db92cb2a8a53a49437a6bb4edca0bc101f11c", - "sha256:83606129247e7610b58d0e1e93d2c5133959e9cf93555d3c27e536892f1ba1f2", - "sha256:93d07494a8900d55492401917a119948ed330b8c3f1d700e0b904a578f10ead4", - "sha256:9c4e3ae8a716c8b3151e16c05edb1daf4cb4d866caa385e861556aff41300c14", - "sha256:9dd4012ac599a1e7eb63c114d1eee1bcfc6dc75a29b589ff0ad0bb3d9412034f", - "sha256:9e3fb1b0e896f14a85aa9a28d5f755daaeeb54c897b746df7a55ccb02b340f33", - "sha256:a0aa8220b89b2e3748a2836fbfa116194378910f1a6e78e4675a095bcd2c762d", - "sha256:d3b3c8924252caaffc54d4a99f1360aeec001e61267595561089f8b5900821bb", - "sha256:e013aed00ed776d790be4cb32826adb72799c61e318676172495383ba4570aa4", - "sha256:f3e7a8867f307e3359cc0ed2c63b61a1e33a19080f92fe377bc7d49f646f2ec1" - ], - "index": "pypi", - "version": "==1.8.1" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "tqdm": { - "hashes": [ - "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", - "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" - ], - "index": "pypi", - "version": "==4.64.0" - } - }, - "develop": { - "altgraph": { - "hashes": [ - "sha256:743628f2ac6a7c26f5d9223c91ed8ecbba535f506f4b6f558885a8a56a105857", - "sha256:ebf2269361b47d97b3b88e696439f6e4cbc607c17c51feb1754f90fb79839158" - ], - "version": "==0.17.2" - }, - "future": { - "hashes": [ - "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.18.2" - }, - "pefile": { - "hashes": [ - "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b" - ], - "markers": "sys_platform == 'win32'", - "version": "==2022.5.30" - }, - "pyinstaller": { - "hashes": [ - "sha256:29b3f17d75ff58b12db7b0116df1b3382c8345aefd500a177987eaf0599dec03", - "sha256:3c9bc373b4a4bf6d81b306b9918e290a3de582d7b50a0f6f4e837970a4db0c54", - "sha256:588f2840c27a8a7e1b1fa361f107a1060c5096b36d3c94c243b23687ee41609d", - "sha256:9596c70c860cbce19537354db95b180351959b4cd14a70db6ab1d1432668c313", - "sha256:996e296d1dfe27c3e89efc26dbcc247f49c0801a9b5e22e2b39f5b983cf5c2f5", - "sha256:a90ee3181c88aed0893bf537a50ad547655f5d555a5b731215a5fe1946d2f3db", - "sha256:ac003d49bbd62f6712b5631487049cee4f0a07445dd31c6d05e5724e33689fca", - "sha256:c421b46ebf81ad4498de480640ce1a47ac15dfd3938dfc2e1c0846cb5cdb0c59", - "sha256:d3852c1f8fe6ca2f5c7942c845dc6d1f6140aa7860fe3500eac3ac88875d1dd4", - "sha256:d8bfe5c32b4b5aaa91a5c1c7912fe3e5d13e7671f3468ee7742ec71ca192a975", - "sha256:f786cb013ebfd3fb23ddb46285902663e31256220b12b368f8c36e859a77ebc9" - ], - "index": "pypi", - "version": "==5.1" - }, - "pyinstaller-hooks-contrib": { - "hashes": [ - "sha256:5fdb97dcae177955db7ab27840cba97b89dc0c7f4fd9142bba0f9b8d8df85c48", - "sha256:6675634279cfe9e475580fb310c3d557037baefb065e6cb5a69a124361b926fd" - ], - "markers": "python_version >= '3.7'", - "version": "==2022.7" - }, - "pywin32-ctypes": { - "hashes": [ - "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", - "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" - ], - "markers": "sys_platform == 'win32'", - "version": "==0.2.0" - }, - "setuptools": { - "hashes": [ - "sha256:16923d366ced322712c71ccb97164d07472abeecd13f3a6c283f6d5d26722793", - "sha256:db3b8e2f922b2a910a29804776c643ea609badb6a32c4bcc226fd4fd902cce65" - ], - "markers": "python_version >= '3.7'", - "version": "==63.1.0" - } - } -} diff --git a/gaze_module.py b/gaze_module.py index ff35e15..0248120 100644 --- a/gaze_module.py +++ b/gaze_module.py @@ -3,6 +3,105 @@ import numpy as np import time +class EyeGazeEstimator(): + def __init__(self, face_mesh, frame_shape): + self.face_mesh = face_mesh + self.frame_shape = frame_shape + + # 3D estimation for a human face + self.model_points = np.array([ + (0.0, 0.0, 0.0), # Nose tip + (0, -63.6, -12.5), # Chin + (-43.3, 32.7, -26), # Left eye, left corner + (43.3, 32.7, -26), # Right eye, right corner + (-28.9, -28.9, -24.1), # Left Mouth corner + (28.9, -28.9, -24.1) # Right mouth corner + ]) + + # 3D model eye points + self.eye_ball_center_right = np.array([[-29.05], [32.7], [-39.5]]) + self.eye_ball_center_left = np.array([[29.05], [32.7], [-39.5]]) # the center of the left eyeball as a vector. + + # camera matrix estimation + self.focal_length = self.frame_shape[1] + self.camera_center = (self.frame_shape[1] / 2, self.frame_shape[0] / 2) + self.camera_matrix = np.array([ + [self.focal_length, 0, self.camera_center[0]], + [0, self.focal_length, self.camera_center[1]], + [0, 0, 1] + ], dtype="double") + + self.dist_coeffs = np.zeros((4, 1)) # Assuming no lens distortion + + def _relative(self, point): + """Relative takes mediapipe points that is normalized to [-1, 1] and returns image points""" + return (int(point.x * self.frame_shape[1]), int(point.y * self.frame_shape[0])) + + def _relativeT(self, point): + """RelativeT takes mediapipe points that is normalized to [-1, 1] and returns image points at (x,y,0) format""" + return (int(point.x * self.frame_shape[1]), int(point.y * self.frame_shape[0], 0)) + + def get_eye_gaze_vector(self, face_lms): + """Returns the eye gaze vector of the face_lms [left_eye_vector, right_eye_vector]""" + + # 2D image points + image_points_2D = np.array([ + self._relative(face_lms.landmark[4]), # Nose tip + self._relative(face_lms.landmark[152]), # Chin + self._relative(face_lms.landmark[263]), # Left eye left corner + self._relative(face_lms.landmark[33]), # Right eye right corner + self._relative(face_lms.landmark[287]), # Left Mouth corner + self._relative(face_lms.landmark[57]) # Right mouth corner + ], dtype="double") + + # 2D.5 image points (it still 3d, nut the z is 0) + image_points_2D5 = np.array([ + self._relativeT(face_lms.landmark[4]), # Nose tip + self._relativeT(face_lms.landmark[152]), # Chin + self._relativeT(face_lms.landmark[263]), # Left eye, left corner + self._relativeT(face_lms.landmark[33]), # Right eye, right corner + self._relativeT(face_lms.landmark[287]), # Left Mouth corner + self._relativeT(face_lms.landmark[57]) # Right mouth corner + ], dtype="double") + + # Solve the PnP problem + success, rotation_vector, translation_vector = cv2.solvePnP( + self.model_points, image_points_2D, self.camera_matrix, self.dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) + + # 2D Pupil location + left_eye = self._relative(face_lms.landmark[468]) + right_eye = self._relative(face_lms.landmark[473]) + + # Transformation between image point to world point + _, transformation = cv2.estimateAffine3D(image_points_2D5, self.model_points) + + if transformation is not None: # if estimateAffine3D succeeded + # Project pupil image point into 3D world point + left_pupil_world_cord = transformation @ np.array([[left_eye[0], left_eye[1], 0, 1]]).T + right_pupil_world_cord = transformation @ np.array([[right_eye[0], right_eye[1], 0, 1]]).T + + # 3D gaze point (10 is arbitrary) + gaze_distance_scale = 10 + left_S_vector = self.eye_ball_center_left + (left_pupil_world_cord - self.eye_ball_center_left) * gaze_distance_scale + right_S_vector = self.eye_ball_center_right + (right_pupil_world_cord - self.eye_ball_center_right) * gaze_distance_scale + + # Project a 3D gaze direction into the image plane + (left_eye_pupil_2d, _) = cv2.projectPoints(left_S_vector, rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) + (right_eye_pupil_2d, _) = cv2.projectPoints(right_S_vector, rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) + + # Project 3D head Pose into the image plane + (head_pose, _) = cv2.projectPoints((int(left_pupil_world_cord[0]), int(left_pupil_world_cord[1]), 40), rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) + + # correct gaze vector for the head pose + left_eye_gaze = left_eye + (left_eye_pupil_2d[0][0] - left_eye) - (head_pose[0][0] - left_eye) + right_eye_gaze = right_eye + (right_eye_pupil_2d[0][0] - right_eye) - (head_pose[0][0] - right_eye) + + print(f'Left Eye Gaze: {left_eye_gaze}') + print(f'Right Eye Gaze: {right_eye_gaze}') + + return [left_eye_gaze, right_eye_gaze] + + class HeadOrientationEstimator(): def __init__(self, lms_3d, img_h, img_w): self.face_3d = [] diff --git a/main.py b/main.py index 88c56e9..f3692be 100644 --- a/main.py +++ b/main.py @@ -106,6 +106,7 @@ def process_video(path): face_info.detect_pupil() face_info.detect_head_orientation() + face_info.detect_eyes_gaze() # salva os dados da face face_data_dict = face_info.get_position_data_as_dict() diff --git a/teste_eye_gaze/gaze.py b/teste_eye_gaze/gaze.py new file mode 100644 index 0000000..0f9bcb6 --- /dev/null +++ b/teste_eye_gaze/gaze.py @@ -0,0 +1,99 @@ +import cv2 +import numpy as np +from helpers import relative, relativeT + + +def gaze(frame, points): + """ + The gaze function gets an image and face landmarks from mediapipe framework. + The function draws the gaze direction into the frame. + """ + + ''' + 2D image points. + relative takes mediapipe points that is normalized to [-1, 1] and returns image points + at (x,y) format + ''' + image_points = np.array([ + relative(points.landmark[4], frame.shape), # Nose tip + relative(points.landmark[152], frame.shape), # Chin + relative(points.landmark[263], frame.shape), # Left eye left corner + relative(points.landmark[33], frame.shape), # Right eye right corner + relative(points.landmark[287], frame.shape), # Left Mouth corner + relative(points.landmark[57], frame.shape) # Right mouth corner + ], dtype="double") + + ''' + 2D image points. + relativeT takes mediapipe points that is normalized to [-1, 1] and returns image points + at (x,y,0) format + ''' + image_points1 = np.array([ + relativeT(points.landmark[4], frame.shape), # Nose tip + relativeT(points.landmark[152], frame.shape), # Chin + relativeT(points.landmark[263], frame.shape), # Left eye, left corner + relativeT(points.landmark[33], frame.shape), # Right eye, right corner + relativeT(points.landmark[287], frame.shape), # Left Mouth corner + relativeT(points.landmark[57], frame.shape) # Right mouth corner + ], dtype="double") + + # 3D model points. + model_points = np.array([ + (0.0, 0.0, 0.0), # Nose tip + (0, -63.6, -12.5), # Chin + (-43.3, 32.7, -26), # Left eye, left corner + (43.3, 32.7, -26), # Right eye, right corner + (-28.9, -28.9, -24.1), # Left Mouth corner + (28.9, -28.9, -24.1) # Right mouth corner + ]) + + ''' + 3D model eye points + The center of the eye ball + ''' + Eye_ball_center_right = np.array([[-29.05], [32.7], [-39.5]]) + Eye_ball_center_left = np.array([[29.05], [32.7], [-39.5]]) # the center of the left eyeball as a vector. + + ''' + camera matrix estimation + ''' + focal_length = frame.shape[1] + center = (frame.shape[1] / 2, frame.shape[0] / 2) + camera_matrix = np.array( + [[focal_length, 0, center[0]], + [0, focal_length, center[1]], + [0, 0, 1]], dtype="double" + ) + + dist_coeffs = np.zeros((4, 1)) # Assuming no lens distortion + (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix, + dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) + + # 2d pupil location + left_pupil = relative(points.landmark[468], frame.shape) + right_pupil = relative(points.landmark[473], frame.shape) + + # Transformation between image point to world point + _, transformation, _ = cv2.estimateAffine3D(image_points1, model_points) # image to world transformation + + if transformation is not None: # if estimateAffine3D secsseded + # project pupil image point into 3d world point + pupil_world_cord = transformation @ np.array([[left_pupil[0], left_pupil[1], 0, 1]]).T + + # 3D gaze point (10 is arbitrary value denoting gaze distance) + S = Eye_ball_center_left + (pupil_world_cord - Eye_ball_center_left) * 10 + + # Project a 3D gaze direction onto the image plane. + (eye_pupil2D, _) = cv2.projectPoints((int(S[0]), int(S[1]), int(S[2])), rotation_vector, + translation_vector, camera_matrix, dist_coeffs) + # project 3D head pose into the image plane + (head_pose, _) = cv2.projectPoints((int(pupil_world_cord[0]), int(pupil_world_cord[1]), int(40)), + rotation_vector, + translation_vector, camera_matrix, dist_coeffs) + # correct gaze for head rotation + gaze = left_pupil + (eye_pupil2D[0][0] - left_pupil) - (head_pose[0][0] - left_pupil) + + # Draw gaze line into screen + p1 = (int(left_pupil[0]), int(left_pupil[1])) + p2 = (int(gaze[0]), int(gaze[1])) + cv2.line(frame, p1, p2, (0, 0, 255), 2) diff --git a/teste_eye_gaze/helpers.py b/teste_eye_gaze/helpers.py new file mode 100644 index 0000000..86d1715 --- /dev/null +++ b/teste_eye_gaze/helpers.py @@ -0,0 +1,2 @@ +relative = lambda landmark, shape: (int(landmark.x * shape[1]), int(landmark.y * shape[0])) +relativeT = lambda landmark, shape: (int(landmark.x * shape[1]), int(landmark.y * shape[0]), 0) \ No newline at end of file diff --git a/teste_eye_gaze/main.py b/teste_eye_gaze/main.py new file mode 100644 index 0000000..5d75201 --- /dev/null +++ b/teste_eye_gaze/main.py @@ -0,0 +1,41 @@ +import mediapipe as mp +import cv2 +import gaze + +mp_face_mesh = mp.solutions.face_mesh # initialize the face mesh model + +# camera stream: +cap = cv2.VideoCapture(0) # chose camera index (try 1, 2, 3) +# open kid.avi as the cap +# cap = cv2.VideoCapture('kid.avi') +with mp_face_mesh.FaceMesh( + max_num_faces=1, # number of faces to track in each frame + refine_landmarks=True, # includes iris landmarks in the face mesh model + min_detection_confidence=0.5, + min_tracking_confidence=0.5) as face_mesh: + while cap.isOpened(): + success, image = cap.read() + if not success: # no frame input + print("Ignoring empty camera frame.") + continue + # To improve performance, optionally mark the image as not writeable to + # pass by reference. + image.flags.writeable = False + image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # frame to RGB for the face-mesh model + results = face_mesh.process(image) + image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # frame back to BGR for OpenCV + + if results.multi_face_landmarks: + gaze.gaze(image, results.multi_face_landmarks[0]) # gaze estimation + + # for point in results.multi_face_landmarks[0].landmark: + # x = int(point.x * image.shape[1]) + # y = int(point.y * image.shape[0]) + # cv2.circle(image, (x, y), 2, (0, 255, 0), -1) + + + + cv2.imshow('output window', image) + if cv2.waitKey(2) & 0xFF == 27: + break +cap.release() From daef56cdbf8e6fa79bf543a0520d78d8463aa0f9 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 11 Apr 2024 19:38:55 -0300 Subject: [PATCH 12/25] pego o gaze de cada olho, ainda falta salvar --- .gitignore | 3 +- Face.py | 4 +- Pipfile.lock | 842 +++++++++++++++++++++++++++++++++++++++++ drawing_utils.py | 25 ++ face_mesh_module.py | 2 +- gaze_module.py | 60 +-- main.py | 10 +- options.txt | 13 +- path.txt | 2 +- requirements.txt | 6 + teste_eye_gaze/gaze.py | 6 +- 11 files changed, 926 insertions(+), 47 deletions(-) create mode 100644 Pipfile.lock create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index b44c572..d605e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ vds/* build/* dst/mediapippe/* mediapipe/* -dst/vds/* \ No newline at end of file +dst/vds/* +Pipefile.lock \ No newline at end of file diff --git a/Face.py b/Face.py index fd52e9e..dda0797 100644 --- a/Face.py +++ b/Face.py @@ -34,8 +34,8 @@ def detect_head_orientation(self): self.head_orientation_vector = head_orientation_vector def detect_eyes_gaze(self): - self._eye_gaze_estimator = EyeGazeEstimator(self.lms_3d, self.image.shape) - left_eye_gaze, right_eye_gaze = self._eye_gaze_estimator.get_eye_gaze() + self._eye_gaze_estimator = EyeGazeEstimator(self.image.shape) + self.left_eye_gaze, self.right_eye_gaze = self._eye_gaze_estimator.get_eye_gaze_vector(self.lms_3d) def get_position_data_as_dict(self): diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..c33741c --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,842 @@ +{ + "_meta": { + "hash": { + "sha256": "09da273f2b9750e8276b384d90369dce023f495ccfc232d58163f100d312b3c4" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "absl-py": { + "hashes": [ + "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", + "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.0" + }, + "attrs": { + "hashes": [ + "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2.0" + }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "python_version >= '3.8'", + "version": "==1.16.0" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.6" + }, + "contourpy": { + "hashes": [ + "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2", + "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9", + "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9", + "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4", + "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce", + "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7", + "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f", + "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922", + "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4", + "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e", + "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b", + "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619", + "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205", + "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480", + "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965", + "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c", + "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd", + "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5", + "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f", + "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc", + "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec", + "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd", + "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b", + "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9", + "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe", + "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce", + "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609", + "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8", + "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0", + "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f", + "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8", + "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b", + "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364", + "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040", + "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f", + "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083", + "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df", + "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba", + "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445", + "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da", + "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3", + "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72", + "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02", + "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985" + ], + "markers": "python_version >= '3.9'", + "version": "==1.2.1" + }, + "cycler": { + "hashes": [ + "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", + "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "flatbuffers": { + "hashes": [ + "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812", + "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4" + ], + "version": "==24.3.25" + }, + "fonttools": { + "hashes": [ + "sha256:0118ef998a0699a96c7b28457f15546815015a2710a1b23a7bf6c1be60c01636", + "sha256:0d145976194a5242fdd22df18a1b451481a88071feadf251221af110ca8f00ce", + "sha256:0e19bd9e9964a09cd2433a4b100ca7f34e34731e0758e13ba9a1ed6e5468cc0f", + "sha256:0f08c901d3866a8905363619e3741c33f0a83a680d92a9f0e575985c2634fcc1", + "sha256:1250e818b5f8a679ad79660855528120a8f0288f8f30ec88b83db51515411fcc", + "sha256:15c94eeef6b095831067f72c825eb0e2d48bb4cea0647c1b05c981ecba2bf39f", + "sha256:1621ee57da887c17312acc4b0e7ac30d3a4fb0fec6174b2e3754a74c26bbed1e", + "sha256:180194c7fe60c989bb627d7ed5011f2bef1c4d36ecf3ec64daec8302f1ae0716", + "sha256:278e50f6b003c6aed19bae2242b364e575bcb16304b53f2b64f6551b9c000e15", + "sha256:32b17504696f605e9e960647c5f64b35704782a502cc26a37b800b4d69ff3c77", + "sha256:3bee3f3bd9fa1d5ee616ccfd13b27ca605c2b4270e45715bd2883e9504735034", + "sha256:4060acc2bfa2d8e98117828a238889f13b6f69d59f4f2d5857eece5277b829ba", + "sha256:54dcf21a2f2d06ded676e3c3f9f74b2bafded3a8ff12f0983160b13e9f2fb4a7", + "sha256:56fc244f2585d6c00b9bcc59e6593e646cf095a96fe68d62cd4da53dd1287b55", + "sha256:599bdb75e220241cedc6faebfafedd7670335d2e29620d207dd0378a4e9ccc5a", + "sha256:5f6bc991d1610f5c3bbe997b0233cbc234b8e82fa99fc0b2932dc1ca5e5afec0", + "sha256:60a3409c9112aec02d5fb546f557bca6efa773dcb32ac147c6baf5f742e6258b", + "sha256:68b3fb7775a923be73e739f92f7e8a72725fd333eab24834041365d2278c3671", + "sha256:76f1777d8b3386479ffb4a282e74318e730014d86ce60f016908d9801af9ca2a", + "sha256:806e7912c32a657fa39d2d6eb1d3012d35f841387c8fc6cf349ed70b7c340039", + "sha256:84d7751f4468dd8cdd03ddada18b8b0857a5beec80bce9f435742abc9a851a74", + "sha256:865a58b6e60b0938874af0968cd0553bcd88e0b2cb6e588727117bd099eef836", + "sha256:8ac27f436e8af7779f0bb4d5425aa3535270494d3bc5459ed27de3f03151e4c2", + "sha256:8b4850fa2ef2cfbc1d1f689bc159ef0f45d8d83298c1425838095bf53ef46308", + "sha256:8b5ad456813d93b9c4b7ee55302208db2b45324315129d85275c01f5cb7e61a2", + "sha256:8e2f1a4499e3b5ee82c19b5ee57f0294673125c65b0a1ff3764ea1f9db2f9ef5", + "sha256:9696fe9f3f0c32e9a321d5268208a7cc9205a52f99b89479d1b035ed54c923f1", + "sha256:96a48e137c36be55e68845fc4284533bda2980f8d6f835e26bca79d7e2006438", + "sha256:a8feca65bab31479d795b0d16c9a9852902e3a3c0630678efb0b2b7941ea9c74", + "sha256:aefa011207ed36cd280babfaa8510b8176f1a77261833e895a9d96e57e44802f", + "sha256:b2b92381f37b39ba2fc98c3a45a9d6383bfc9916a87d66ccb6553f7bdd129097", + "sha256:b3c61423f22165541b9403ee39874dcae84cd57a9078b82e1dce8cb06b07fa2e", + "sha256:b5b48a1121117047d82695d276c2af2ee3a24ffe0f502ed581acc2673ecf1037", + "sha256:c18b49adc721a7d0b8dfe7c3130c89b8704baf599fb396396d07d4aa69b824a1", + "sha256:c5b8cab0c137ca229433570151b5c1fc6af212680b58b15abd797dcdd9dd5051", + "sha256:c7e91abdfae1b5c9e3a543f48ce96013f9a08c6c9668f1e6be0beabf0a569c1b", + "sha256:cadf4e12a608ef1d13e039864f484c8a968840afa0258b0b843a0556497ea9ed", + "sha256:dc0673361331566d7a663d7ce0f6fdcbfbdc1f59c6e3ed1165ad7202ca183c68", + "sha256:de7c29bdbdd35811f14493ffd2534b88f0ce1b9065316433b22d63ca1cd21f14", + "sha256:e9d9298be7a05bb4801f558522adbe2feea1b0b103d5294ebf24a92dd49b78e5", + "sha256:ee1af4be1c5afe4c96ca23badd368d8dc75f611887fb0c0dac9f71ee5d6f110e", + "sha256:f7e89853d8bea103c8e3514b9f9dc86b5b4120afb4583b57eb10dfa5afbe0936" + ], + "markers": "python_version >= '3.8'", + "version": "==4.51.0" + }, + "importlib-metadata": { + "hashes": [ + "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" + ], + "markers": "python_version < '3.10'", + "version": "==7.1.0" + }, + "importlib-resources": { + "hashes": [ + "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c", + "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145" + ], + "markers": "python_version < '3.10'", + "version": "==6.4.0" + }, + "jax": { + "hashes": [ + "sha256:2cce025d0a279ec630d550524749bc8efe25d2ff47240d2a7d4cfbc5090c5383", + "sha256:50dc795148ee6b0735b48b477e5abc556aa3a4c7af5d6940dad08024a908b02f" + ], + "markers": "python_version >= '3.9'", + "version": "==0.4.26" + }, + "kiwisolver": { + "hashes": [ + "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf", + "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", + "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af", + "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", + "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046", + "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", + "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", + "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71", + "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee", + "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3", + "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9", + "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b", + "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985", + "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea", + "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", + "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89", + "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c", + "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", + "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712", + "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342", + "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", + "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958", + "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d", + "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a", + "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130", + "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", + "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898", + "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b", + "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f", + "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265", + "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93", + "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929", + "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635", + "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709", + "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b", + "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb", + "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a", + "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920", + "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e", + "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544", + "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", + "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390", + "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77", + "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", + "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff", + "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4", + "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", + "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", + "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c", + "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", + "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", + "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", + "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc", + "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a", + "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901", + "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", + "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770", + "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525", + "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad", + "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", + "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29", + "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", + "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250", + "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d", + "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3", + "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54", + "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f", + "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1", + "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da", + "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238", + "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa", + "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523", + "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", + "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205", + "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3", + "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4", + "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", + "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", + "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb", + "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced", + "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd", + "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0", + "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", + "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18", + "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", + "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276", + "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333", + "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b", + "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", + "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126", + "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", + "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09", + "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0", + "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", + "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7", + "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff", + "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9", + "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", + "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", + "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", + "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6", + "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", + "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892", + "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f" + ], + "markers": "python_version >= '3.7'", + "version": "==1.4.5" + }, + "matplotlib": { + "hashes": [ + "sha256:1c13f041a7178f9780fb61cc3a2b10423d5e125480e4be51beaf62b172413b67", + "sha256:232ce322bfd020a434caaffbd9a95333f7c2491e59cfc014041d95e38ab90d1c", + "sha256:493e9f6aa5819156b58fce42b296ea31969f2aab71c5b680b4ea7a3cb5c07d94", + "sha256:50bac6e4d77e4262c4340d7a985c30912054745ec99756ce213bfbc3cb3808eb", + "sha256:606e3b90897554c989b1e38a258c626d46c873523de432b1462f295db13de6f9", + "sha256:6209e5c9aaccc056e63b547a8152661324404dd92340a6e479b3a7f24b42a5d0", + "sha256:6485ac1f2e84676cff22e693eaa4fbed50ef5dc37173ce1f023daef4687df616", + "sha256:6addbd5b488aedb7f9bc19f91cd87ea476206f45d7116fcfe3d31416702a82fa", + "sha256:72f9322712e4562e792b2961971891b9fbbb0e525011e09ea0d1f416c4645661", + "sha256:7a6769f58ce51791b4cb8b4d7642489df347697cd3e23d88266aaaee93b41d9a", + "sha256:8080d5081a86e690d7688ffa542532e87f224c38a6ed71f8fbed34dd1d9fedae", + "sha256:843cbde2f0946dadd8c5c11c6d91847abd18ec76859dc319362a0964493f0ba6", + "sha256:8aac397d5e9ec158960e31c381c5ffc52ddd52bd9a47717e2a694038167dffea", + "sha256:8f65c9f002d281a6e904976007b2d46a1ee2bcea3a68a8c12dda24709ddc9106", + "sha256:90df07db7b599fe7035d2f74ab7e438b656528c68ba6bb59b7dc46af39ee48ef", + "sha256:9bb0189011785ea794ee827b68777db3ca3f93f3e339ea4d920315a0e5a78d54", + "sha256:a0e47eda4eb2614300fc7bb4657fced3e83d6334d03da2173b09e447418d499f", + "sha256:abc9d838f93583650c35eca41cfcec65b2e7cb50fd486da6f0c49b5e1ed23014", + "sha256:ac24233e8f2939ac4fd2919eed1e9c0871eac8057666070e94cbf0b33dd9c338", + "sha256:b12ba985837e4899b762b81f5b2845bd1a28f4fdd1a126d9ace64e9c4eb2fb25", + "sha256:b7a2a253d3b36d90c8993b4620183b55665a429da8357a4f621e78cd48b2b30b", + "sha256:c7064120a59ce6f64103c9cefba8ffe6fba87f2c61d67c401186423c9a20fd35", + "sha256:c89ee9314ef48c72fe92ce55c4e95f2f39d70208f9f1d9db4e64079420d8d732", + "sha256:cc4ccdc64e3039fc303defd119658148f2349239871db72cd74e2eeaa9b80b71", + "sha256:ce1edd9f5383b504dbc26eeea404ed0a00656c526638129028b758fd43fc5f10", + "sha256:ecd79298550cba13a43c340581a3ec9c707bd895a6a061a78fa2524660482fc0", + "sha256:f51c4c869d4b60d769f7b4406eec39596648d9d70246428745a681c327a8ad30", + "sha256:fb44f53af0a62dc80bba4443d9b27f2fde6acfdac281d95bc872dc148a6509cc" + ], + "markers": "python_version >= '3.9'", + "version": "==3.8.4" + }, + "mediapipe": { + "hashes": [ + "sha256:0e612a978b244b0cd89209f6349fcca407891a928988fcc3b2d5d465f8f6a319", + "sha256:23c21cc83cf2d9535d5e7ab998fc6caef9eebcace8a14c164dadacebee57aadc", + "sha256:36231eaf23cd795a923a8b015d36bd6e410a8e997c36dd9432db0157b822b181", + "sha256:3a23f8ba919ff9454f8c102f6c20d6d0226c1920a1058f2b817d5998fcde8cbe", + "sha256:3cc6bfefe82bdb61a1ce373c79ff65afe794e143917470a0a227fcf1dbfdbe90", + "sha256:56fd0cb6948ea13d0d00bcebbef133b8a3955a84454ae29be85212d0d3abc968", + "sha256:82779e206b880588b2f9b9c510e3e3eb757b6494660736f3d6fa6472b20ee9e1", + "sha256:83fdc8efa821fe0aa24bc7a4469fd1a08531b651a4fffc8be0796e43c6c657b2", + "sha256:849e2e4b2b6a155d354ab1cd4e2daedaa9aa90032d55baba8c63d720277d52da", + "sha256:863b429c96eedc3414a99e35ebefb73a5ca08a2aa81caa39aae7177d44f1ce9c", + "sha256:8e6dafe84b263df2b517fb3944ca558b5b123fe85c7c3267b0e62508dc7612cc", + "sha256:9ad3fa29de8c3b3aff98e806355e1b6118944d77898658115e8fd95232c3131a", + "sha256:9e508fa6e9a69dd602248f710b3337e9333e1abca4e2fcb3ac9d6dd5d731220d", + "sha256:dbbf88c95e65af62d077786d64349cf92ddb12e471b262406e6a49b42a4c00ae", + "sha256:ea751e043909ba7bbe27e7afdbcdafd79723d50ef4165afcaae431ab428eea13", + "sha256:fc5283a50227a93d7755fd0f83d0d6daeb0f1c841df1ac9101e96e32e7e03ba1" + ], + "index": "pypi", + "version": "==0.10.11" + }, + "ml-dtypes": { + "hashes": [ + "sha256:03e7cda6ef164eed0abb31df69d2c00c3a5ab3e2610b6d4c42183a43329c72a5", + "sha256:2bb83fd064db43e67e67d021e547698af4c8d5c6190f2e9b1c53c09f6ff5531d", + "sha256:3b67ec73a697c88c1122038e0de46520e48dc2ec876d42cf61bc5efe3c0b7675", + "sha256:41affb38fdfe146e3db226cf2953021184d6f0c4ffab52136613e9601706e368", + "sha256:43cf4356a0fe2eeac6d289018d0734e17a403bdf1fd911953c125dd0358edcc0", + "sha256:723af6346447268a3cf0b7356e963d80ecb5732b5279b2aa3fa4b9fc8297c85e", + "sha256:75b4faf99d0711b81f393db36d210b4255fd419f6f790bc6c1b461f95ffb7a9e", + "sha256:93afe37f3a879d652ec9ef1fc47612388890660a2657fbb5747256c3b818fd81", + "sha256:a15d96d090aebb55ee85173d1775ae325a001aab607a76c8ea0b964ccd6b5364", + "sha256:ad6849a2db386b38e4d54fe13eb3293464561780531a918f8ef4c8169170dd49", + "sha256:bdf689be7351cc3c95110c910c1b864002f113e682e44508910c849e144f3df1", + "sha256:c83e4d443962d891d51669ff241d5aaad10a8d3d37a81c5532a45419885d591c", + "sha256:e1e2f4237b459a63c97c2c9f449baa637d7e4c20addff6a9bac486f22432f3b6", + "sha256:eaa32979ebfde3a0d7c947cafbf79edc1ec77ac05ad0780ee86c1d8df70f2259", + "sha256:eaf197e72f4f7176a19fe3cb8b61846b38c6757607e7bf9cd4b1d84cd3e74deb", + "sha256:ee9f91d4c4f9959a7e1051c141dc565f39e54435618152219769e24f5e9a4d06", + "sha256:f1724ddcdf5edbaf615a62110af47407f1719b8d02e68ccee60683acb5f74da1" + ], + "markers": "python_version >= '3.9'", + "version": "==0.4.0" + }, + "numpy": { + "hashes": [ + "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", + "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", + "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", + "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", + "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", + "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", + "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", + "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", + "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", + "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", + "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", + "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", + "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", + "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", + "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", + "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", + "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", + "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", + "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", + "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", + "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", + "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", + "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", + "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", + "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", + "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", + "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", + "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", + "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", + "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", + "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", + "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", + "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", + "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", + "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", + "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.26.4" + }, + "opencv-contrib-python": { + "hashes": [ + "sha256:4ce33844dbdf4bcbb0bd1ea7249367ffccab82de09f531ad25ac258aca5f813c", + "sha256:760e76c535da81e50f5a5a0acd85bada376f505736875eb446811b4b76fb9bb5", + "sha256:86078d3653ec3107877536c9178622b1f98b51acf59e554ddbc552785cba55fa", + "sha256:89ca1508dd895ae42176640bdd503cac82772f6efa25120738a469a6a69de321", + "sha256:b52e381144f774b486729ccee69911bdc7d16b5ced4830502e906ad803373ab0", + "sha256:fdd9b14028f74af8dbb69f90e6e4a956ce2eb5b59947df28ba0b79d337431477", + "sha256:ff1c00b06e2c3f4ad47de64fa9c527d67f7c6fce93f513b64163d41bea7c41b2" + ], + "markers": "python_version >= '3.6'", + "version": "==4.9.0.80" + }, + "opencv-python": { + "hashes": [ + "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1", + "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0", + "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3", + "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a", + "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb", + "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c", + "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==4.9.0.80" + }, + "opt-einsum": { + "hashes": [ + "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147", + "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549" + ], + "markers": "python_version >= '3.5'", + "version": "==3.3.0" + }, + "packaging": { + "hashes": [ + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + ], + "markers": "python_version >= '3.7'", + "version": "==24.0" + }, + "pandas": { + "hashes": [ + "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863", + "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2", + "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", + "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", + "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", + "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", + "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51", + "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", + "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08", + "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", + "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4", + "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921", + "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", + "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", + "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0", + "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", + "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99", + "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", + "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce", + "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57", + "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", + "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", + "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a", + "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", + "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772", + "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", + "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.2.2" + }, + "pillow": { + "hashes": [ + "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c", + "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2", + "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb", + "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d", + "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa", + "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3", + "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1", + "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a", + "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd", + "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8", + "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999", + "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599", + "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936", + "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375", + "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d", + "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b", + "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60", + "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572", + "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3", + "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced", + "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f", + "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b", + "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19", + "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f", + "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d", + "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383", + "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795", + "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355", + "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57", + "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09", + "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b", + "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462", + "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf", + "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f", + "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a", + "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad", + "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9", + "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d", + "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45", + "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994", + "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d", + "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338", + "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463", + "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451", + "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591", + "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c", + "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd", + "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32", + "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9", + "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf", + "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5", + "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828", + "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3", + "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5", + "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2", + "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b", + "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2", + "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475", + "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3", + "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb", + "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef", + "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015", + "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002", + "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170", + "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84", + "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57", + "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f", + "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27", + "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a" + ], + "markers": "python_version >= '3.8'", + "version": "==10.3.0" + }, + "protobuf": { + "hashes": [ + "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7", + "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c", + "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2", + "sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b", + "sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050", + "sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9", + "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7", + "sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454", + "sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480", + "sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469", + "sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c", + "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e", + "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db", + "sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905", + "sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b", + "sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86", + "sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4", + "sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402", + "sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7", + "sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4", + "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99", + "sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee" + ], + "markers": "python_version >= '3.7'", + "version": "==3.20.3" + }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, + "pyparsing": { + "hashes": [ + "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", + "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.1.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, + "pytz": { + "hashes": [ + "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", + "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" + ], + "version": "==2024.1" + }, + "scipy": { + "hashes": [ + "sha256:05f1432ba070e90d42d7fd836462c50bf98bd08bed0aa616c359eed8a04e3922", + "sha256:09c74543c4fbeb67af6ce457f6a6a28e5d3739a87f62412e4a16e46f164f0ae5", + "sha256:0fbcf8abaf5aa2dc8d6400566c1a727aed338b5fe880cde64907596a89d576fa", + "sha256:109d391d720fcebf2fbe008621952b08e52907cf4c8c7efc7376822151820820", + "sha256:1d2f7bb14c178f8b13ebae93f67e42b0a6b0fc50eba1cd8021c9b6e08e8fb1cd", + "sha256:1e7626dfd91cdea5714f343ce1176b6c4745155d234f1033584154f60ef1ff42", + "sha256:22789b56a999265431c417d462e5b7f2b487e831ca7bef5edeb56efe4c93f86e", + "sha256:28e286bf9ac422d6beb559bc61312c348ca9b0f0dae0d7c5afde7f722d6ea13d", + "sha256:33fde20efc380bd23a78a4d26d59fc8704e9b5fd9b08841693eb46716ba13d86", + "sha256:45c08bec71d3546d606989ba6e7daa6f0992918171e2a6f7fbedfa7361c2de1e", + "sha256:4dca18c3ffee287ddd3bc8f1dabaf45f5305c5afc9f8ab9cbfab855e70b2df5c", + "sha256:5407708195cb38d70fd2d6bb04b1b9dd5c92297d86e9f9daae1576bd9e06f602", + "sha256:58569af537ea29d3f78e5abd18398459f195546bb3be23d16677fb26616cc11e", + "sha256:5e4a756355522eb60fcd61f8372ac2549073c8788f6114449b37e9e8104f15a5", + "sha256:6bf9fe63e7a4bf01d3645b13ff2aa6dea023d38993f42aaac81a18b1bda7a82a", + "sha256:8930ae3ea371d6b91c203b1032b9600d69c568e537b7988a3073dfe4d4774f21", + "sha256:9ff7dad5d24a8045d836671e082a490848e8639cabb3dbdacb29f943a678683d", + "sha256:a2f471de4d01200718b2b8927f7d76b5d9bde18047ea0fa8bd15c5ba3f26a1d6", + "sha256:ac38c4c92951ac0f729c4c48c9e13eb3675d9986cc0c83943784d7390d540c78", + "sha256:b2a3ff461ec4756b7e8e42e1c681077349a038f0686132d623fa404c0bee2551", + "sha256:b5acd8e1dbd8dbe38d0004b1497019b2dbbc3d70691e65d69615f8a7292865d7", + "sha256:b8434f6f3fa49f631fae84afee424e2483289dfc30a47755b4b4e6b07b2633a4", + "sha256:ba419578ab343a4e0a77c0ef82f088238a93eef141b2b8017e46149776dfad4d", + "sha256:d0de696f589681c2802f9090fff730c218f7c51ff49bf252b6a97ec4a5d19e8b", + "sha256:dcbb9ea49b0167de4167c40eeee6e167caeef11effb0670b554d10b1e693a8b9" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.13.0" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.16.0" + }, + "sounddevice": { + "hashes": [ + "sha256:3236b78f15f0415bdf006a620cef073d0c0522851d66f4a961ed6d8eb1482fe9", + "sha256:5de768ba6fe56ad2b5aaa2eea794b76b73e427961c95acad2ee2ed7f866a4b20", + "sha256:7830d4f8f8570f2e5552942f81d96999c5fcd9a0b682d6fc5d5c5529df23be2c", + "sha256:8b0b806c205dd3e3cd5a97262b2482624fd21db7d47083b887090148a08051c8", + "sha256:e3ba6e674ffa8f79a591d744a1d4ab922fe5bdfd4faf8b25069a08e051010b7b" + ], + "markers": "python_version >= '3.7'", + "version": "==0.4.6" + }, + "tqdm": { + "hashes": [ + "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9", + "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==4.66.2" + }, + "tzdata": { + "hashes": [ + "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", + "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" + ], + "markers": "python_version >= '2'", + "version": "==2024.1" + }, + "zipp": { + "hashes": [ + "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b", + "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715" + ], + "markers": "python_version >= '3.8'", + "version": "==3.18.1" + } + }, + "develop": { + "altgraph": { + "hashes": [ + "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", + "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff" + ], + "version": "==0.17.4" + }, + "importlib-metadata": { + "hashes": [ + "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" + ], + "markers": "python_version < '3.10'", + "version": "==7.1.0" + }, + "packaging": { + "hashes": [ + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + ], + "markers": "python_version >= '3.7'", + "version": "==24.0" + }, + "pefile": { + "hashes": [ + "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", + "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6" + ], + "markers": "sys_platform == 'win32'", + "version": "==2023.2.7" + }, + "pyinstaller": { + "hashes": [ + "sha256:0dae0edbe6d667b6b0ccd8c97a148f86474a82da7ce582296f9025f4c7242ec6", + "sha256:1b3b7d6d3b18d76a833fd5a4d7f4544c5e2c2a4db4a728ea191e62f69d5cc33c", + "sha256:5f432f3fdef053989e0a44134e483131c533dab7637e6afd80c3f7c26e6dbcc9", + "sha256:61865eee5e0d8f8252722f6d001baec497b7cee79ebe62c33a6ba86ba0c7010d", + "sha256:6cfee8a74ea2d3a1dc8e99e732a87b314739dc14363778143caac31f8aee9039", + "sha256:6ffd76a0194dac4df5e66dcfccc7b597f3eaa40ef9a3f63548f260aa2c187512", + "sha256:7c76bfcb624803c311fa8fb137e4780d0ec86d11b7d90a8f43f185e2554afdcc", + "sha256:81ec15c0deb8c7a0f95bea85b49eecc2df1bdeaf5fe487a41d97de6b0ad29dff", + "sha256:9d828213aea5401bb33a36ca396f8dc76a59a25bce1d76a13c9ad94ba29fbe42", + "sha256:a54968df2228f0128607b1dced41bbff94149d459987fb5cd1a41893e9bb85df", + "sha256:b1e55113c5a40cb7041c908a57f212f3ebd3e444dbb245ca2f91d86a76dabec5", + "sha256:e1266498893ce1d6cc7337e8d2acbf7905a10ed2b7c8377270117d6b7b922fc4" + ], + "index": "pypi", + "markers": "python_version < '3.13' and python_version >= '3.8'", + "version": "==6.5.0" + }, + "pyinstaller-hooks-contrib": { + "hashes": [ + "sha256:6701752d525e1f4eda1eaec2c2affc206171e15c7a4e188a152fcf3ed3308024", + "sha256:d18657c29267c63563a96b8fc78db6ba9ae40af6702acb2f8c871df12c75b60b" + ], + "markers": "python_version >= '3.7'", + "version": "==2024.3" + }, + "pywin32-ctypes": { + "hashes": [ + "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60", + "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.2.2" + }, + "setuptools": { + "hashes": [ + "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e", + "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c" + ], + "markers": "python_version >= '3.8'", + "version": "==69.2.0" + }, + "zipp": { + "hashes": [ + "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b", + "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715" + ], + "markers": "python_version >= '3.8'", + "version": "==3.18.1" + } + } +} diff --git a/drawing_utils.py b/drawing_utils.py index 1ffe7cf..44fac68 100644 --- a/drawing_utils.py +++ b/drawing_utils.py @@ -5,6 +5,31 @@ from definitions import * +def draw_eye_gaze(image, left_pupil, left_gaze, right_pupil, right_gaze): + if(left_pupil == 0 or right_pupil == 0 or left_pupil == None or right_pupil == None): + return image + left_pupil_pos, left_pupil_radius = left_pupil + right_pupil_pos, right_pupil_radius = right_pupil + + if left_gaze is not None: + left_gaze_x, left_gaze_y = left_gaze + + p1_left = (int(left_pupil_pos[0]), int(left_pupil_pos[1])) + p2_left = (int(left_gaze[0]), int(left_gaze[1])) + + image = cv2.line(image, p1_left, p2_left, (0, 0, 255), 2) + + + if right_gaze is not None: + right_gaze_x, right_gaze_y = right_gaze + p1_right = (int(right_pupil_pos[0]), int(right_pupil_pos[1])) + p2_right = (int(right_gaze[0]), int(right_gaze[1])) + + image = cv2.line(image, p1_right, p2_right, (0, 0, 255), 2) + + return image + + def draw_head_orientation(image, head_orientation_vector, nose_2d): x,y,z = head_orientation_vector p1 = (int(nose_2d[0]), int(nose_2d[1])) diff --git a/face_mesh_module.py b/face_mesh_module.py index 2021be8..58aa152 100644 --- a/face_mesh_module.py +++ b/face_mesh_module.py @@ -36,6 +36,6 @@ def findFaceMesh(self, image): # mesh_points=np.array([np.multiply([p.x, p.y], [img_w, img_h]).astype(int) for p in results.multi_face_landmarks[0].landmark]) mesh_points_3d = np.array([np.multiply([p.x, p.y, p.z], [img_w, img_h, 1]).astype(float) for p in results.multi_face_landmarks[0].landmark]) - return mesh_points_3d + return mesh_points_3d # teh points are normalized to [-1, 1] and returned as (x, y, z) format else: return None diff --git a/gaze_module.py b/gaze_module.py index 0248120..f263bd3 100644 --- a/gaze_module.py +++ b/gaze_module.py @@ -4,8 +4,7 @@ import time class EyeGazeEstimator(): - def __init__(self, face_mesh, frame_shape): - self.face_mesh = face_mesh + def __init__(self, frame_shape): self.frame_shape = frame_shape # 3D estimation for a human face @@ -19,8 +18,8 @@ def __init__(self, face_mesh, frame_shape): ]) # 3D model eye points - self.eye_ball_center_right = np.array([[-29.05], [32.7], [-39.5]]) - self.eye_ball_center_left = np.array([[29.05], [32.7], [-39.5]]) # the center of the left eyeball as a vector. + self.eye_ball_center_left = np.array([[-29.05], [32.7], [-39.5]]) # the center of the left eyeball as a vector. + self.eye_ball_center_right = np.array([[29.05], [32.7], [-39.5]]) # the center of the right eyeball as a vector. # camera matrix estimation self.focal_length = self.frame_shape[1] @@ -35,71 +34,72 @@ def __init__(self, face_mesh, frame_shape): def _relative(self, point): """Relative takes mediapipe points that is normalized to [-1, 1] and returns image points""" - return (int(point.x * self.frame_shape[1]), int(point.y * self.frame_shape[0])) + return (int(point[0]), int(point[1])) def _relativeT(self, point): """RelativeT takes mediapipe points that is normalized to [-1, 1] and returns image points at (x,y,0) format""" - return (int(point.x * self.frame_shape[1]), int(point.y * self.frame_shape[0], 0)) + return (int(point[0]), int(point[1]), 0) def get_eye_gaze_vector(self, face_lms): """Returns the eye gaze vector of the face_lms [left_eye_vector, right_eye_vector]""" # 2D image points image_points_2D = np.array([ - self._relative(face_lms.landmark[4]), # Nose tip - self._relative(face_lms.landmark[152]), # Chin - self._relative(face_lms.landmark[263]), # Left eye left corner - self._relative(face_lms.landmark[33]), # Right eye right corner - self._relative(face_lms.landmark[287]), # Left Mouth corner - self._relative(face_lms.landmark[57]) # Right mouth corner + self._relative(face_lms[4]), # Nose tip + self._relative(face_lms[152]), # Chin + self._relative(face_lms[263]), # Left eye left corner + self._relative(face_lms[33]), # Right eye right corner + self._relative(face_lms[287]), # Left Mouth corner + self._relative(face_lms[57]) # Right mouth corner ], dtype="double") # 2D.5 image points (it still 3d, nut the z is 0) image_points_2D5 = np.array([ - self._relativeT(face_lms.landmark[4]), # Nose tip - self._relativeT(face_lms.landmark[152]), # Chin - self._relativeT(face_lms.landmark[263]), # Left eye, left corner - self._relativeT(face_lms.landmark[33]), # Right eye, right corner - self._relativeT(face_lms.landmark[287]), # Left Mouth corner - self._relativeT(face_lms.landmark[57]) # Right mouth corner + self._relativeT(face_lms[4]), # Nose tip + self._relativeT(face_lms[152]), # Chin + self._relativeT(face_lms[263]), # Left eye, left corner + self._relativeT(face_lms[33]), # Right eye, right corner + self._relativeT(face_lms[287]), # Left Mouth corner + self._relativeT(face_lms[57]) # Right mouth corner ], dtype="double") + # Solve the PnP problem success, rotation_vector, translation_vector = cv2.solvePnP( self.model_points, image_points_2D, self.camera_matrix, self.dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) # 2D Pupil location - left_eye = self._relative(face_lms.landmark[468]) - right_eye = self._relative(face_lms.landmark[473]) + left_eye = self._relative(face_lms[473]) + right_eye = self._relative(face_lms[468]) # Transformation between image point to world point - _, transformation = cv2.estimateAffine3D(image_points_2D5, self.model_points) - + _, transformation,_ = cv2.estimateAffine3D(image_points_2D5, self.model_points) if transformation is not None: # if estimateAffine3D succeeded # Project pupil image point into 3D world point left_pupil_world_cord = transformation @ np.array([[left_eye[0], left_eye[1], 0, 1]]).T + right_pupil_world_cord = transformation @ np.array([[right_eye[0], right_eye[1], 0, 1]]).T # 3D gaze point (10 is arbitrary) gaze_distance_scale = 10 left_S_vector = self.eye_ball_center_left + (left_pupil_world_cord - self.eye_ball_center_left) * gaze_distance_scale - right_S_vector = self.eye_ball_center_right + (right_pupil_world_cord - self.eye_ball_center_right) * gaze_distance_scale + # Project a 3D gaze direction into the image plane - (left_eye_pupil_2d, _) = cv2.projectPoints(left_S_vector, rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) - (right_eye_pupil_2d, _) = cv2.projectPoints(right_S_vector, rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) + (left_eye_pupil_2d, _) = cv2.projectPoints((int(left_S_vector[0]), int(left_S_vector[1]), int(left_S_vector[2])), rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) + # Project 3D head Pose into the image plane - (head_pose, _) = cv2.projectPoints((int(left_pupil_world_cord[0]), int(left_pupil_world_cord[1]), 40), rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) + (head_pose, _) = cv2.projectPoints((int(left_pupil_world_cord[0]), int(left_pupil_world_cord[1]), int(40)), rotation_vector, translation_vector, self.camera_matrix, self.dist_coeffs) # correct gaze vector for the head pose left_eye_gaze = left_eye + (left_eye_pupil_2d[0][0] - left_eye) - (head_pose[0][0] - left_eye) - right_eye_gaze = right_eye + (right_eye_pupil_2d[0][0] - right_eye) - (head_pose[0][0] - right_eye) - print(f'Left Eye Gaze: {left_eye_gaze}') - print(f'Right Eye Gaze: {right_eye_gaze}') + right_eye_gaze = right_eye + (left_eye_pupil_2d[0][0] - right_eye) - (head_pose[0][0] - right_eye) - return [left_eye_gaze, right_eye_gaze] + return (left_eye_gaze, right_eye_gaze) + else: + return (None, None) class HeadOrientationEstimator(): diff --git a/main.py b/main.py index f3692be..cb7403b 100644 --- a/main.py +++ b/main.py @@ -26,6 +26,7 @@ 'use_multicore': None, 'overwrite': None, 'draw_head_orientation': None, + 'draw_eye_gaze': None, 'path': None } @@ -131,6 +132,8 @@ def process_video(path): frame = draw_face_mesh_points(image=frame, lms=face_info.lms_2d) if (global_options['draw_head_orientation']): frame = draw_head_orientation(frame, face_info.head_orientation_vector, face_info.nose_2d) + if(global_options['draw_eye_gaze']): + frame = draw_eye_gaze(frame, face_info.left_pupil, face_info.left_eye_gaze, face_info.right_pupil, face_info.right_eye_gaze) else: face_not_found_counter += 1 @@ -220,7 +223,8 @@ def get_path_argument(arguments, default): -drawpu s/n (default: n) -> desenha os circulos da pupila -drawpp s/n (default: s) -> desenha as ultimas posicoes da pupila -drawmp s/n (default: n) -> desenha os pontos da malha da face - -drawgz s/n (default: n) -> desenha o vetor de olhar + -drawheadpose s/n (default: n) -> desenha o vetor da posição da orientação da cabeça + -draweyegaze s/n (default: n) -> desenha o vetor do olhar -showwarn s/n (default: s) -> mostra avisos -multicore s/n (default: n) -> usa processamento multicore -overwrite s/n (default: s) -> sobrescreve os arquivos ja processados @@ -235,10 +239,12 @@ def get_path_argument(arguments, default): global_options['draw_pupil'] = find_argument_by_option(option = '-drawpu', arguments = arguments, default = 'n') global_options['draw_past_pos'] = find_argument_by_option(option = '-drawpp', arguments = arguments, default = 's') global_options['draw_mask_points'] = find_argument_by_option(option = '-drawmp', arguments = arguments, default = 'n') - global_options['draw_head_orientation'] = find_argument_by_option(option = '-drawgz', arguments = arguments, default = 'n') + global_options['draw_head_orientation'] = find_argument_by_option(option = '-drawheadpose', arguments = arguments, default = 'n') + global_options['draw_eye_gaze'] = find_argument_by_option(option = '-draweyegaze', arguments = arguments, default = 'n') global_options['show_warnings'] = find_argument_by_option(option = '-showwarn', arguments = arguments, default = 's') global_options['use_multicore'] = find_argument_by_option(option = '-multicore', arguments = arguments, default = 'n') + if (global_options['use_multicore']): global_options['overwrite'] = find_argument_by_option(option = '-overwrite', arguments = arguments, default='n') else: diff --git a/options.txt b/options.txt index 1d858e7..6952a1e 100644 --- a/options.txt +++ b/options.txt @@ -1,10 +1,11 @@ -show_process n +show_process s draw_bb n -draw_iris s -draw_pupil s -draw_past_pos s -draw_mask_points s +draw_iris n +draw_pupil n +draw_past_pos n +draw_mask_points n show_warnings s use_multicore n overwrite s -draw_head_orientation s +draw_head_orientation n +draw_eye_gaze s diff --git a/path.txt b/path.txt index 6fd9bd6..5f39be0 100644 --- a/path.txt +++ b/path.txt @@ -1 +1 @@ -D:/Projects_Datasets/EyeTracker/dataset/recordings/2/18 \ No newline at end of file +C:/Users/ralph/Pictures/Camera Roll \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e20518b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +opencv-python==* +numpy +mediapipe +tqdm +scipy +pandas \ No newline at end of file diff --git a/teste_eye_gaze/gaze.py b/teste_eye_gaze/gaze.py index 0f9bcb6..86b9102 100644 --- a/teste_eye_gaze/gaze.py +++ b/teste_eye_gaze/gaze.py @@ -66,8 +66,7 @@ def gaze(frame, points): ) dist_coeffs = np.zeros((4, 1)) # Assuming no lens distortion - (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix, - dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) + (success, rotation_vector, translation_vector) = cv2.solvePnP(model_points, image_points, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE) # 2d pupil location left_pupil = relative(points.landmark[468], frame.shape) @@ -84,8 +83,7 @@ def gaze(frame, points): S = Eye_ball_center_left + (pupil_world_cord - Eye_ball_center_left) * 10 # Project a 3D gaze direction onto the image plane. - (eye_pupil2D, _) = cv2.projectPoints((int(S[0]), int(S[1]), int(S[2])), rotation_vector, - translation_vector, camera_matrix, dist_coeffs) + (eye_pupil2D, _) = cv2.projectPoints((int(S[0]), int(S[1]), int(S[2])), rotation_vector, translation_vector, camera_matrix, dist_coeffs) # project 3D head pose into the image plane (head_pose, _) = cv2.projectPoints((int(pupil_world_cord[0]), int(pupil_world_cord[1]), int(40)), rotation_vector, From fdbcae94de838a562cd2b5df1583d5e7ae63850f Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Thu, 11 Apr 2024 19:45:46 -0300 Subject: [PATCH 13/25] =?UTF-8?q?salvando=20os=20dados=20de=20gaze=20(depo?= =?UTF-8?q?is=20para=20reconstruir=20o=20vetor=20em=203d=20vai=20ter=20que?= =?UTF-8?q?=20calcular=20a=20proje=C3=A7=C3=A3o=20levando=20em=20considera?= =?UTF-8?q?=C3=A7=C3=A3o=20que=20o=20comprimento=20foi=20de=2010)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 1 + positions_module.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/main.py b/main.py index cb7403b..351739b 100644 --- a/main.py +++ b/main.py @@ -116,6 +116,7 @@ def process_video(path): frame_data.add_position_data(face_data_dict[key],key) frame_data.add_head_orientation_data(face_info.head_orientation_vector) + frame_data.add_eyes_gaze_data(face_info.left_eye_gaze, face_info.right_eye_gaze) # desenha os dados da face if (global_options['draw_bb']): diff --git a/positions_module.py b/positions_module.py index f41eb88..2d8c55d 100644 --- a/positions_module.py +++ b/positions_module.py @@ -56,6 +56,12 @@ def save_data(self, path): 'head_orientation_x': [ data._head_orientation['x'] if data._head_orientation is not None else "" for data in self._data], 'head_orientation_y': [ data._head_orientation['y'] if data._head_orientation is not None else "" for data in self._data], 'head_orientation_z': [ data._head_orientation['z'] if data._head_orientation is not None else "" for data in self._data], + + 'left_eye_gaze_x': [ data._left_eye_gaze['x'] if data._left_eye_gaze is not None else "" for data in self._data], + 'left_eye_gaze_y': [ data._left_eye_gaze['y'] if data._left_eye_gaze is not None else "" for data in self._data], + + 'right_eye_gaze_x': [ data._right_eye_gaze['x'] if data._right_eye_gaze is not None else "" for data in self._data], + 'right_eye_gaze_y': [ data._right_eye_gaze['y'] if data._right_eye_gaze is not None else "" for data in self._data], } @@ -70,6 +76,8 @@ def __init__(self, frame,height, width): self._left_pupil = None self._right_pupil = None self._head_orientation = None + self._left_eye_gaze = None + self._right_eye_gaze = None self._frame = frame self._height = height self._width = width @@ -99,6 +107,29 @@ def add_head_orientation_data(self, data): "y": None, "z": None } + + def add_eyes_gaze_data(self, left_gaze, right_gaze): + if (left_gaze is not None): + self._left_eye_gaze = { + "x": left_gaze[0], + "y": left_gaze[1] + } + else: + self._left_eye_gaze = { + "x": None, + "y": None + } + + if (right_gaze is not None): + self._right_eye_gaze = { + "x": right_gaze[0], + "y": right_gaze[1] + } + else: + self._right_eye_gaze = { + "x": None, + "y": None + } def add_position_data(self, data, key): if(data is not None): From 55859a6faa8fcc8a9eb48486b8d7dba10fb56ef3 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 18:12:14 -0300 Subject: [PATCH 14/25] =?UTF-8?q?coloquei=20pra=20salvar=20a=20posi=C3=A7?= =?UTF-8?q?=C3=A3o=20do=20nariz=20pra=20poder=20descontar=20o=20movimento?= =?UTF-8?q?=20depois=20nas=20analises?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- positions_module.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/positions_module.py b/positions_module.py index 2d8c55d..9630f91 100644 --- a/positions_module.py +++ b/positions_module.py @@ -62,6 +62,9 @@ def save_data(self, path): 'right_eye_gaze_x': [ data._right_eye_gaze['x'] if data._right_eye_gaze is not None else "" for data in self._data], 'right_eye_gaze_y': [ data._right_eye_gaze['y'] if data._right_eye_gaze is not None else "" for data in self._data], + + 'nose_tip_x': [ data._nose_tip['x'] if data._nose_tip is not None else "" for data in self._data], + 'nose_tip_y': [ data._nose_tip['y'] if data._nose_tip is not None else "" for data in self._data], } @@ -81,6 +84,7 @@ def __init__(self, frame,height, width): self._frame = frame self._height = height self._width = width + self._nose_tip = None def print_data(self): print(f'\nFrame: {self._frame}') print("\nLeft Iris:") @@ -93,6 +97,19 @@ def print_data(self): print(self._right_pupil) print("\n-------------------------------------") + def add_nose_tip_data(self, data): + if(data is not None): + x,y = data + self._nose_tip = { + "x": int(x), + "y": int(y) + } + else: + self._nose_tip = { + "x": None, + "y": None + } + def add_head_orientation_data(self, data): if(data is not None): x,y,z = data From a689352020585965f492028e08d58ca5ec739e1f Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 18:12:32 -0300 Subject: [PATCH 15/25] =?UTF-8?q?comecei=20um=20alg=20pra=20gerar=20visual?= =?UTF-8?q?iza=C3=A7=C3=B5es=20dos=20dados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 2 + Pipfile.lock | 37 +++++++++-- main.py | 1 + path.txt | 2 +- visualization_generator/main.py | 109 ++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 visualization_generator/main.py diff --git a/Pipfile b/Pipfile index bb6046b..5526e05 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,8 @@ mediapipe = "*" tqdm = "*" scipy = "*" pandas = "*" +plotly = "*" +kaleido = "==0.1.0post1" [dev-packages] pyinstaller = "*" diff --git a/Pipfile.lock b/Pipfile.lock index c33741c..3500808 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "09da273f2b9750e8276b384d90369dce023f495ccfc232d58163f100d312b3c4" + "sha256": "d9b2df6da80efece6513c1953fc54310122fd91be1a06761d8bcbd504c494b13" }, "pipfile-spec": 6, "requires": { @@ -235,6 +235,14 @@ "markers": "python_version >= '3.9'", "version": "==0.4.26" }, + "kaleido": { + "hashes": [ + "sha256:2a942606a13c70dfd0a02e092ec140a1083e093ae06661c5e1b1179e477a9e44", + "sha256:636aedcd89f359f54687b4fe331776a7224f7c31c27d03230e4e13c7cf4cb66a" + ], + "index": "pypi", + "version": "==0.1.0.post1" + }, "kiwisolver": { "hashes": [ "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf", @@ -619,6 +627,15 @@ "markers": "python_version >= '3.8'", "version": "==10.3.0" }, + "plotly": { + "hashes": [ + "sha256:837a9c8aa90f2c0a2f0d747b82544d014dc2a2bdde967b5bb1da25b53932d1a9", + "sha256:bf901c805d22032cfa534b2ff7c5aa6b0659e037f19ec1e0cca7f585918b5c89" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==5.20.0" + }, "protobuf": { "hashes": [ "sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7", @@ -668,7 +685,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0.post0" }, "pytz": { @@ -715,7 +732,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "sounddevice": { @@ -729,6 +746,14 @@ "markers": "python_version >= '3.7'", "version": "==0.4.6" }, + "tenacity": { + "hashes": [ + "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a", + "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c" + ], + "markers": "python_version >= '3.7'", + "version": "==8.2.3" + }, "tqdm": { "hashes": [ "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9", @@ -824,11 +849,11 @@ }, "setuptools": { "hashes": [ - "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e", - "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c" + "sha256:659e902e587e77fab8212358f5b03977b5f0d18d4724310d4a093929fee4ca1a", + "sha256:b6df12d754b505e4ca283c61582d5578db83ae2f56a979b3bc9a8754705ae3bf" ], "markers": "python_version >= '3.8'", - "version": "==69.2.0" + "version": "==69.4.0" }, "zipp": { "hashes": [ diff --git a/main.py b/main.py index 351739b..d544376 100644 --- a/main.py +++ b/main.py @@ -117,6 +117,7 @@ def process_video(path): frame_data.add_head_orientation_data(face_info.head_orientation_vector) frame_data.add_eyes_gaze_data(face_info.left_eye_gaze, face_info.right_eye_gaze) + frame_data.add_nose_tip_data(face_info.nose_2d) # desenha os dados da face if (global_options['draw_bb']): diff --git a/path.txt b/path.txt index 5f39be0..ade4242 100644 --- a/path.txt +++ b/path.txt @@ -1 +1 @@ -C:/Users/ralph/Pictures/Camera Roll \ No newline at end of file +C:/Users/ralph/Pictures/Camera Roll/fixation plot test \ No newline at end of file diff --git a/visualization_generator/main.py b/visualization_generator/main.py new file mode 100644 index 0000000..c3f1668 --- /dev/null +++ b/visualization_generator/main.py @@ -0,0 +1,109 @@ +# receives using argaparse the path to the file with the data to be visualized (csv), the visualizations will be saved in the same path in a folder called "visualizations" + +# the csv file has the following columns: +# ,frame,height,width,left_iris_x,left_iris_y,left_iris_r,right_iris_x,right_iris_y,right_iris_r,left_pupil_x,left_pupil_y,left_pupil_r,right_pupil_x,right_pupil_y,right_pupil_r,head_orientation_x,head_orientation_y,head_orientation_z,left_eye_gaze_x,left_eye_gaze_y,right_eye_gaze_x,right_eye_gaze_y, nose_tip_x, nose_tip_y +""" +frame: frame number +height: height of the image +width: width of the image +left_iris_x: x coordinate of the left iris +left_iris_y: y coordinate of the left iris +left_iris_r: radius of the left iris +right_iris_x: x coordinate of the right iris +right_iris_y: y coordinate of the right iris +right_iris_r: radius of the right iris +left_pupil_x: x coordinate of the left pupil +left_pupil_y: y coordinate of the left pupil +left_pupil_r: radius of the left pupil +right_pupil_x: x coordinate of the right pupil +right_pupil_y: y coordinate of the right pupil +right_pupil_r: radius of the right pupil +head_orientation_x: x coordinate of the head orientation +head_orientation_y: y coordinate of the head orientation +head_orientation_z: z coordinate of the head orientation +left_eye_gaze_x: x coordinate of the left eye gaze +left_eye_gaze_y: y coordinate of the left eye gaze +right_eye_gaze_x: x coordinate of the right eye gaze +right_eye_gaze_y: y coordinate of the right eye gaze +nose_tip_x: x coordinate of the nose tip +nose_tip_y: y coordinate of the nose tip + +- head pose is a vector the shows the direction of the head +- eye gaze are two points that show a 2d projection of the vector that shows the direction of the eyes +""" + +# the visualizations are: +# 1. Eye fixation: a plot for each eye that show throu time the position of the iris and the pupil. this will help to see if the eyes are fixating (stoping the movement) on a point +# - the plot can be: a line plot for each axis (x,y) so we can see the movement of the eyes in the x and y axis through time +# 2. Eye range of motion: A plot that will show the difference between the vectors of the head orientation and the eye gaze through time. this will help to see how much the eyes are moving in relation to the head +# - the plot can be: a line plot with the y axis being the angle between the head orientation and the eye gaze and the x axis being the time + + +import argparse +import pandas as pd +# use plotly to create the visualizations +import plotly.express as px +import os + +verbose = False + +def generate_eye_fixation_visualization(data_df, save_path="visualizations"): + # create a line plot for the iris and pupil position through time + + if(verbose): + print("Generating eye fixation visualization") + print("Saving in:", save_path) + print("Data columns:", data_df.columns) + print(data_df.head()) + + # get only the data needed for the plot (only pupil) + pupil_data = data_df[['frame', 'left_pupil_x', 'left_pupil_y', 'right_pupil_x', 'right_pupil_y', 'nose_tip_x', 'nose_tip_y']] + + if(verbose): + print("Data for the plot filtered") + # first remove the lines with no data (it will assume the value of the last known value) + filled_data = pupil_data.ffill() + if(verbose): + print("data filled") + print(filled_data.head()) + + # remove the head movement from the pupil position using the nose tip as a reference + filled_data['left_pupil_x'] = filled_data['left_pupil_x'] - filled_data['nose_tip_x'] + filled_data['left_pupil_y'] = filled_data['left_pupil_y'] - filled_data['nose_tip_y'] + + # moves the data so the mean is 0 + filled_data['left_pupil_x'] = filled_data['left_pupil_x'] - filled_data['left_pupil_x'].mean() + filled_data['left_pupil_y'] = filled_data['left_pupil_y'] - filled_data['left_pupil_y'].mean() + + + + fig = px.line(filled_data, x='frame', y=['left_pupil_x', 'left_pupil_y'], title='Left eye fixation') + if(verbose): + print("plot created") + # saves as 1000x1000 image + fig.write_image(f"{save_path}/left_eye_fixation.png", width=1000, height=500 , format='png',engine='kaleido') + if(verbose): + print("plot saved") + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Generate visualizations for eye tracking data') + parser.add_argument('data_path', type=str, help='path to the csv file with the data') + # verbose argument + parser.add_argument('--verbose', action='store_true', help='print more information') + args = parser.parse_args() + + if args.verbose: + verbose = True + + data_df = pd.read_csv(args.data_path) + dir_path = os.path.dirname(args.data_path) + save_path = f"{dir_path}/visualizations" + # create a folder to save the visualizations + if not os.path.exists(f"{save_path}"): + os.makedirs(f"{save_path}") + + + generate_eye_fixation_visualization(data_df, save_path) + \ No newline at end of file From f73d3e14eff2ab16cb809622f1d161315ce4d4bd Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 19:41:10 -0300 Subject: [PATCH 16/25] =?UTF-8?q?agora=20to=20salvando=20a=20visualiza?= =?UTF-8?q?=C3=A7=C3=A3o=20da=20amplitude=20de=20movimento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- options.txt | 11 --- visualization_generator/main.py | 169 ++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 11 deletions(-) delete mode 100644 options.txt diff --git a/options.txt b/options.txt deleted file mode 100644 index 6952a1e..0000000 --- a/options.txt +++ /dev/null @@ -1,11 +0,0 @@ -show_process s -draw_bb n -draw_iris n -draw_pupil n -draw_past_pos n -draw_mask_points n -show_warnings s -use_multicore n -overwrite s -draw_head_orientation n -draw_eye_gaze s diff --git a/visualization_generator/main.py b/visualization_generator/main.py index c3f1668..e28069c 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -44,6 +44,7 @@ # use plotly to create the visualizations import plotly.express as px import os +import numpy as np verbose = False @@ -85,8 +86,175 @@ def generate_eye_fixation_visualization(data_df, save_path="visualizations"): if(verbose): print("plot saved") + # save the data with the left pupil position + filled_data.to_csv(f"{save_path}/left_pupil_position.csv", index=False) + +def generate_ocular_movement_range_vizualization(data, save_path="visualizations"): + # create a line plot for the angle between the head orientation and the eye gaze through time + + if verbose: + print("Generating ocular movement range visualization") + print("Saving in:", save_path) + print("Data columns:", data.columns) + print(data.head()) + + # get only the data needed for the plot (gaze and head orientation) + gaze_data = data[['frame', 'left_eye_gaze_x', 'left_eye_gaze_y', 'right_eye_gaze_x', 'right_eye_gaze_y', + 'head_orientation_x', 'head_orientation_y', 'head_orientation_z', + 'left_pupil_x', 'left_pupil_y', 'right_pupil_x', 'right_pupil_y']] + + if verbose: + print("Data for the plot filtered") + # first remove the lines with no data (it will assume the value of the last known value) + filled_data = gaze_data.ffill() + if verbose: + print("Data filled") + print(filled_data.head()) + + # Adjust gaze vectors based on pupil positions + filled_data['left_eye_gaze_x'] -= filled_data['left_pupil_x'] + filled_data['left_eye_gaze_y'] -= filled_data['left_pupil_y'] + filled_data['right_eye_gaze_x'] -= filled_data['right_pupil_x'] + filled_data['right_eye_gaze_y'] -= filled_data['right_pupil_y'] + + # divide by 10 + filled_data['left_eye_gaze_x'] /= 10 + filled_data['left_eye_gaze_y'] /= 10 + filled_data['right_eye_gaze_x'] /= 10 + filled_data['right_eye_gaze_y'] /= 10 + + # Append the head orientation Z to gaze data to make 3D vectors + filled_data['left_eye_gaze_z'] = filled_data['head_orientation_z'] + filled_data['right_eye_gaze_z'] = filled_data['head_orientation_z'] + + # Normalize the gaze and head orientation vectors + right_eye_gaze = filled_data[['right_eye_gaze_x', 'right_eye_gaze_y', 'right_eye_gaze_z']].values + left_eye_gaze = filled_data[['left_eye_gaze_x', 'left_eye_gaze_y', 'left_eye_gaze_z']].values + head_orientation = filled_data[['head_orientation_x', 'head_orientation_y', 'head_orientation_z']].values + + # salva o df com os dados normalizados + filled_data.to_csv(f"{save_path}/normalized_vectors.csv", index=False) + + right_eye_gaze /= np.linalg.norm(right_eye_gaze, axis=1)[:, np.newaxis] + left_eye_gaze /= np.linalg.norm(left_eye_gaze, axis=1)[:, np.newaxis] + head_orientation /= np.linalg.norm(head_orientation, axis=1)[:, np.newaxis] + + # Calcula o ângulo entre a orientação da cabeça e a direção do olhar para cada frame, convertendo para graus + # = x1*x2 + y1*y2 + z1*z2 + # |v| = sqrt(x2^2 + y2^2 + z2^2) + # = cos(phi) * |u| * |v| + # cos(phi) = / (|u| * |v|) = (x1*x2 + y1*y2 + z1*z2 )/ sqrt(x1^2 + y1^2 + z1^2) * sqrt(x2^2 + y2^2 + z2^2) + # phi = arccos((x1*x2 + y1*y2 + z1*z2 )/ sqrt(x1^2 + y1^2 + z1^2) * sqrt(x2^2 + y2^2 + z2^2)) + angle_left = np.arccos(np.divide(np.sum(left_eye_gaze * head_orientation, axis=1), np.linalg.norm(left_eye_gaze, axis=1) * np.linalg.norm(head_orientation, axis=1))) * 180 / np.pi + angle_right = np.arccos(np.divide(np.sum(right_eye_gaze * head_orientation, axis=1), np.linalg.norm(right_eye_gaze, axis=1) * np.linalg.norm(head_orientation, axis=1))) * 180 / np.pi + + # if phi > 90, phi = 180 - phi + angle_left = np.where(angle_left > 90, 180 - angle_left, angle_left) + angle_right = np.where(angle_right > 90, 180 - angle_right, angle_right) + + + filled_data['angle_left_degree'] = angle_left + filled_data['angle_right_degree'] = angle_right + + + + # agora pega somente a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) + vertical_angle_left = np.arccos(np.divide(np.sum(left_eye_gaze[:, [1, 2]] * head_orientation[:, [1, 2]], axis=1), np.linalg.norm(left_eye_gaze[:, [1, 2]], axis=1) * np.linalg.norm(head_orientation[:, [1, 2]], axis=1))) * 180 / np.pi + vertical_angle_right = np.arccos(np.divide(np.sum(right_eye_gaze[:, [1, 2]] * head_orientation[:, [1, 2]], axis=1), np.linalg.norm(right_eye_gaze[:, [1, 2]], axis=1) * np.linalg.norm(head_orientation[:, [1, 2]], axis=1))) * 180 / np.pi + + # if phi > 90, phi = 180 - phi + vertical_angle_left = np.where(vertical_angle_left > 90, 180 - vertical_angle_left, vertical_angle_left) + vertical_angle_right = np.where(vertical_angle_right > 90, 180 - vertical_angle_right, vertical_angle_right) + + + # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) + horizontal_angle_left = np.arccos(np.divide(np.sum(left_eye_gaze[:, [0, 2]] * head_orientation[:, [0, 2]], axis=1), np.linalg.norm(left_eye_gaze[:, [0, 2]], axis=1) * np.linalg.norm(head_orientation[:, [0, 2]], axis=1))) * 180 / np.pi + horizontal_angle_right = np.arccos(np.divide(np.sum(right_eye_gaze[:, [0, 2]] * head_orientation[:, [0, 2]], axis=1), np.linalg.norm(right_eye_gaze[:, [0, 2]], axis=1) * np.linalg.norm(head_orientation[:, [0, 2]], axis=1))) * 180 / np.pi + + + # if phi > 90, phi = 180 - phi + horizontal_angle_left = np.where(horizontal_angle_left > 90, 180 - horizontal_angle_left, horizontal_angle_left) + horizontal_angle_right = np.where(horizontal_angle_right > 90, 180 - horizontal_angle_right, horizontal_angle_right) + + filled_data['vertical_angle_left_degree'] = vertical_angle_left + filled_data['vertical_angle_right_degree'] = vertical_angle_right + filled_data['horizontal_angle_left_degree'] = horizontal_angle_left + filled_data['horizontal_angle_right_degree'] = horizontal_angle_right + + + # save the data with the angles + filled_data.to_csv(f"{save_path}/angles.csv", index=False) + + + # remove outliers and smooth data + # outliers in this context are the top N% of the data + outlier_top_threshold = 0.01 + # the remotion will be done nullifying the values and then using the ffill method to fill the null values + filled_data.loc[filled_data['angle_left_degree'] > filled_data['angle_left_degree'].quantile(1 - outlier_top_threshold), 'angle_left_degree'] = None + filled_data.loc[filled_data['angle_right_degree'] > filled_data['angle_right_degree'].quantile(1 - outlier_top_threshold), 'angle_right_degree'] = None + + filled_data.loc[filled_data['vertical_angle_left_degree'] > filled_data['vertical_angle_left_degree'].quantile(1 - outlier_top_threshold), 'vertical_angle_left_degree'] = None + filled_data.loc[filled_data['vertical_angle_right_degree'] > filled_data['vertical_angle_right_degree'].quantile(1 - outlier_top_threshold), 'vertical_angle_right_degree'] = None + + filled_data.loc[filled_data['horizontal_angle_left_degree'] > filled_data['horizontal_angle_left_degree'].quantile(1 - outlier_top_threshold), 'horizontal_angle_left_degree'] = None + filled_data.loc[filled_data['horizontal_angle_right_degree'] > filled_data['horizontal_angle_right_degree'].quantile(1 - outlier_top_threshold), 'horizontal_angle_right_degree'] = None + + filled_data = filled_data.ffill() + + # save the data with the outliers removed + filled_data.to_csv(f"{save_path}/angles_no_outliers.csv", index=False) + + # smooth the data using a rolling mean + filled_data['angle_left_degree'] = filled_data['angle_left_degree'].rolling(10).mean() + filled_data['angle_right_degree'] = filled_data['angle_right_degree'].rolling(10).mean() + + filled_data['vertical_angle_left_degree'] = filled_data['vertical_angle_left_degree'].rolling(10).mean() + filled_data['vertical_angle_right_degree'] = filled_data['vertical_angle_right_degree'].rolling(10).mean() + + filled_data['horizontal_angle_left_degree'] = filled_data['horizontal_angle_left_degree'].rolling(10).mean() + filled_data['horizontal_angle_right_degree'] = filled_data['horizontal_angle_right_degree'].rolling(10).mean() + + # save the data with the smoothed values + filled_data.to_csv(f"{save_path}/angles_smoothed.csv", index=False) + + + + + + # Plotting the data + fig = px.line(filled_data, x='frame', y=['angle_left_degree'], title='Ocular Movement Range') + if verbose: + print("Plot created") + # Save the plot as an image + fig.write_image(f"{save_path}/ocular_movement_range.png", width=1000, height=500, format='png', engine='kaleido') + if verbose: + print("Plot saved") + + # a 2d line plot with the vertical and horizontal components of the vectors (head orientation and eye gaze) through in a arbitrary time + #only draw left eye (one image for horizontal and one for vertical) + fig = px.line(filled_data, x='frame', y=['vertical_angle_left_degree'], title='Vertical ocular movement range') + fig.write_image(f"{save_path}/vertical_ocular_movement_range_left_eye.png", width=1000, height=500, format='png', engine='kaleido') + + fig = px.line(filled_data, x='frame', y=['horizontal_angle_left_degree'], title='Horizontal ocular movement range') + fig.write_image(f"{save_path}/horizontal_ocular_movement_range_left_eye.png", width=1000, height=500, format='png', engine='kaleido') + + + + + # a 3d line plot with the vectors (head orientation and eye gaze) through in a arbitrary time + # there will be 2 lines drawn, staring from 0,0,0 to the head orientation and eye gaze vectors + # the plot will be saved as html + fig = px.line_3d(filled_data, title='Head orientation vector') + fig.add_scatter3d(x=[0, filled_data['left_eye_gaze_x'][0]], y=[0, filled_data['left_eye_gaze_y'][0]], z=[0, filled_data['left_eye_gaze_z'][0]], mode='lines', name='Left eye gaze vector') + fig.add_scatter3d(x=[0, filled_data['head_orientation_x'][0]], y=[0, filled_data['head_orientation_y'][0]], z=[0, filled_data['head_orientation_z'][0]], mode='lines', name='Head orientation vector') + + fig.write_html(f"{save_path}/head_orientation_and_eye_gaze.html") + if verbose: + print("3d plot saved") + print(f'The angle between the head orientation and the left eye gaze is {angle_left[0]} degrees') + if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generate visualizations for eye tracking data') parser.add_argument('data_path', type=str, help='path to the csv file with the data') @@ -106,4 +274,5 @@ def generate_eye_fixation_visualization(data_df, save_path="visualizations"): generate_eye_fixation_visualization(data_df, save_path) + generate_ocular_movement_range_vizualization(data_df, save_path) \ No newline at end of file From cd052619cb790d4126dcde95f9134d63f50412c1 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 23:07:36 -0300 Subject: [PATCH 17/25] =?UTF-8?q?extrai=20a=20fun=C3=A7=C3=A3o=20de=20filt?= =?UTF-8?q?ro=20de=20outliers=20e=20smoothing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- options.txt | 11 ++++++++ path.txt | 2 +- visualization_generator/main.py | 47 ++++++++++++--------------------- 3 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 options.txt diff --git a/options.txt b/options.txt new file mode 100644 index 0000000..19cc199 --- /dev/null +++ b/options.txt @@ -0,0 +1,11 @@ +show_process s +draw_bb n +draw_iris n +draw_pupil n +draw_past_pos n +draw_mask_points n +show_warnings s +use_multicore n +overwrite s +draw_head_orientation s +draw_eye_gaze s diff --git a/path.txt b/path.txt index ade4242..ef87f1d 100644 --- a/path.txt +++ b/path.txt @@ -1 +1 @@ -C:/Users/ralph/Pictures/Camera Roll/fixation plot test \ No newline at end of file +C:/Users/ralph/Pictures/Camera Roll/babies \ No newline at end of file diff --git a/visualization_generator/main.py b/visualization_generator/main.py index e28069c..17ecac2 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -89,7 +89,17 @@ def generate_eye_fixation_visualization(data_df, save_path="visualizations"): # save the data with the left pupil position filled_data.to_csv(f"{save_path}/left_pupil_position.csv", index=False) +def remove_outliers_and_smooth_data(data, column_name, outlier_top_threshold=0.01, window_size=10): + # remove outliers and smooth data + # outliers in this context are the top N% of the data + # the remotion will be done nullifying the values and then using the ffill method to fill the null values + data.loc[data[column_name] > data[column_name].quantile(1 - outlier_top_threshold), column_name] = None + data = data.ffill() + + # smooth the data using a rolling mean + data[column_name] = data[column_name].rolling(window_size).mean() + return data def generate_ocular_movement_range_vizualization(data, save_path="visualizations"): # create a line plot for the angle between the head orientation and the eye gaze through time @@ -189,38 +199,15 @@ def generate_ocular_movement_range_vizualization(data, save_path="visualizations # remove outliers and smooth data - # outliers in this context are the top N% of the data - outlier_top_threshold = 0.01 - # the remotion will be done nullifying the values and then using the ffill method to fill the null values - filled_data.loc[filled_data['angle_left_degree'] > filled_data['angle_left_degree'].quantile(1 - outlier_top_threshold), 'angle_left_degree'] = None - filled_data.loc[filled_data['angle_right_degree'] > filled_data['angle_right_degree'].quantile(1 - outlier_top_threshold), 'angle_right_degree'] = None - - filled_data.loc[filled_data['vertical_angle_left_degree'] > filled_data['vertical_angle_left_degree'].quantile(1 - outlier_top_threshold), 'vertical_angle_left_degree'] = None - filled_data.loc[filled_data['vertical_angle_right_degree'] > filled_data['vertical_angle_right_degree'].quantile(1 - outlier_top_threshold), 'vertical_angle_right_degree'] = None - - filled_data.loc[filled_data['horizontal_angle_left_degree'] > filled_data['horizontal_angle_left_degree'].quantile(1 - outlier_top_threshold), 'horizontal_angle_left_degree'] = None - filled_data.loc[filled_data['horizontal_angle_right_degree'] > filled_data['horizontal_angle_right_degree'].quantile(1 - outlier_top_threshold), 'horizontal_angle_right_degree'] = None + filled_data = remove_outliers_and_smooth_data(filled_data, 'angle_left_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, 'angle_right_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = filled_data.ffill() + filled_data = remove_outliers_and_smooth_data(filled_data, 'vertical_angle_left_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, 'vertical_angle_right_degree', outlier_top_threshold=0.01, window_size=10) - # save the data with the outliers removed - filled_data.to_csv(f"{save_path}/angles_no_outliers.csv", index=False) - - # smooth the data using a rolling mean - filled_data['angle_left_degree'] = filled_data['angle_left_degree'].rolling(10).mean() - filled_data['angle_right_degree'] = filled_data['angle_right_degree'].rolling(10).mean() - - filled_data['vertical_angle_left_degree'] = filled_data['vertical_angle_left_degree'].rolling(10).mean() - filled_data['vertical_angle_right_degree'] = filled_data['vertical_angle_right_degree'].rolling(10).mean() - - filled_data['horizontal_angle_left_degree'] = filled_data['horizontal_angle_left_degree'].rolling(10).mean() - filled_data['horizontal_angle_right_degree'] = filled_data['horizontal_angle_right_degree'].rolling(10).mean() - - # save the data with the smoothed values - filled_data.to_csv(f"{save_path}/angles_smoothed.csv", index=False) - - - + filled_data = remove_outliers_and_smooth_data(filled_data, 'horizontal_angle_left_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, 'horizontal_angle_right_degree', outlier_top_threshold=0.01, window_size=10) + # Plotting the data From 036c8d1b230a52bc33cc210f2fa88f1fda2b1e32 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 23:15:48 -0300 Subject: [PATCH 18/25] pega o angulo (e deixa negativo dependendo do lado) --- visualization_generator/main.py | 54 ++++++++++++++------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/visualization_generator/main.py b/visualization_generator/main.py index 17ecac2..6b59568 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -101,6 +101,22 @@ def remove_outliers_and_smooth_data(data, column_name, outlier_top_threshold=0.0 return data +def get_angles_between_vectors(v1, v2): + # get the angle between two vectors + # v1 and v2 are numpy arrays + # the angle is in degrees + + # = x1*x2 + y1*y2 + z1*z2 + # |v| = sqrt(x2^2 + y2^2 + z2^2) + # = cos(phi) * |u| * |v| + # cos(phi) = / (|u| * |v|) = (x1*x2 + y1*y2 + z1*z2 )/ sqrt(x1^2 + y1^2 + z1^2) * sqrt(x2^2 + y2^2 + z2^2) + # phi = arccos((x1*x2 + y1*y2 + z1*z2 )/ sqrt(x1^2 + y1^2 + z1^2) * sqrt(x2^2 + y2^2 + z2^2)) + + angle = np.arccos(np.divide(np.sum(v1 * v2, axis=1), np.linalg.norm(v1, axis=1) * np.linalg.norm(v2, axis=1))) * 180 / np.pi + # if phi > 90, phi = -(180 - phi) + angle = np.where(angle > 90, -(180 - angle), angle) + return angle + def generate_ocular_movement_range_vizualization(data, save_path="visualizations"): # create a line plot for the angle between the head orientation and the eye gaze through time @@ -152,47 +168,23 @@ def generate_ocular_movement_range_vizualization(data, save_path="visualizations head_orientation /= np.linalg.norm(head_orientation, axis=1)[:, np.newaxis] # Calcula o ângulo entre a orientação da cabeça e a direção do olhar para cada frame, convertendo para graus - # = x1*x2 + y1*y2 + z1*z2 - # |v| = sqrt(x2^2 + y2^2 + z2^2) - # = cos(phi) * |u| * |v| - # cos(phi) = / (|u| * |v|) = (x1*x2 + y1*y2 + z1*z2 )/ sqrt(x1^2 + y1^2 + z1^2) * sqrt(x2^2 + y2^2 + z2^2) - # phi = arccos((x1*x2 + y1*y2 + z1*z2 )/ sqrt(x1^2 + y1^2 + z1^2) * sqrt(x2^2 + y2^2 + z2^2)) - angle_left = np.arccos(np.divide(np.sum(left_eye_gaze * head_orientation, axis=1), np.linalg.norm(left_eye_gaze, axis=1) * np.linalg.norm(head_orientation, axis=1))) * 180 / np.pi - angle_right = np.arccos(np.divide(np.sum(right_eye_gaze * head_orientation, axis=1), np.linalg.norm(right_eye_gaze, axis=1) * np.linalg.norm(head_orientation, axis=1))) * 180 / np.pi - # if phi > 90, phi = 180 - phi - angle_left = np.where(angle_left > 90, 180 - angle_left, angle_left) - angle_right = np.where(angle_right > 90, 180 - angle_right, angle_right) - filled_data['angle_left_degree'] = angle_left - filled_data['angle_right_degree'] = angle_right + filled_data['angle_left_degree'] = get_angles_between_vectors(left_eye_gaze, head_orientation) + filled_data['angle_right_degree'] = get_angles_between_vectors(right_eye_gaze, head_orientation) # agora pega somente a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) - vertical_angle_left = np.arccos(np.divide(np.sum(left_eye_gaze[:, [1, 2]] * head_orientation[:, [1, 2]], axis=1), np.linalg.norm(left_eye_gaze[:, [1, 2]], axis=1) * np.linalg.norm(head_orientation[:, [1, 2]], axis=1))) * 180 / np.pi - vertical_angle_right = np.arccos(np.divide(np.sum(right_eye_gaze[:, [1, 2]] * head_orientation[:, [1, 2]], axis=1), np.linalg.norm(right_eye_gaze[:, [1, 2]], axis=1) * np.linalg.norm(head_orientation[:, [1, 2]], axis=1))) * 180 / np.pi - - # if phi > 90, phi = 180 - phi - vertical_angle_left = np.where(vertical_angle_left > 90, 180 - vertical_angle_left, vertical_angle_left) - vertical_angle_right = np.where(vertical_angle_right > 90, 180 - vertical_angle_right, vertical_angle_right) + filled_data['vertical_angle_left_degree'] = get_angles_between_vectors(left_eye_gaze[:, 1:], head_orientation[:, 1:]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) + filled_data['vertical_angle_right_degree'] = get_angles_between_vectors(right_eye_gaze[:, 1:], head_orientation[:, 1:]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) - horizontal_angle_left = np.arccos(np.divide(np.sum(left_eye_gaze[:, [0, 2]] * head_orientation[:, [0, 2]], axis=1), np.linalg.norm(left_eye_gaze[:, [0, 2]], axis=1) * np.linalg.norm(head_orientation[:, [0, 2]], axis=1))) * 180 / np.pi - horizontal_angle_right = np.arccos(np.divide(np.sum(right_eye_gaze[:, [0, 2]] * head_orientation[:, [0, 2]], axis=1), np.linalg.norm(right_eye_gaze[:, [0, 2]], axis=1) * np.linalg.norm(head_orientation[:, [0, 2]], axis=1))) * 180 / np.pi - - - # if phi > 90, phi = 180 - phi - horizontal_angle_left = np.where(horizontal_angle_left > 90, 180 - horizontal_angle_left, horizontal_angle_left) - horizontal_angle_right = np.where(horizontal_angle_right > 90, 180 - horizontal_angle_right, horizontal_angle_right) - - filled_data['vertical_angle_left_degree'] = vertical_angle_left - filled_data['vertical_angle_right_degree'] = vertical_angle_right - filled_data['horizontal_angle_left_degree'] = horizontal_angle_left - filled_data['horizontal_angle_right_degree'] = horizontal_angle_right - + filled_data['horizontal_angle_left_degree'] = get_angles_between_vectors(left_eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) + filled_data['horizontal_angle_right_degree'] = get_angles_between_vectors(right_eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) + # save the data with the angles filled_data.to_csv(f"{save_path}/angles.csv", index=False) From 00806ab77342e4b312eab63d40789ca89968bf6c Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 23:26:46 -0300 Subject: [PATCH 19/25] =?UTF-8?q?extrai=20a=20fun=C3=A7=C3=A3o=20de=20dese?= =?UTF-8?q?nhar=20o=20gr=C3=A1fico=20para=20uma=20fun=C3=A7=C3=A3o=20separ?= =?UTF-8?q?ada,=20que=20recebe=20o=20objeto=20de=20dados=20e=20o=20nome=20?= =?UTF-8?q?da=20coluna=20a=20ser=20usada=20como=20eixo=20x.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- visualization_generator/main.py | 66 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/visualization_generator/main.py b/visualization_generator/main.py index 6b59568..5211c97 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -117,6 +117,31 @@ def get_angles_between_vectors(v1, v2): angle = np.where(angle > 90, -(180 - angle), angle) return angle +def draw_eye_gaze_visualizations(eye, data, save_path): + # create a line plot for the iris and pupil position through time + + if(verbose): + print("Generating eye fixation visualization") + print("Saving in:", save_path) + print("Data columns:", data_df.columns) + print(data_df.head()) + + # create plot for {eye}_eye + + fig_ocular_angle = px.line(data, x='frame', y=[f'{eye}_ocular_degree'], title=f'{eye} eye ocular movement range') + fig_ocular_angle.write_image(f"{save_path}/{eye}_eye_ocular_movement_range.png", width=1000, height=500, format='png', engine='kaleido') + + fig_vertical_angle = px.line(data, x='frame', y=[f'vertical_angle_{eye}_degree'], title=f'{eye} eye vertical ocular movement range') + fig_vertical_angle.write_image(f"{save_path}/{eye}_eye_vertical_ocular_movement_range.png", width=1000, height=500, format='png', engine='kaleido') + + fig_horizontal_angle = px.line(data, x='frame', y=[f'horizontal_angle_{eye}_degree'], title=f'{eye} eye horizontal ocular movement range') + fig_horizontal_angle.write_image(f"{save_path}/{eye}_eye_horizontal_ocular_movement_range.png", width=1000, height=500, format='png', engine='kaleido') + + + + + + def generate_ocular_movement_range_vizualization(data, save_path="visualizations"): # create a line plot for the angle between the head orientation and the eye gaze through time @@ -171,8 +196,8 @@ def generate_ocular_movement_range_vizualization(data, save_path="visualizations - filled_data['angle_left_degree'] = get_angles_between_vectors(left_eye_gaze, head_orientation) - filled_data['angle_right_degree'] = get_angles_between_vectors(right_eye_gaze, head_orientation) + filled_data['left_ocular_degree'] = get_angles_between_vectors(left_eye_gaze, head_orientation) + filled_data['right_ocular_degree'] = get_angles_between_vectors(right_eye_gaze, head_orientation) @@ -191,8 +216,8 @@ def generate_ocular_movement_range_vizualization(data, save_path="visualizations # remove outliers and smooth data - filled_data = remove_outliers_and_smooth_data(filled_data, 'angle_left_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, 'angle_right_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, 'left_ocular_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, 'right_ocular_degree', outlier_top_threshold=0.01, window_size=10) filled_data = remove_outliers_and_smooth_data(filled_data, 'vertical_angle_left_degree', outlier_top_threshold=0.01, window_size=10) filled_data = remove_outliers_and_smooth_data(filled_data, 'vertical_angle_right_degree', outlier_top_threshold=0.01, window_size=10) @@ -202,37 +227,8 @@ def generate_ocular_movement_range_vizualization(data, save_path="visualizations - # Plotting the data - fig = px.line(filled_data, x='frame', y=['angle_left_degree'], title='Ocular Movement Range') - if verbose: - print("Plot created") - # Save the plot as an image - fig.write_image(f"{save_path}/ocular_movement_range.png", width=1000, height=500, format='png', engine='kaleido') - if verbose: - print("Plot saved") - - # a 2d line plot with the vertical and horizontal components of the vectors (head orientation and eye gaze) through in a arbitrary time - #only draw left eye (one image for horizontal and one for vertical) - fig = px.line(filled_data, x='frame', y=['vertical_angle_left_degree'], title='Vertical ocular movement range') - fig.write_image(f"{save_path}/vertical_ocular_movement_range_left_eye.png", width=1000, height=500, format='png', engine='kaleido') - - fig = px.line(filled_data, x='frame', y=['horizontal_angle_left_degree'], title='Horizontal ocular movement range') - fig.write_image(f"{save_path}/horizontal_ocular_movement_range_left_eye.png", width=1000, height=500, format='png', engine='kaleido') - - - - - # a 3d line plot with the vectors (head orientation and eye gaze) through in a arbitrary time - # there will be 2 lines drawn, staring from 0,0,0 to the head orientation and eye gaze vectors - # the plot will be saved as html - fig = px.line_3d(filled_data, title='Head orientation vector') - fig.add_scatter3d(x=[0, filled_data['left_eye_gaze_x'][0]], y=[0, filled_data['left_eye_gaze_y'][0]], z=[0, filled_data['left_eye_gaze_z'][0]], mode='lines', name='Left eye gaze vector') - fig.add_scatter3d(x=[0, filled_data['head_orientation_x'][0]], y=[0, filled_data['head_orientation_y'][0]], z=[0, filled_data['head_orientation_z'][0]], mode='lines', name='Head orientation vector') - - fig.write_html(f"{save_path}/head_orientation_and_eye_gaze.html") - if verbose: - print("3d plot saved") - print(f'The angle between the head orientation and the left eye gaze is {angle_left[0]} degrees') + # Plotting the data + draw_eye_gaze_visualizations('left', filled_data, save_path) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generate visualizations for eye tracking data') From 21d251822de3e9a15f29b032d2bf7bceb52cf986 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 23:33:21 -0300 Subject: [PATCH 20/25] extrai tudo pra fora e chamei para os dois olhos o gaze --- visualization_generator/main.py | 103 ++++++++++++-------------------- 1 file changed, 37 insertions(+), 66 deletions(-) diff --git a/visualization_generator/main.py b/visualization_generator/main.py index 5211c97..b93e134 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -137,98 +137,69 @@ def draw_eye_gaze_visualizations(eye, data, save_path): fig_horizontal_angle = px.line(data, x='frame', y=[f'horizontal_angle_{eye}_degree'], title=f'{eye} eye horizontal ocular movement range') fig_horizontal_angle.write_image(f"{save_path}/{eye}_eye_horizontal_ocular_movement_range.png", width=1000, height=500, format='png', engine='kaleido') - - - - - -def generate_ocular_movement_range_vizualization(data, save_path="visualizations"): - # create a line plot for the angle between the head orientation and the eye gaze through time - +def process_eye_movement_range(data, eye, save_path): + filled_data = data.ffill() if verbose: - print("Generating ocular movement range visualization") - print("Saving in:", save_path) - print("Data columns:", data.columns) - print(data.head()) + print(f'{eye} data filled') - # get only the data needed for the plot (gaze and head orientation) - gaze_data = data[['frame', 'left_eye_gaze_x', 'left_eye_gaze_y', 'right_eye_gaze_x', 'right_eye_gaze_y', - 'head_orientation_x', 'head_orientation_y', 'head_orientation_z', - 'left_pupil_x', 'left_pupil_y', 'right_pupil_x', 'right_pupil_y']] - - if verbose: - print("Data for the plot filtered") - # first remove the lines with no data (it will assume the value of the last known value) - filled_data = gaze_data.ffill() - if verbose: - print("Data filled") - print(filled_data.head()) - # Adjust gaze vectors based on pupil positions - filled_data['left_eye_gaze_x'] -= filled_data['left_pupil_x'] - filled_data['left_eye_gaze_y'] -= filled_data['left_pupil_y'] - filled_data['right_eye_gaze_x'] -= filled_data['right_pupil_x'] - filled_data['right_eye_gaze_y'] -= filled_data['right_pupil_y'] + filled_data[f'{eye}_eye_gaze_x'] -= filled_data[f'{eye}_pupil_x'] + filled_data[f'{eye}_eye_gaze_y'] -= filled_data[f'{eye}_pupil_y'] # divide by 10 - filled_data['left_eye_gaze_x'] /= 10 - filled_data['left_eye_gaze_y'] /= 10 - filled_data['right_eye_gaze_x'] /= 10 - filled_data['right_eye_gaze_y'] /= 10 + filled_data[f'{eye}_eye_gaze_x'] /= 10 + filled_data[f'{eye}_eye_gaze_y'] /= 10 # Append the head orientation Z to gaze data to make 3D vectors - filled_data['left_eye_gaze_z'] = filled_data['head_orientation_z'] - filled_data['right_eye_gaze_z'] = filled_data['head_orientation_z'] + filled_data[f'{eye}_eye_gaze_z'] = filled_data['head_orientation_z'] + filled_data[f'{eye}_eye_gaze_z'] = filled_data['head_orientation_z'] # Normalize the gaze and head orientation vectors - right_eye_gaze = filled_data[['right_eye_gaze_x', 'right_eye_gaze_y', 'right_eye_gaze_z']].values - left_eye_gaze = filled_data[['left_eye_gaze_x', 'left_eye_gaze_y', 'left_eye_gaze_z']].values + eye_gaze = filled_data[[f'{eye}_eye_gaze_x', f'{eye}_eye_gaze_y', f'{eye}_eye_gaze_z']].values head_orientation = filled_data[['head_orientation_x', 'head_orientation_y', 'head_orientation_z']].values - # salva o df com os dados normalizados - filled_data.to_csv(f"{save_path}/normalized_vectors.csv", index=False) - - right_eye_gaze /= np.linalg.norm(right_eye_gaze, axis=1)[:, np.newaxis] - left_eye_gaze /= np.linalg.norm(left_eye_gaze, axis=1)[:, np.newaxis] + eye_gaze /= np.linalg.norm(eye_gaze, axis=1)[:, np.newaxis] head_orientation /= np.linalg.norm(head_orientation, axis=1)[:, np.newaxis] # Calcula o ângulo entre a orientação da cabeça e a direção do olhar para cada frame, convertendo para graus - + filled_data[f'{eye}_ocular_degree'] = get_angles_between_vectors(eye_gaze, head_orientation) + # agora pega somente a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) + filled_data[f'vertical_angle_{eye}_degree'] = get_angles_between_vectors(eye_gaze[:, 1:], head_orientation[:, 1:]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) - filled_data['left_ocular_degree'] = get_angles_between_vectors(left_eye_gaze, head_orientation) - filled_data['right_ocular_degree'] = get_angles_between_vectors(right_eye_gaze, head_orientation) - - + # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) + filled_data[f'horizontal_angle_{eye}_degree'] = get_angles_between_vectors(eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) - # agora pega somente a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) - filled_data['vertical_angle_left_degree'] = get_angles_between_vectors(left_eye_gaze[:, 1:], head_orientation[:, 1:]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) - filled_data['vertical_angle_right_degree'] = get_angles_between_vectors(right_eye_gaze[:, 1:], head_orientation[:, 1:]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) + # remove outliers and smooth data + filled_data = remove_outliers_and_smooth_data(filled_data, f'{eye}_ocular_degree', outlier_top_threshold=0.01, window_size=10) + filled_data.to_csv(f"{save_path}/{eye}_angles.csv", index=False) - # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) - filled_data['horizontal_angle_left_degree'] = get_angles_between_vectors(left_eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) - filled_data['horizontal_angle_right_degree'] = get_angles_between_vectors(right_eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) - + draw_eye_gaze_visualizations(eye, filled_data, save_path) - # save the data with the angles - filled_data.to_csv(f"{save_path}/angles.csv", index=False) + - # remove outliers and smooth data - filled_data = remove_outliers_and_smooth_data(filled_data, 'left_ocular_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, 'right_ocular_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, 'vertical_angle_left_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, 'vertical_angle_right_degree', outlier_top_threshold=0.01, window_size=10) +def generate_ocular_movement_range_vizualization(data, save_path="visualizations"): + # create a line plot for the angle between the head orientation and the eye gaze through time - filled_data = remove_outliers_and_smooth_data(filled_data, 'horizontal_angle_left_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, 'horizontal_angle_right_degree', outlier_top_threshold=0.01, window_size=10) - + if verbose: + print("Generating ocular movement range visualization") + print("Saving in:", save_path) + print("Data columns:", data.columns) + print(data.head()) + # get only the data needed for the plot (gaze and head orientation) + gaze_data = data[['frame', 'left_eye_gaze_x', 'left_eye_gaze_y', 'right_eye_gaze_x', 'right_eye_gaze_y', + 'head_orientation_x', 'head_orientation_y', 'head_orientation_z', + 'left_pupil_x', 'left_pupil_y', 'right_pupil_x', 'right_pupil_y']] - # Plotting the data - draw_eye_gaze_visualizations('left', filled_data, save_path) + if verbose: + print("Data for the plot filtered") + + process_eye_movement_range(gaze_data, 'left', save_path) + process_eye_movement_range(gaze_data, 'right', save_path) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generate visualizations for eye tracking data') From 66a0e69667fecd02845763ff9311457f0bae630d Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Fri, 12 Apr 2024 23:43:06 -0300 Subject: [PATCH 21/25] =?UTF-8?q?extrai=20todo=20o=20conteudo=20da=20fun?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20fixation=20e=20deixei=20ela=20processar=20?= =?UTF-8?q?os=20dois=20olhos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- visualization_generator/main.py | 57 ++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/visualization_generator/main.py b/visualization_generator/main.py index b93e134..c47fbbf 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -48,6 +48,35 @@ verbose = False +def draw_fixation_visualization(data_df, eye, save_path): + fig = px.line(data_df, x='frame', y=[f'{eye}_pupil_x', f'{eye}_pupil_y'], title=f'{eye} eye fixation') + fig.write_image(f"{save_path}/{eye}_eye_fixation.png", width=1000, height=500, format='png', engine='kaleido') + + if verbose: + print(f"{eye} eye fixation plot saved") + + +def process_eye_fixation_data(data, eye, save_path): + filled_data = data.ffill() + if verbose: + print(f'{eye} data filled') + + # Adjust gaze vectors based on pupil positions + filled_data[f'{eye}_pupil_x'] -= filled_data[f'nose_tip_x'] + filled_data[f'{eye}_pupil_y'] -= filled_data[f'nose_tip_y'] + + # move the data so the mean is 0 + filled_data[f'{eye}_pupil_x'] -= filled_data[f'{eye}_pupil_x'].mean() + filled_data[f'{eye}_pupil_y'] -= filled_data[f'{eye}_pupil_y'].mean() + + # remove outliers and smooth data + filled_data = remove_outliers_and_smooth_data(filled_data, f'{eye}_pupil_x', outlier_top_threshold=0.001, window_size=3) + filled_data = remove_outliers_and_smooth_data(filled_data, f'{eye}_pupil_y', outlier_top_threshold=0.001, window_size=3) + + filled_data.to_csv(f"{save_path}/{eye}_pupil_position.csv", index=False) + + draw_fixation_visualization(filled_data, eye, save_path) + def generate_eye_fixation_visualization(data_df, save_path="visualizations"): # create a line plot for the iris and pupil position through time @@ -62,32 +91,8 @@ def generate_eye_fixation_visualization(data_df, save_path="visualizations"): if(verbose): print("Data for the plot filtered") - # first remove the lines with no data (it will assume the value of the last known value) - filled_data = pupil_data.ffill() - if(verbose): - print("data filled") - print(filled_data.head()) - - # remove the head movement from the pupil position using the nose tip as a reference - filled_data['left_pupil_x'] = filled_data['left_pupil_x'] - filled_data['nose_tip_x'] - filled_data['left_pupil_y'] = filled_data['left_pupil_y'] - filled_data['nose_tip_y'] - - # moves the data so the mean is 0 - filled_data['left_pupil_x'] = filled_data['left_pupil_x'] - filled_data['left_pupil_x'].mean() - filled_data['left_pupil_y'] = filled_data['left_pupil_y'] - filled_data['left_pupil_y'].mean() - - - - fig = px.line(filled_data, x='frame', y=['left_pupil_x', 'left_pupil_y'], title='Left eye fixation') - if(verbose): - print("plot created") - # saves as 1000x1000 image - fig.write_image(f"{save_path}/left_eye_fixation.png", width=1000, height=500 , format='png',engine='kaleido') - if(verbose): - print("plot saved") - - # save the data with the left pupil position - filled_data.to_csv(f"{save_path}/left_pupil_position.csv", index=False) + process_eye_fixation_data(pupil_data, 'left', save_path) + process_eye_fixation_data(pupil_data, 'right', save_path) def remove_outliers_and_smooth_data(data, column_name, outlier_top_threshold=0.01, window_size=10): # remove outliers and smooth data From b4dc56ed9180eb2700c3f7a45fef59ff61280277 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Sat, 13 Apr 2024 00:08:47 -0300 Subject: [PATCH 22/25] tinha esquecido dos filtros nos movimentos verticais e horizontal --- visualization_generator/main.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/visualization_generator/main.py b/visualization_generator/main.py index c47fbbf..6060d3f 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -166,17 +166,20 @@ def process_eye_movement_range(data, eye, save_path): eye_gaze /= np.linalg.norm(eye_gaze, axis=1)[:, np.newaxis] head_orientation /= np.linalg.norm(head_orientation, axis=1)[:, np.newaxis] + # Calcula o ângulo entre a orientação da cabeça e a direção do olhar para cada frame, convertendo para graus filled_data[f'{eye}_ocular_degree'] = get_angles_between_vectors(eye_gaze, head_orientation) # agora pega somente a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) - filled_data[f'vertical_angle_{eye}_degree'] = get_angles_between_vectors(eye_gaze[:, 1:], head_orientation[:, 1:]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) + filled_data[f'vertical_angle_{eye}_degree'] = get_angles_between_vectors(eye_gaze[:, [1, 2]], head_orientation[:, [1, 2]]) # pega a componente vertical da diferença angular (ou seja projeta o vetor no plano y,z) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) filled_data[f'horizontal_angle_{eye}_degree'] = get_angles_between_vectors(eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) # remove outliers and smooth data filled_data = remove_outliers_and_smooth_data(filled_data, f'{eye}_ocular_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, f'vertical_angle_{eye}_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, f'horizontal_angle_{eye}_degree', outlier_top_threshold=0.01, window_size=10) filled_data.to_csv(f"{save_path}/{eye}_angles.csv", index=False) @@ -203,8 +206,8 @@ def generate_ocular_movement_range_vizualization(data, save_path="visualizations if verbose: print("Data for the plot filtered") - process_eye_movement_range(gaze_data, 'left', save_path) - process_eye_movement_range(gaze_data, 'right', save_path) + process_eye_movement_range(gaze_data.copy(), 'left', save_path) + process_eye_movement_range(gaze_data.copy(), 'right', save_path) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generate visualizations for eye tracking data') From e080110e042973be852321bac9b8a7f794010dc4 Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Sat, 13 Apr 2024 00:17:53 -0300 Subject: [PATCH 23/25] como agora o angulo pode ser negativo, eu removo outliers de baixo tb --- visualization_generator/main.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/visualization_generator/main.py b/visualization_generator/main.py index 6060d3f..c62a9f9 100644 --- a/visualization_generator/main.py +++ b/visualization_generator/main.py @@ -94,11 +94,13 @@ def generate_eye_fixation_visualization(data_df, save_path="visualizations"): process_eye_fixation_data(pupil_data, 'left', save_path) process_eye_fixation_data(pupil_data, 'right', save_path) -def remove_outliers_and_smooth_data(data, column_name, outlier_top_threshold=0.01, window_size=10): +def remove_outliers_and_smooth_data(data, column_name, outlier_top_threshold=0.01 , window_size=10, outlier_bottom_threshold = None): # remove outliers and smooth data # outliers in this context are the top N% of the data # the remotion will be done nullifying the values and then using the ffill method to fill the null values data.loc[data[column_name] > data[column_name].quantile(1 - outlier_top_threshold), column_name] = None + if outlier_bottom_threshold is not None: + data.loc[data[column_name] < data[column_name].quantile(outlier_bottom_threshold), column_name] = None data = data.ffill() # smooth the data using a rolling mean @@ -177,9 +179,9 @@ def process_eye_movement_range(data, eye, save_path): filled_data[f'horizontal_angle_{eye}_degree'] = get_angles_between_vectors(eye_gaze[:, [0, 2]], head_orientation[:, [0, 2]]) # pega a componente horizontal da diferença angular (ou seja projeta o vetor no plano x,z) # remove outliers and smooth data - filled_data = remove_outliers_and_smooth_data(filled_data, f'{eye}_ocular_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, f'vertical_angle_{eye}_degree', outlier_top_threshold=0.01, window_size=10) - filled_data = remove_outliers_and_smooth_data(filled_data, f'horizontal_angle_{eye}_degree', outlier_top_threshold=0.01, window_size=10) + filled_data = remove_outliers_and_smooth_data(filled_data, f'{eye}_ocular_degree', outlier_top_threshold=0.01, window_size=10, outlier_bottom_threshold=0.01) + filled_data = remove_outliers_and_smooth_data(filled_data, f'vertical_angle_{eye}_degree', outlier_top_threshold=0.01, window_size=10, outlier_bottom_threshold=0.01) + filled_data = remove_outliers_and_smooth_data(filled_data, f'horizontal_angle_{eye}_degree', outlier_top_threshold=0.01, window_size=10, outlier_bottom_threshold=0.01) filled_data.to_csv(f"{save_path}/{eye}_angles.csv", index=False) From 40116cf90335af85f90581446336411e3942360d Mon Sep 17 00:00:00 2001 From: CodeWracker Date: Sat, 13 Apr 2024 11:14:28 -0300 Subject: [PATCH 24/25] update readme --- README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 72064e1..3a65278 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ A combinação que teve o melhor resultado foi analisar o rosto com o mediapipe Com a imagem correspondente a cada olho foi-se feite uma analise de histograma das cores da imagem, apos alguns filtros serem aplicados, para tentar analisar onde estavam a iris e a pupila. O resultado foi que a pupia é razoavelmente fácil e consistente de se obter, contudo, a iris não. Por conta disso será adicionada mais uma etapa no processo de analise, com um outro algorítimo da mediapipe, o qual extrai a posição da iris de um rosto. ## Pré-requisitos + - Python 3.9 - pipenv + ## Como rodar Eu utilizo o gerenciador de pacotes pipenv, caso não o tenha instalado execute `pip install pipenv` @@ -25,7 +27,9 @@ Para rodar o programa: pipenv run pyinstaller --onefile --paths [seu_caminho] ./main.py --add-data '[seu_caminho]\Lib\site-packages\mediapipe\modules:mediapipe/modules' --add-data '[seu_caminho]\Lib\site-packages\mediapipe\python\solutions:mediapipe/solutions' ``` + Substitua [seu_caminho] pelo caminho apropriado do seu ambiente. + ## Regras PROIBIDO colocar pastas que contenham o caractere '.' no nome dentro do diretório /raw para o processamento em lote @@ -33,74 +37,95 @@ PROIBIDO colocar pastas que contenham o caractere '.' no nome dentro do diretór ## Documentação de Argumentos CLI ### Descrição + O script aceita vários argumentos de linha de comando para configurar suas opções globais. Abaixo estão as opções disponíveis e suas descrições. ### Argumentos #### -showprocess + Define se o processo será mostrado. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n #### -drawbb + Define se a bounding box será desenhada. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n #### -drawir + Define se a íris será desenhada. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n #### -drawpu + Define se a pupila será desenhada. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n #### -drawpp + Define se as posições passadas serão desenhadas. - Valores aceitos: s (sim) ou n (não) - Valor padrão: s #### -drawmp + Define se os pontos da máscara serão desenhados. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n -#### -drawgz -Define se o olhar será desenhado. +#### -drawheadpose + +Define se o vetor de direção da cabeça vai ser mostrado + +- Valores aceitos: s (sim) ou n (não) +- Valor padrão: n + +#### -draweyegaze + +Define se os vetores de direção do olhar de ambos os olhos vão ser mostrados - Valores aceitos: s (sim) ou n (não) - Valor padrão: n #### -showwarn + Define se os avisos serão mostrados. - Valores aceitos: s (sim) ou n (não) - Valor padrão: s #### -multicore + Define se o processamento multicore será usado. - Valores aceitos: s (sim) ou n (não) - Valor padrão: n #### -overwrite + Define se os arquivos existentes serão sobrescritos. Se -multicore for s, o valor padrão é s. Caso contrário, o valor padrão é n. - Valores aceitos: s (sim) ou n (não) #### -path + Define o caminho para os arquivos. - Valor padrão: ""./vds" + ##### Exemplo de Uso `python main.py -showprocess s -drawbb n -path "./meus_arquivos"` ->Nota: Se um argumento inválido for fornecido para uma opção, o script imprimirá uma mensagem de erro e encerrará sua execução. \ No newline at end of file + +> Nota: Se um argumento inválido for fornecido para uma opção, o script imprimirá uma mensagem de erro e encerrará sua execução. From 6db60c2561a09af2e77fdca83582bced63c5ca65 Mon Sep 17 00:00:00 2001 From: Rodrigo Ferraz Souza Date: Wed, 17 Apr 2024 19:52:34 -0300 Subject: [PATCH 25/25] Criei um workflow que verifica pull requests verifica se tem ao menos uma labelm, uma pessoa para review e ao menos uma pessoa atribuida --- .github/workflows/main.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..bac1fcb --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,37 @@ +name: Verifica Pull Request + +on: + pull_request: + types: [opened, reopened, labeled, review_requested, assigned] + +jobs: + check-pr: + runs-on: ubuntu-latest + steps: + - name: Checar labels + uses: actions/github-script@v3 + with: + script: | + const pullRequest = github.context.payload.pull_request; + if (pullRequest.labels.length === 0) { + core.setFailed("Pull Request deve ter pelo menos um label."); + } + + - name: Checar reviewers + uses: actions/github-script@v3 + with: + script: | + const pullRequest = github.context.payload.pull_request; + const reviewsRequested = pullRequest.requested_reviewers.length + pullRequest.requested_teams.length; + if (reviewsRequested === 0) { + core.setFailed("Pull Request deve ter pelo menos um reviewer."); + } + + - name: Checar assignees + uses: actions/github-script@v3 + with: + script: | + const pullRequest = github.context.payload.pull_request; + if (pullRequest.assignees.length === 0) { + core.setFailed("Pull Request deve ter pelo menos um assignee."); + }