This commit is contained in:
Fynnley 2025-04-03 11:53:45 -07:00 committed by GitHub
commit 516ee79faa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1506 additions and 1383 deletions

View File

@ -36,7 +36,9 @@ from config import EyeTrackCameraConfig
from enum import Enum from enum import Enum
import psutil, os import psutil, os
import sys import sys
from ctypes import windll
import win32gui
import win32ui
process = psutil.Process(os.getpid()) # set process priority to low process = psutil.Process(os.getpid()) # set process priority to low
try: try:
@ -74,6 +76,14 @@ def is_serial_capture_source(addr: str) -> bool:
addr.startswith("COM") or addr.startswith("/dev/cu") or addr.startswith("/dev/tty") # Windows # macOS # Linux addr.startswith("COM") or addr.startswith("/dev/cu") or addr.startswith("/dev/tty") # Windows # macOS # Linux
) )
def is_aseevr_capture_source(addr: str) -> bool:
"""
Returns True if the capture source address is ASeeVR/Droolon Pi 1
"""
if addr == "aseevrleft" or addr == "aseevrright":
return(True)
else:
return(False)
class Camera: class Camera:
def __init__( def __init__(
@ -98,6 +108,7 @@ class Camera:
self.cv2_camera: "cv2.VideoCapture" = None self.cv2_camera: "cv2.VideoCapture" = None
self.serial_connection = None self.serial_connection = None
self.aseevr_camera = None
self.last_frame_time = time.time() self.last_frame_time = time.time()
self.frame_number = 0 self.frame_number = 0
self.fps = 0 self.fps = 0
@ -137,6 +148,9 @@ class Camera:
if is_serial_capture_source(addr): if is_serial_capture_source(addr):
pass # TODO: find a nicer way to stop the com port pass # TODO: find a nicer way to stop the com port
# self.serial_connection.close() # self.serial_connection.close()
elif is_aseevr_capture_source(addr):
# We don't need any special release for ASeeVR, each capture event is fully closed within itself
pass
else: else:
self.cv2_camera.release() self.cv2_camera.release()
@ -156,6 +170,24 @@ class Camera:
port = self.config.capture_source port = self.config.capture_source
self.current_capture_source = port self.current_capture_source = port
self.start_serial_connection(port) self.start_serial_connection(port)
elif is_aseevr_capture_source(addr):
if (
self.aseevr_camera is None
or self.camera_status == CameraState.DISCONNECTED
or self.config.capture_source != self.current_capture_source
):
self.current_capture_source = self.config.capture_source
# Determine if we want the left or the right video feed and
# set the pimax_camera variable to the window title of that eye
if ( self.current_capture_source == "aseevrleft"):
self.aseevr_camera = "draw Image1"
elif (self.current_capture_source == "aseevrright"):
self.aseevr_camera = "draw Image2"
else:
# This should be a completely impossible scenario, but... I guess I need to do something here
print("There is only aseevrleft and aseevrright.")
should_push = False
else: else:
if ( if (
self.cv2_camera is None self.cv2_camera is None
@ -190,6 +222,8 @@ class Camera:
addr = str(self.current_capture_source) addr = str(self.current_capture_source)
if is_serial_capture_source(addr): if is_serial_capture_source(addr):
self.get_serial_camera_picture(should_push) self.get_serial_camera_picture(should_push)
elif is_aseevr_capture_source(addr):
self.get_aseevr_camera_picture(should_push)
else: else:
self.get_cv2_camera_picture(should_push) self.get_cv2_camera_picture(should_push)
if not should_push: if not should_push:
@ -237,6 +271,66 @@ class Camera:
self.camera_status = CameraState.DISCONNECTED self.camera_status = CameraState.DISCONNECTED
pass pass
def get_aseevr_camera_picture(self, should_push):
try:
# We probably need to be DPI aware to capture the window correctly for
# users that have window scaling set to something else than 100%
windll.user32.SetProcessDPIAware()
# Find the right window and then capture it to a bitmap using win32gui
hwnd = win32gui.FindWindow(None, self.aseevr_camera)
hwnd_dc = win32gui.GetWindowDC(hwnd)
mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
save_dc = mfc_dc.CreateCompatibleDC()
bitmap = win32ui.CreateBitmap()
bitmap.CreateCompatibleBitmap(mfc_dc, 320, 240)
save_dc.SelectObject(bitmap)
result = windll.user32.PrintWindow(hwnd, save_dc.GetSafeHdc(), 3)
# Convert the created bitmap into a format that ETVR likes
bmpinfo = bitmap.GetInfo()
bmpstr = bitmap.GetBitmapBits(True)
image = np.frombuffer(bmpstr, dtype=np.uint8).reshape((bmpinfo["bmHeight"], bmpinfo["bmWidth"], 4))
image = np.ascontiguousarray(image)[..., :-1]
# Clean up after writing the image
win32gui.DeleteObject(bitmap.GetHandle())
save_dc.DeleteDC()
mfc_dc.DeleteDC()
win32gui.ReleaseDC(hwnd, hwnd_dc)
# Calculate the aspect ratio
height, width = image.shape[:2]
# Calculate the fps.
current_frame_time = time.time()
delta_time = current_frame_time - self.last_frame_time
self.last_frame_time = current_frame_time
if delta_time > 0:
self.bps = len(image) / delta_time
self.frame_number = self.frame_number + 1
self.fps = (self.fps + self.pf_fps) / 2
self.newft = time.time()
self.fps = 1 / (self.newft - self.prevft)
self.prevft = self.newft
self.fps = int(self.fps)
if len(self.fl) < 60:
self.fl.append(self.fps)
else:
self.fl.pop(0)
self.fl.append(self.fps)
self.fps = sum(self.fl) / len(self.fl)
# self.bps = image.nbytes
frame_number = self.frame_number
if should_push:
self.push_image_to_queue(image, frame_number, self.fps)
except:
print(
f"{Fore.YELLOW}[WARN] Capture source problem, assuming camera disconnected, waiting for reconnect.{Fore.RESET}"
)
self.camera_status = CameraState.DISCONNECTED
pass
def get_next_packet_bounds(self): def get_next_packet_bounds(self):
beg = -1 beg = -1
while beg == -1: while beg == -1:

View File

@ -388,6 +388,7 @@ class CameraWidget:
if ( if (
len(values[self.gui_camera_addr]) > 5 len(values[self.gui_camera_addr]) > 5
and "http" not in values[self.gui_camera_addr] and "http" not in values[self.gui_camera_addr]
and "aseevr" not in values[self.gui_camera_addr]
and ".mp4" not in values[self.gui_camera_addr] and ".mp4" not in values[self.gui_camera_addr]
and "/dev" not in values[self.gui_camera_addr] and "/dev" not in values[self.gui_camera_addr]
): # If http is not in camera address, add it. ): # If http is not in camera address, add it.

View File

@ -72,7 +72,7 @@ class EyeTrackCameraConfig(BaseModel):
# we were passed an IP, probably, lets add HTTP:// to it # we were passed an IP, probably, lets add HTTP:// to it
if len(new_camera_address) > 5 and not ( if len(new_camera_address) > 5 and not (
not new_camera_address.startswith(("http", "/dev")) or not new_camera_address.endswith(".mp4") not new_camera_address.startswith(("http", "/dev", "aseevr")) or not new_camera_address.endswith(".mp4")
): ):
self.capture_source = f"http://{new_camera_address}" self.capture_source = f"http://{new_camera_address}"
return return

View File

@ -11,7 +11,7 @@ a = Analysis(
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=resources, datas=resources,
hiddenimports=['cv2', 'numpy', 'PySimpleGui', 'pkg_resources.extern'], hiddenimports=['cv2', 'numpy', 'PySimpleGui', 'pkg_resources.extern', 'pywin32'],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
@ -44,4 +44,4 @@ exe = EXE(
codesign_identity=None, codesign_identity=None,
entitlements_file=None, entitlements_file=None,
icon="Images/logo.ico", icon="Images/logo.ico",
) )

2785
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ colorama = "^0.4.6"
taskipy = "^1.10.4" taskipy = "^1.10.4"
pytest = "^8.0.0" pytest = "^8.0.0"
pytest-cov = "^4.1.0" pytest-cov = "^4.1.0"
pywin32 = "^308"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^22.10.0" black = "^22.10.0"