Merge branch 'HSF-and-new-algos-feature-branch' into HSF-and-new-algos-feature-branch

This commit is contained in:
Prohurtz 2023-02-09 20:27:04 -06:00 committed by GitHub
commit 12304b5b3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 715 additions and 1562 deletions

View File

@ -19,8 +19,8 @@
@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@( @@@@@@@@@@@@@(
HSR By: Sean.Denka (Optimization Wizard, Contributor), Summer#2406 (Main Algorithm Engineer) HSR By: PallasNeko (Optimization Wizard, Contributor), Summer#2406 (Main Algorithm Engineer)
RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), Sean.Denka (Optimization) RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), PallasNeko (Optimization)
BLOB By: Prohurtz#0001 (Main App Developer) BLOB By: Prohurtz#0001 (Main App Developer)
Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator) Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator)
@ -51,10 +51,10 @@ if sys.platform.startswith("win"):
import importlib import importlib
from osc_calibrate_filter import * from osc_calibrate_filter import *
from haar_surround_feature import * from haar_surround_feature import External_Run_HSF
from blob import * from blob import *
from ransac import * from ransac import *
from hsrac import * from hsrac import External_Run_HSRACS
from blink import * from blink import *
@ -147,7 +147,6 @@ class EyeProcessor:
self.cccs = False self.cccs = False
self.ts = 10 self.ts = 10
self.previous_rotation = self.config.rotation_angle self.previous_rotation = self.config.rotation_angle
self.calibration_frame_counter
self.camera_model = None self.camera_model = None
self.detector_3d = None self.detector_3d = None
@ -242,13 +241,13 @@ class EyeProcessor:
pass pass
def BLINKM(self): def BLINKM(self):
self.blinkvalue = BLINK(self) self.eyeoffx = BLINK(self)
def HSRACM(self): def HSRACM(self):
cx, cy, thresh, gray_frame = External_Run_HSRACS.HSRACS(self) cx, cy, thresh, gray_frame, uncropframe = External_Run_HSRACS().run(self.current_image_gray)
self.current_image_gray = gray_frame self.current_image_gray = gray_frame
if self.prev_x == None: if self.prev_x is None:
self.prev_x = cx self.prev_x = cx
self.prev_y = cy self.prev_y = cy
#print(self.prev_x, self.prev_y, cx, cy) #print(self.prev_x, self.prev_y, cx, cy)
@ -256,43 +255,43 @@ class EyeProcessor:
# if (cx - self.prev_x) <= 45 and (cy - self.prev_y) <= 45 : # if (cx - self.prev_x) <= 45 and (cy - self.prev_y) <= 45 :
# self.prev_x = cx # self.prev_x = cx
# self.prev_y = cy # self.prev_y = cy
eyeopen = intense(cx, cy, self.current_image_gray) self.eyeopen = intense(cx, cy, uncropframe)
out_x, out_y = cal_osc(self, cx, cy) out_x, out_y = cal_osc(self, cx, cy)
#print(self.eyeoffx, self.eyeopen)
if cx == 0: if self.eyeoffx:
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, eyeopen)) #update app self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, 0.0)) #update app
else: else:
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, self.eyeopen))
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, eyeopen))
# else: # else:
# print("EYE MOVED TOO FAST") # print("EYE MOVED TOO FAST")
# self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, 0, 0, 0, False)) # self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, 0, 0, 0, False))
def HSFM(self): def HSFM(self):
cx, cy, frame = External_Run_HSF.HSFS(self) cx, cy, frame = External_Run_HSF().run(self.current_image_gray)
eyeopen = intense(cx, cy, self.current_image_gray) self.eyeopen = intense(cx, cy, self.current_image_gray)
out_x, out_y = cal_osc(self, cx, cy) out_x, out_y = cal_osc(self, cx, cy)
if cx == 0: if cx == 0:
self.output_images_and_update(frame, EyeInformation(InformationOrigin.HSF, out_x, out_y, 0, eyeopen)) #update app self.output_images_and_update(frame, EyeInformation(InformationOrigin.HSF, out_x, out_y, 0, self.eyeopen)) #update app
else: else:
self.output_images_and_update(frame, EyeInformation(InformationOrigin.HSF, out_x, out_y, 0, eyeopen)) self.output_images_and_update(frame, EyeInformation(InformationOrigin.HSF, out_x, out_y, 0, self.eyeopen))
def RANSAC3DM(self): def RANSAC3DM(self):
cx, cy, thresh = RANSAC3D(self) cx, cy, thresh = RANSAC3D(self)
eyeopen = intense(cx, cy, self.current_image_gray) self.eyeopen = intense(cx, cy, self.current_image_gray)
out_x, out_y = cal_osc(self, cx, cy) out_x, out_y = cal_osc(self, cx, cy)
if cx == 0: if cx == 0:
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.RANSAC, out_x, out_y, 0, eyeopen)) #update app self.output_images_and_update(thresh, EyeInformation(InformationOrigin.RANSAC, out_x, out_y, 0, self.eyeopen)) #update app
else: else:
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.RANSAC, out_x, out_y, 0, eyeopen)) self.output_images_and_update(thresh, EyeInformation(InformationOrigin.RANSAC, out_x, out_y, 0, self.eyeopen))
def BLOBM(self): def BLOBM(self):
cx, cy, thresh = BLOB(self) cx, cy, thresh = BLOB(self)
eyeopen = intense(cx, cy, self.current_image_gray) self.eyeopen = intense(cx, cy, self.current_image_gray)
out_x, out_y = cal_osc(self, cx, cy) out_x, out_y = cal_osc(self, cx, cy)
if cx == 0: if cx == 0:
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, eyeopen)) #update app self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, self.eyeopen)) #update app
else: else:
self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, eyeopen)) self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, out_x, out_y, 0, self.eyeopen))
@ -347,7 +346,7 @@ class EyeProcessor:
elif self.settings.gui_RANSAC3D and self.settings.gui_RANSAC3DP == 4: elif self.settings.gui_RANSAC3D and self.settings.gui_RANSAC3DP == 4:
self.fourthalgo = self.RANSAC3DM self.fourthalgo = self.RANSAC3DM
if self.settings.gui_HSRAC == True and self.settings.gui_HSRACP == 1: if self.settings.gui_HSRAC and self.settings.gui_HSRACP == 1:
self.firstalgo = self.HSRACM self.firstalgo = self.HSRACM
elif self.settings.gui_HSRAC and self.settings.gui_HSRACP == 2: elif self.settings.gui_HSRAC and self.settings.gui_HSRACP == 2:
self.secondalgo = self.HSRACM self.secondalgo = self.HSRACM

View File

@ -7,8 +7,7 @@ import queue
import threading import threading
import PySimpleGUI as sg import PySimpleGUI as sg
import sys import sys
from urllib.request import urlopen import urllib.request
from bs4 import BeautifulSoup
import webbrowser import webbrowser
@ -53,35 +52,33 @@ def main():
if config.settings.gui_update_check: if config.settings.gui_update_check:
print("\033[95m[INFO] Checking for updates...\033[0m") print("\033[95m[INFO] Checking for updates...\033[0m")
url = "https://raw.githubusercontent.com/RedHawk989/EyeTrackVR-Installer/master/Version-Data/Version_Num.txt" url = "https://raw.githubusercontent.com/RedHawk989/EyeTrackVR-Installer/master/Version-Data/Version_Num.txt"
html = urlopen(url).read() req = urllib.request.Request(url)
soup = BeautifulSoup(html, features="html.parser") try:
for script in soup(["script", "style"]): with urllib.request.urlopen(req, timeout=10) as res:
script.extract() latestversion = res.read().decode("utf-8").strip()
text = soup.get_text() except urllib.error.HTTPError as err:
print("Failed to check latest version.")
# break into lines and remove leading and trailing space on each print("{} : {}".format(err.code,err.reason))
lines = (line.strip() for line in text.splitlines()) except urllib.error.URLError as err:
# break multi-headlines into a line each print("Failed to check latest version.")
chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) print(err.reason)
# drop blank lines else:
latestversion = '\n'.join(chunk for chunk in chunks if chunk)
if appversion == latestversion: # If what we scraped and hardcoded versions are same, assume we are up to date. if appversion == latestversion: # If what we scraped and hardcoded versions are same, assume we are up to date.
print(f"\033[92m[INFO] App is up to date! [{latestversion}]\033[0m") print(f"\033[92m[INFO] App is up to date! [{latestversion}]\033[0m")
else: else:
print(f"\033[93m[INFO] You have app version [{appversion}] installed. Please update to [{latestversion}] for the newest features.\033[0m") print(
f"\033[93m[INFO] You have app version [{appversion}] installed. Please update to [{latestversion}] for the newest features.\033[0m")
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
toaster = ToastNotifier() toaster = ToastNotifier()
toaster.show_toast( #show windows toast toaster.show_toast( # show windows toast
"EyeTrackVR has an update.", "EyeTrackVR has an update.",
"Click to go to the latest version.", "Click to go to the latest version.",
icon_path= "Images/logo.ico", icon_path="Images/logo.ico",
duration=5, duration=5,
threaded=True, threaded=True,
callback_on_click=open_url callback_on_click=open_url
) )
# Check to see if we have an ROI. If not, bring up ROI finder GUI. # Check to see if we have an ROI. If not, bring up ROI finder GUI.
# Spawn worker threads # Spawn worker threads

View File

@ -1,16 +1,18 @@
import functools
import math
import sys
import timeit import timeit
from functools import lru_cache from functools import lru_cache
import cv2 import cv2
import numpy as np import numpy as np
from utils.misc_utils import clamp
from utils.img_utils import safe_crop
# from line_profiler_pycharm import profile # from line_profiler_pycharm import profile
video_path = "ezgif.com-gif-maker.avi" video_path = "ezgif.com-gif-maker.avi"
imshow_enable = True imshow_enable = False
calc_print_enable = True calc_print_enable = True
save_video = False save_video = False
skip_autoradius = False skip_autoradius = False
@ -27,181 +29,6 @@ blink_init_frames = 60 * 3 # 60fps*3sec,Number of blink statistical frames
# step==(x,y) # step==(x,y)
default_step = (5, 5) # bigger the steps,lower the processing time! ofc acc also takes an impact default_step = (5, 5) # bigger the steps,lower the processing time! ofc acc also takes an impact
"""
Attention.
If using cv2.filter2D in this code, be careful with the kernel
https://stackoverflow.com/questions/39457468/convolution-without-any-padding-opencv-python
"""
def TimeitWrapper(*args, **kwargs):
"""
This decorator @TimeitWrapper() prints the function name and execution time in seconds.
:param args:
:param kwargs:
:return:
"""
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
start = timeit.default_timer()
results = function(*args, **kwargs)
end = timeit.default_timer()
print('{} execution time: {:.10f} s'.format(function.__name__, end - start))
return results
return wrapper
return decorator
class TimeitResult(object):
"""
from https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
Object returned by the timeit magic with info about the run.
Contains the following attributes :
loops: (int) number of loops done per measurement
repeat: (int) number of times the measurement has been repeated
best: (float) best execution time / number
all_runs: (list of float) execution time of each run (in s)
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = best
self.worst = worst
self.all_runs = all_runs
self._precision = precision
self.timings = [dt / self.loops for dt in all_runs]
@property
def average(self):
return math.fsum(self.timings) / len(self.timings)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean=format_time(self.average, self._precision),
std=format_time(self.stdev, self._precision),
best=format_time(self.best, self._precision),
worst=format_time(self.worst, self._precision),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<TimeitResult : ' + unic + u'>')
class FPSResult(object):
"""
base https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = 1 / best
self.worst = 1 / worst
self.all_runs = all_runs
self._precision = precision
self.fps = [1 / dt for dt in all_runs]
self.unit = "fps"
@property
def average(self):
return math.fsum(self.fps) / len(self.fps)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.fps]) / len(self.fps)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean="%.*g%s" % (self._precision, self.average, self.unit),
std="%.*g%s" % (self._precision, self.stdev, self.unit),
best="%.*g%s" % (self._precision, self.best, self.unit),
worst="%.*g%s" % (self._precision, self.worst, self.unit),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<FPSResult : ' + unic + u'>')
def format_time(timespan, precision=3):
"""
https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L1473
Formats the timespan in a human readable form
"""
if timespan >= 60.0:
# we have more than a minute, format that in a human readable form
# Idea from http://snipplr.com/view/5713/
parts = [("d", 60 * 60 * 24), ("h", 60 * 60), ("min", 60), ("s", 1)]
time = []
leftover = timespan
for suffix, length in parts:
value = int(leftover / length)
if value > 0:
leftover = leftover % length
time.append(u'%s%s' % (str(value), suffix))
if leftover < 1:
break
return " ".join(time)
# Unfortunately the unicode 'micro' symbol can cause problems in
# certain terminals.
# See bug: https://bugs.launchpad.net/ipython/+bug/348466
# Try to prevent crashes by being more secure than it needs to
# E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
units = [u"s", u"ms", u'us', "ns"] # the save value
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb5'.encode(sys.stdout.encoding)
units = [u"s", u"ms", u'\xb5s', "ns"]
except:
pass
scaling = [1, 1e3, 1e6, 1e9]
if timespan > 0.0:
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
else:
order = 3
return u"%.*g %s" % (precision, timespan * scaling[order], units[order])
class CvParameters: class CvParameters:
# It may be a little slower because a dict named "self" is read for each function call. # It may be a little slower because a dict named "self" is read for each function call.
def __init__(self, radius, step): def __init__(self, radius, step):
@ -419,7 +246,7 @@ def conv_int(frame_int, kernel, xy_step, padding, xy_steps_list):
return frame_conv, min_response, center return frame_conv, min_response, center
class Auto_Radius_Calc(object): class AutoRadiusCalc(object):
def __init__(self): def __init__(self):
self.response_list = [] self.response_list = []
self.radius_cand_list = [] self.radius_cand_list = []
@ -522,7 +349,7 @@ class Auto_Radius_Calc(object):
return None return None
class Blink_Detector(object): class BlinkDetector(object):
def __init__(self): def __init__(self):
self.response_list = [] self.response_list = []
self.response_max = None self.response_max = None
@ -665,7 +492,7 @@ class CenterCorrection(object):
return out_x, out_y return out_x, out_y
class HSRAC_cls(object): class HSF_cls(object):
def __init__(self): def __init__(self):
# I'd like to take into account things like print, end_time - start_time processing time, etc., but it's too much trouble. # I'd like to take into account things like print, end_time - start_time processing time, etc., but it's too much trouble.
@ -679,9 +506,9 @@ class HSRAC_cls(object):
self.cv_modeo = ["first_frame", "radius_adjust", "blink_adjust", "normal"] self.cv_modeo = ["first_frame", "radius_adjust", "blink_adjust", "normal"]
self.now_modeo = self.cv_modeo[0] self.now_modeo = self.cv_modeo[0]
self.auto_radius_calc = Auto_Radius_Calc() self.auto_radius_calc = AutoRadiusCalc()
self.blink_detector = Blink_Detector() self.blink_detector = BlinkDetector()
self.center_q1 = Blink_Detector() self.center_q1 = BlinkDetector()
self.center_correct = CenterCorrection() self.center_correct = CenterCorrection()
self.cap = None self.cap = None
@ -710,8 +537,12 @@ class HSRAC_cls(object):
def single_run(self): def single_run(self):
# Temporary implementation to run # Temporary implementation to run
## default_radius = 14 ## default_radius = 14
# cropbox=[] # debug code
frame = self.current_image_gray frame = self.current_image_gray
if self.now_modeo == self.cv_modeo[1]: if self.now_modeo == self.cv_modeo[1]:
# adjustment of radius # adjustment of radius
@ -756,7 +587,12 @@ class HSRAC_cls(object):
lower_y = center_y - radius lower_y = center_y - radius
# Crop the image using the calculated bounds # Crop the image using the calculated bounds
cropped_image = gray_frame[lower_y:upper_y, lower_x:upper_x]
cropped_image = safe_crop(gray_frame, lower_x, lower_y, upper_x, upper_y)
# cropbox = [clamp(val, 0, gray_frame.shape[i]) for i, val in
# zip([1, 0, 1, 0], [lower_x, lower_y, upper_x, upper_y])] # debug code
if self.now_modeo == self.cv_modeo[0] or self.now_modeo == self.cv_modeo[1]: if self.now_modeo == self.cv_modeo[0] or self.now_modeo == self.cv_modeo[1]:
# If mode is first_frame or radius_adjust, record current radius and response # If mode is first_frame or radius_adjust, record current radius and response
@ -770,7 +606,13 @@ class HSRAC_cls(object):
lower_x = center_x - self.center_correct.center_q1_radius lower_x = center_x - self.center_correct.center_q1_radius
upper_y = center_y + self.center_correct.center_q1_radius upper_y = center_y + self.center_correct.center_q1_radius
lower_y = center_y - self.center_correct.center_q1_radius lower_y = center_y - self.center_correct.center_q1_radius
self.center_q1.add_response(cv2.mean(gray_frame[lower_y:upper_y, lower_x:upper_x])[0])
self.center_q1.add_response(
cv2.mean(safe_crop(gray_frame, lower_x, lower_y, upper_x, upper_y,keepsize=False))[
0
]
)
else: else:
@ -792,9 +634,19 @@ class HSRAC_cls(object):
else: else:
# pass # pass
if not self.center_correct.setup_comp: if not self.center_correct.setup_comp:
self.center_correct.init_array(gray_frame.shape, self.center_q1.quartile_1, radius) self.center_correct.init_array(
gray_frame.shape, self.center_q1.quartile_1, radius
)
elif self.center_correct.frame_shape != gray_frame.shape:
"""The resolution should have changed and the statistics should have changed, so essentially the statistics
need to be reworked, but implementation will be postponed as viability is the highest priority. """
self.center_correct.init_array(
gray_frame.shape, self.center_q1.quartile_1, radius
)
center_x, center_y = self.center_correct.correction(gray_frame, center_x, center_y) center_x, center_y = self.center_correct.correction(
gray_frame, center_x, center_y
)
# Define the center point and radius # Define the center point and radius
center_xy = (center_x, center_y) center_xy = (center_x, center_y)
upper_x = center_x + radius upper_x = center_x + radius
@ -802,10 +654,16 @@ class HSRAC_cls(object):
upper_y = center_y + radius upper_y = center_y + radius
lower_y = center_y - radius lower_y = center_y - radius
# Crop the image using the calculated bounds # Crop the image using the calculated bounds
cropped_image = gray_frame[lower_y:upper_y, lower_x:upper_x] cropped_image = safe_crop(
gray_frame, lower_x, lower_y, upper_x, upper_y
)
# cropbox = [clamp(val, 0, gray_frame.shape[i]) for i, val in
# zip([1, 0, 1, 0], [lower_x, lower_y, upper_x, upper_y])] # debug code
# if imshow_enable or save_video: # if imshow_enable or save_video:
# cv2.circle(frame, (orig_x, orig_y), 6, (0, 0, 255), -1) # cv2.circle(frame, (orig_x, orig_y), 6, (0, 0, 255), -1)
# cv2.circle(frame, (center_x, center_y), 3, (255, 0, 0), -1) # cv2.circle(frame, (center_x, center_y), 3, (255, 0, 0), -1)
# If you want to update response_max. it may be more cost-effective to rewrite response_list in the following way # If you want to update response_max. it may be more cost-effective to rewrite response_list in the following way
# https://stackoverflow.com/questions/42771110/fastest-way-to-left-cycle-a-numpy-array-like-pop-push-for-a-queue # https://stackoverflow.com/questions/42771110/fastest-way-to-left-cycle-a-numpy-array-like-pop-push-for-a-queue
@ -838,19 +696,27 @@ class HSRAC_cls(object):
else: else:
self.now_modeo = self.cv_modeo[1] self.now_modeo = self.cv_modeo[1]
# debug code
# return center_x,center_y,cropbox,frame
return center_x, center_y, frame return center_x, center_y, frame
class External_Run_HSF: class External_Run_HSF(object):
def __init__(self):
self.algo = HSF_cls()
hsrac = HSRAC_cls() def run(self, current_image_gray):
self.algo.current_image_gray = current_image_gray
def HSFS(self): # debug code
External_Run_HSF.hsrac.current_image_gray = self.current_image_gray # center_x, center_y,cropbox, frame = self.algo.single_run()
center_x, center_y, frame = External_Run_HSF.hsrac.single_run() # return center_x, center_y,cropbox, frame
center_x, center_y, frame = self.algo.single_run()
return center_x, center_y, frame return center_x, center_y, frame
if __name__ == '__main__':
hsrac = HSRAC_cls()
hsrac.open_video(video_path) if __name__ == "__main__":
while hsrac.read_frame(): hsf = HSF_cls()
_ = hsrac.single_run() hsf.open_video(video_path)
while hsf.read_frame():
_ = hsf.single_run()

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,18 @@
import pandas as pd
import numpy as np import numpy as np
import time
import os
import cv2 import cv2
from pythonosc import udp_client
from enum import IntEnum
#higher intensity means more closed/ more white/less pupil #higher intensity means more closed/ more white/less pupil
#Hm I need an acronym for this, any ideas? #Hm I need an acronym for this, any ideas?
#IBO Intensity Based Openess #IBO Intensity Based Openess
class EyeId(IntEnum):
RIGHT = 0
LEFT = 1
BOTH = 2
SETTINGS = 3
# HOW THIS WORKS: # HOW THIS WORKS:
# we get the intensity of pupil area from HSF crop, When the eyelid starts to close, the pupil starts being obstructed by skin which is generally lighter than the pupil. # we get the intensity of pupil area from HSF crop, When the eyelid starts to close, the pupil starts being obstructed by skin which is generally lighter than the pupil.
@ -15,79 +20,183 @@ from pythonosc import udp_client
# ex. when you look up there is less pupil visible, which results in an uncalculated change in intensity even though the eyelid has not moved in a meaningful way. # ex. when you look up there is less pupil visible, which results in an uncalculated change in intensity even though the eyelid has not moved in a meaningful way.
# We compare the darkest intensity of that area, to the lightest (global) intensity to find the appropriate openness state via a float. # We compare the darkest intensity of that area, to the lightest (global) intensity to find the appropriate openness state via a float.
fname = "IBO.csv" #TODO Expose as setting
try: # Note.
data = pd.read_csv(fname, sep=",") # OpenCV on Windows will generate an error if the file path contains non-ASCII characters when using cv2.imread(), cv2.imwrite(), etc.
except: # https://stackoverflow.com/questions/43185605/how-do-i-read-an-image-from-a-path-with-unicode-characters
cf = open(fname, "w") # https://github.com/opencv/opencv/issues/18305
cf.write("xy,intensity")
cf.close()
data = pd.read_csv(fname, sep=",") if EyeId.RIGHT:
fname = "IBO_RIGHT.png"
if EyeId.LEFT:
fname = "IBO_LEFT.png"
lct = time.time()
data = None
def csv2data(frameshape, filepath):
# For data checking
frameshape = (frameshape[0], frameshape[1]+1)
out = np.zeros(frameshape, dtype=np.uint32)
xy_list = []
val_list = []
with open(filepath, mode="r", encoding="utf-8") as in_f:
# Skip header.
_ = in_f.readline()
for s in in_f:
xyval = [int(val) for val in s.strip().split(',')]
xy_list.append((xyval[0], xyval[1]))
val_list.append(xyval[2])
xy_list = np.array(xy_list)
val_list = np.array(val_list)
out[xy_list[:, 1], xy_list[:, 0]] = val_list[:]
return out
def data2csv(data_u32, filepath):
# For data checking
nonzero_index = np.nonzero(data_u32) #(row,col)
data_list = data_u32[nonzero_index].tolist()
datalines = ["{},{},{}\n".format(x, y, val) for y, x, val in zip(*nonzero_index, data_list)]
with open(filepath, 'w', encoding="utf-8") as out_f:
out_f.write("x,y,intensity\n")
out_f.writelines(datalines)
return
def u32_u16_1ch3ch(img):
img_copy = img.copy()
# In the case of bit operations. (img>>32)&0xffff,(img>>16)&0xffff,img&0xffff
out = np.zeros((*img.shape[:2], 3), dtype=np.uint16)
for i in range(3):
out[:, :, i] = img_copy % 0xffff
img_copy //= 0xffff
return out
def u16_u32_3ch_1ch(img):
# In the case of bit operations. ((img >> 32) & 0xffff) << 32 |((img >> 16) & 0xffff) << 16 | (img & 0xffff)
out = np.zeros(img.shape[:2], dtype=np.uint32)
for i in range(3):
out += img[:, :, i] if i == 0 else img[:, :, i] * (i * 0xffff)
return out
def newdata(frameshape):
print("Initialise data for blinking.")
return np.zeros(frameshape, dtype=np.uint32)
def check_and_load(frameshape, now_data):
# In the future, both eyes may be processed at the same time. Therefore, data should be passed as arguments.
req_newdata = False
# Not very clever, but increase the width by 1px to save the maximum value.
frameshape = (frameshape[0], frameshape[1]+1)
if now_data is None:
print("Load data for blinking: {}".format(fname))
if os.path.isfile(fname):
img = cv2.imread(fname, flags=cv2.IMREAD_UNCHANGED)
if img.shape[:2] != frameshape:
print("size does not match the input frame.")
req_newdata = True
else:
now_data = u16_u32_3ch_1ch(img)
else:
print("File does not exist.")
req_newdata = True
else:
if now_data.shape != frameshape:
# Using the previous and current frame sizes and centre positions from the original, etc., the data can be ported to some extent, but there may be many areas where code changes are required.
print("Frame size changed.")
req_newdata = True
if req_newdata:
now_data = newdata(frameshape)
# data2csv(now_data, "a.csv")
# csv2data(frameshape,"a.csv")
return now_data
#TODO we need more pixel points for smooth operation, lets get this setup in hsrac
def intense(x, y, frame): def intense(x, y, frame):
# upper_x = x + 25 global lct, data
# lower_x = x - 25
# upper_y = y + 25
# lower_y = y - 25
# frame = frame[lower_y:upper_y, lower_x:upper_x]
try: # 0 in data is used as the initial value.
xy = int(str(int(x)) + str(int(y)) + str(int(x)+int(y))) # When assigning a value, +1 is added to the value to be assigned.
intensity = np.sum(frame) data = check_and_load(frame.shape[:2], data)
except: int_x, int_y = int(x), int(y)
return 0.0 #TODO find how on earth a hyphen gets thrown into this
# upper_x = min(int_x + 25, frame.shape[1]) #TODO make this a setting
#lower_x = max(int_x - 25, 0)
#upper_y = min(int_y + 25, frame.shape[0])
#lower_y = max(int_y - 25, 0)
#frame_crop = frame[lower_y:upper_y, lower_x:upper_x]
frame_crop = frame
# The same can be done with cv2.integral, but since there is only one area of the rectangle for which we want to know the total value, there is no advantage in terms of computational complexity.
intensity = frame_crop.sum()+1
# numpy:np.sum(),ndarray.sum()
# opencv:cv2.sumElems()
# I don't know which is faster.
print(frame.shape[1], frame.shape[0], int_x, int_y)
changed = False changed = False
try: #max pupil per cord newval_flg = False
dfb = data[data['xy']==xy].index.values.astype(int)[0] # find pandas index of line with matching xy value if int_y >= frame.shape[1]:
data_val = 0
print('CAUGHT Y OUT OF BOUNDS')
if intensity < data.at[dfb, 'intensity']: #if current intensity value is less (more pupil), save that else:
data.at[dfb, 'intensity'] = intensity # set value
data_val = data[int_y, int_x]
# max pupil per cord
if data_val == 0:
# The value of the specified coordinates has not yet been recorded.
data[int_y, int_x] = intensity
changed = True
newval_flg = True
elif intensity < data_val: # if current intensity value is less (more pupil), save that
data[int_y, int_x] = intensity # set value
changed = True changed = True
print("var adjusted") print("var adjusted")
else: else:
intensitya = data.at[dfb, 'intensity'] - 3 #if current intensity value is less (more pupil), save that intensitya = max(data_val - 3, 1) # if current intensity value is less (more pupil), save that
data.at[dfb, 'intensity'] = intensitya # set value data[int_y, int_x] = intensitya # set value
changed = True changed = True
except: # that value is not yet saved # min pupil global
data.loc[len(data.index)] = [xy, intensity] #create new data on last line of csv with current intesity if data[0, -1] == 0: # that value is not yet saved
changed = True data[0, -1] = intensity # set value at 0 index
try: # min pupil global
if intensity > data.at[0, 'intensity']: #if current intensity value is more (less pupil), save that NOTE: we have the
data.at[0, 'intensity'] = intensity # set value at 0 index
changed = True
print("new max", intensity)
else:
intensityd = data.at[0, 'intensity'] - 10 #continuously adjust closed intensity, will be set when user blink, used to allow eyes to close when lighting changes
data.at[0, 'intensity'] = intensityd # set value at 0 index
changed = True
except: # there is no max intensity yet, create
data.at[0, 'intensity'] = intensity # set value at 0 index
changed = True changed = True
print("create max", intensity) print("create max", intensity)
elif intensity > data[0, -1]: # if current intensity value is more (less pupil), save that NOTE: we have the
data[0, -1] = intensity # set value at 0 index
changed = True
print("new max", intensity)
else:
intensityd = max(data[0, -1] - 10, 1) #continuously adjust closed intensity, will be set when user blink, used to allow eyes to close when lighting changes
data[0, -1] = intensityd # set value at 0 index
changed = True
try: if newval_flg:
maxp = data.at[dfb, 'intensity'] # Do the same thing as in the original version.
minp = data.at[0, 'intensity']
#eyeopen = (intensity - minp) / (maxp - minp)
eyeopen = (intensity - maxp) / (minp - maxp)
eyeopen = 1 - eyeopen
print(intensity, maxp, minp, x, y)
# print(f"EYEOPEN: {eyeopen}")
except:
print('[INFO] Something went wrong, assuming blink.') print('[INFO] Something went wrong, assuming blink.')
eyeopen = 0.0 eyeopen = 0.7
else:
maxp = data[int_y, int_x]
minp = data[0, -1]
diffp = minp - maxp if (minp - maxp) != 0 else 1
eyeopen = (intensity - maxp) / diffp
eyeopen = 1 - eyeopen
#eyeopen = eyeopen - 0.2
# print(intensity, maxp, minp, x, y)
# print(f"EYEOPEN: {eyeopen}")
# print(int(x), int(y), eyeopen, maxp, minp)
if changed == True: if changed and ((time.time() - lct) > 4): # save every 4 seconds if something changed to save disk usage
data.to_csv(fname, encoding='utf-8', index=False) #save file since we made a change cv2.imwrite(fname, u32_u16_1ch3ch(data))
lct = time.time()
print("SAVED")
return eyeopen return eyeopen

View File

@ -126,11 +126,9 @@ class VRChatOSC:
if eye_id in [EyeId.RIGHT]: if eye_id in [EyeId.RIGHT]:
rb = True rb = True
if last_blink > 0.7:
for i in range(5):
self.client.send_message("/avatar/parameters/RightEyeLid", float(1)) self.client.send_message("/avatar/parameters/RightEyeLid", float(1))
self.client.send_message("/avatar/parameters/RightEyeLidExpandedSqueeze", float(eye_info.blink)) # close eye self.client.send_message("/avatar/parameters/RightEyeLidExpandedSqueeze", float(eye_info.blink)) # close eye
last_blink = time.time() - last_blink
else: else:
if eye_id in [EyeId.LEFT]: if eye_id in [EyeId.LEFT]:
@ -146,13 +144,10 @@ class VRChatOSC:
self.client.send_message("/avatar/parameters/LeftEyeLid", float(0))# old param open left self.client.send_message("/avatar/parameters/LeftEyeLid", float(0))# old param open left
self.client.send_message("/avatar/parameters/LeftEyeLidExpandedSqueeze", float(eye_info.blink)) # open left eye self.client.send_message("/avatar/parameters/LeftEyeLidExpandedSqueeze", float(eye_info.blink)) # open left eye
if rb and lb: # If both eyes are closed, blink if rb and lb: # If both eyes are closed, blink
if last_blink > 0.5:
for i in range(4):
self.client.send_message("/avatar/parameters/RightEyeLid", float(1)) #close eye self.client.send_message("/avatar/parameters/RightEyeLid", float(1)) #close eye
self.client.send_message("/avatar/parameters/LeftEyeLid", float(1)) self.client.send_message("/avatar/parameters/LeftEyeLid", float(1))
self.client.send_message("/avatar/parameters/RightEyeLidExpandedSqueeze", float(eye_info.blink)) # close eye self.client.send_message("/avatar/parameters/RightEyeLidExpandedSqueeze", float(eye_info.blink)) # close eye
self.client.send_message("/avatar/parameters/LeftEyeLidExpandedSqueeze", float(eye_info.blink)) self.client.send_message("/avatar/parameters/LeftEyeLidExpandedSqueeze", float(eye_info.blink))
last_blink = time.time() - last_blink
class VRChatOSCReceiver: class VRChatOSCReceiver:

View File

@ -19,7 +19,7 @@
@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@( @@@@@@@@@@@@@(
RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), Sean.Denka (Optimization) RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), PallasNeko (Optimization)
Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator) Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator)
Copyright (c) 2022 EyeTrackVR <3 Copyright (c) 2022 EyeTrackVR <3

View File

@ -1,308 +0,0 @@
xy,intensity
473582.0,357639.0
473583.0,250971.0
463582.0,259772.0
463581.0,250001.0
463481.0,262307.0
473482.0,259063.0
503485.0,253050.0
513586.0,242916.0
523486.0,245595.0
523587.0,239752.0
523588.0,235965.0
533589.0,234207.0
553792.0,227890.0
573997.0,219112.0
594099.0,211998.0
6040100.0,97336.0
6040101.0,204647.0
6041101.0,206041.0
553692.0,222899.0
533689.0,234080.0
523689.0,235235.0
533588.0,234591.0
513587.0,240166.0
503586.0,242147.0
483584.0,248543.0
453681.0,252077.0
453581.0,252706.0
453580.0,238681.0
443580.0,251563.0
0.0,-282.0
483583.0,246862.0
513687.0,239926.0
513486.0,244214.0
543691.0,229066.0
553691.0,224848.0
563793.0,222710.0
563693.0,221737.0
563692.0,221532.0
543589.0,233991.0
523688.0,237634.0
513688.0,240610.0
503686.0,241587.0
493686.0,243116.0
483685.0,241468.0
483785.0,242252.0
473784.0,243209.0
463784.0,244225.0
453884.0,245899.0
453883.0,245353.0
443883.0,244192.0
443882.0,245493.0
433982.0,246043.0
433882.0,246397.0
423982.0,247620.0
423981.0,248308.0
433881.0,250237.0
443782.0,255290.0
453782.0,251893.0
463783.0,247845.0
443781.0,256185.0
453783.0,252276.0
463682.0,257657.0
473683.0,248249.0
473684.0,254666.0
483684.0,253290.0
493685.0,253745.0
553590.0,160147.0
563591.0,228955.0
573592.0,227065.0
583594.0,229045.0
573593.0,229591.0
563592.0,230609.0
553490.0,235088.0
543489.0,235599.0
503585.0,235551.0
493585.0,252256.0
463683.0,249050.0
453682.0,256510.0
433780.0,257233.0
423780.0,257321.0
433781.0,254818.0
423881.0,255737.0
463884.0,245749.0
473785.0,242568.0
483786.0,249030.0
493786.0,247564.0
503787.0,244608.0
543690.0,234709.0
543590.0,233992.0
553591.0,232013.0
583593.0,228357.0
583493.0,226658.0
593594.0,228134.0
583492.0,227921.0
573391.0,231334.0
573390.0,231444.0
563390.0,234623.0
563389.0,234593.0
553388.0,237340.0
543387.0,235194.0
533386.0,239102.0
523386.0,241225.0
523385.0,241552.0
513384.0,250384.0
503383.0,253299.0
493383.0,254572.0
483382.0,258460.0
473481.0,259955.0
463480.0,258556.0
453480.0,259863.0
443680.0,261992.0
433579.0,261555.0
433679.0,259909.0
433680.0,256797.0
423880.0,258517.0
424082.0,253054.0
434083.0,250342.0
434084.0,248101.0
444084.0,246720.0
444085.0,250595.0
444488.0,265335.0
464995.0,289119.0
4954104.0,295429.0
4953103.0,294742.0
454893.0,283001.0
464591.0,271508.0
474491.0,264746.0
484290.0,258544.0
484190.0,255352.0
484089.0,251344.0
494089.0,245657.0
493989.0,232298.0
503990.0,230283.0
513890.0,238264.0
513990.0,226967.0
523890.0,235722.0
523891.0,234865.0
533891.0,233355.0
533791.0,232810.0
543791.0,232428.0
523790.0,237078.0
513789.0,242689.0
513788.0,242871.0
533488.0,237010.0
533487.0,238427.0
533387.0,237412.0
513385.0,244320.0
503384.0,248585.0
493382.0,255212.0
483381.0,254841.0
473381.0,258108.0
473380.0,257087.0
453479.0,260130.0
443579.0,261332.0
473885.0,242285.0
483886.0,240662.0
483887.0,238495.0
493887.0,236504.0
493988.0,234020.0
503989.0,232407.0
513991.0,225947.0
523991.0,224778.0
504090.0,229604.0
494090.0,233456.0
483988.0,236861.0
484088.0,237088.0
483987.0,239272.0
473987.0,238618.0
473986.0,240348.0
473886.0,241339.0
493484.0,249358.0
503484.0,247653.0
543388.0,233512.0
523284.0,256409.0
543084.0,269285.0
542983.0,280529.0
542883.0,291685.0
9738135.0,213939.0
9638134.0,219657.0
9738136.0,213451.0
9839137.0,204007.0
9839138.0,204217.0
9440134.0,228059.0
9241134.0,240587.0
9141133.0,246331.0
9539134.0,224047.0
9739136.0,211537.0
533084.0,279261.0
533184.0,262679.0
523285.0,255018.0
532983.0,274461.0
532781.0,302302.0
9637133.0,222471.0
9537132.0,229626.0
9737135.0,215710.0
9637134.0,221660.0
9537133.0,229847.0
9737134.0,215498.0
523184.0,257570.0
494291.0,298100.0
5250102.0,307350.0
514192.0,302543.0
523487.0,269115.0
493483.0,259705.0
483482.0,264756.0
483483.0,263669.0
542782.0,288667.0
543185.0,253212.0
533185.0,257473.0
533285.0,252283.0
513284.0,256989.0
8552138.0,258478.0
5151103.0,312095.0
514799.0,313296.0
544296.0,302653.0
533892.0,289151.0
543488.0,256997.0
514395.0,299664.0
5351104.0,304022.0
485098.0,307963.0
514294.0,304829.0
503687.0,251585.0
503788.0,248328.0
493888.0,246838.0
464086.0,252127.0
454085.0,239326.0
443984.0,255446.0
433983.0,256770.0
443681.0,264741.0
543286.0,253677.0
543186.0,251910.0
513283.0,258668.0
503283.0,259877.0
493282.0,264590.0
483281.0,266513.0
463380.0,269924.0
483280.0,270615.0
493079.0,271830.0
492979.0,271781.0
492978.0,272371.0
493080.0,271169.0
513082.0,261750.0
513183.0,260039.0
523083.0,271107.0
532881.0,278287.0
9635131.0,228473.0
9535130.0,233599.0
9535131.0,233232.0
9436130.0,236829.0
9736134.0,218341.0
8850138.0,238086.0
5249101.0,310885.0
494695.0,315451.0
533286.0,252608.0
504394.0,302886.0
504293.0,306282.0
522982.0,272159.0
9635132.0,229019.0
9736133.0,218560.0
523082.0,298688.0
493181.0,280463.0
504696.0,307991.0
9149141.0,221327.0
504091.0,304619.0
493787.0,253151.0
513182.0,261244.0
513081.0,263879.0
503181.0,264893.0
493584.0,256296.0
503182.0,264151.0
6050110.0,143710.0
554095.0,79411.0
10050150.0,83752.0
6045105.0,146315.0
6540105.0,48067.0
6535100.0,137496.0
653095.0,86445.0
7535110.0,153158.0
4040.0,-6.0
7030100.0,163605.0
652085.0,0.0
65065.0,-15.0
6565130.0,235622.0
6555120.0,292006.0
6560125.0,234630.0
5560115.0,372229.0
5055105.0,272624.0
5550105.0,241914.0
5050100.0,318630.0
5555110.0,252782.0
6055115.0,217787.0
5545100.0,225673.0
504595.0,236617.0
405090.0,262150.0
455095.0,272500.0
454590.0,267193.0
405595.0,267416.0
404080.0,215254.0
4060100.0,273388.0
603595.0,269172.0
404585.0,257809.0
403575.0,253865.0
403070.0,264744.0
453075.0,265099.0
503080.0,261448.0
6550115.0,158537.0
4065105.0,67964.0

View File

View File

@ -0,0 +1,12 @@
import cv2
def safe_crop(img, x, y, x2, y2, keepsize=True):
# The order of the arguments can be reconsidered.
img_h, img_w = img.shape[:2]
outimg = img[max(0, y) : min(img_h, y2), max(0, x) : min(img_w, x2)].copy()
reqsize_x, reqsize_y = abs(x2 - x), abs(y2 - y)
if keepsize and outimg.shape[:2] != (reqsize_y, reqsize_x):
# If the size is different from the expected size (smaller by the amount that is out of range)
outimg = cv2.resize(outimg, (reqsize_x, reqsize_y))
return outimg

View File

@ -0,0 +1,2 @@
def clamp(x, low, high):
return max(low, min(x, high))

View File

@ -0,0 +1,171 @@
import functools
import math
import sys
import timeit
def TimeitWrapper(*args, **kwargs):
"""
This decorator @TimeitWrapper() prints the function name and execution time in seconds.
:param args:
:param kwargs:
:return:
"""
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
start = timeit.default_timer()
results = function(*args, **kwargs)
end = timeit.default_timer()
print('{} execution time: {:.10f} s'.format(function.__name__, end - start))
return results
return wrapper
return decorator
class TimeitResult(object):
"""
from https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
Object returned by the timeit magic with info about the run.
Contains the following attributes :
loops: (int) number of loops done per measurement
repeat: (int) number of times the measurement has been repeated
best: (float) best execution time / number
all_runs: (list of float) execution time of each run (in s)
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = best
self.worst = worst
self.all_runs = all_runs
self._precision = precision
self.timings = [dt / self.loops for dt in all_runs]
@property
def average(self):
return math.fsum(self.timings) / len(self.timings)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean=format_time(self.average, self._precision),
std=format_time(self.stdev, self._precision),
best=format_time(self.best, self._precision),
worst=format_time(self.worst, self._precision),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<TimeitResult : ' + unic + u'>')
class FPSResult(object):
"""
base https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = 1 / best
self.worst = 1 / worst
self.all_runs = all_runs
self._precision = precision
self.fps = [1 / dt for dt in all_runs]
self.unit = "fps"
@property
def average(self):
return math.fsum(self.fps) / len(self.fps)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.fps]) / len(self.fps)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean="%.*g%s" % (self._precision, self.average, self.unit),
std="%.*g%s" % (self._precision, self.stdev, self.unit),
best="%.*g%s" % (self._precision, self.best, self.unit),
worst="%.*g%s" % (self._precision, self.worst, self.unit),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<FPSResult : ' + unic + u'>')
def format_time(timespan, precision=3):
"""
https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L1473
Formats the timespan in a human readable form
"""
if timespan >= 60.0:
# we have more than a minute, format that in a human readable form
# Idea from http://snipplr.com/view/5713/
parts = [("d", 60 * 60 * 24), ("h", 60 * 60), ("min", 60), ("s", 1)]
time = []
leftover = timespan
for suffix, length in parts:
value = int(leftover / length)
if value > 0:
leftover = leftover % length
time.append(u'%s%s' % (str(value), suffix))
if leftover < 1:
break
return " ".join(time)
# Unfortunately the unicode 'micro' symbol can cause problems in
# certain terminals.
# See bug: https://bugs.launchpad.net/ipython/+bug/348466
# Try to prevent crashes by being more secure than it needs to
# E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
units = [u"s", u"ms", u'us', "ns"] # the save value
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb5'.encode(sys.stdout.encoding)
units = [u"s", u"ms", u'\xb5s', "ns"]
except:
pass
scaling = [1, 1e3, 1e6, 1e9]
if timespan > 0.0:
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
else:
order = 3
return u"%.*g %s" % (precision, timespan * scaling[order], units[order])

12
poetry.lock generated
View File

@ -12,18 +12,6 @@ files = [
{file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"}, {file = "altgraph-0.17.3.tar.gz", hash = "sha256:ad33358114df7c9416cdb8fa1eaa5852166c505118717021c6a8c7c7abbd03dd"},
] ]
[[package]]
name = "beautifulsoup4"
version = "4.11.1"
description = "Screen-scraping library"
category = "main"
optional = false
python-versions = ">=3.6.0"
files = [
{file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"},
{file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"},
]
[package.dependencies] [package.dependencies]
soupsieve = ">1.2" soupsieve = ">1.2"

View File

@ -18,7 +18,6 @@ pydantic = "^1.10.2"
win10toast_click = [ win10toast_click = [
{ version = "^0.1.2", platform = 'win32' } { version = "^0.1.2", platform = 'win32' }
] ]
beautifulsoup4 = "^4.11.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^22.10.0" black = "^22.10.0"