mirror of
https://github.com/EyeTrackVR/EyeTrackVR.git
synced 2025-11-04 14:39:42 +08:00
Merge dd97a467ba into b001b15ed8
This commit is contained in:
commit
516ee79faa
@ -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:
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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=[],
|
||||||
|
|||||||
31
poetry.lock
generated
31
poetry.lock
generated
@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "altgraph"
|
name = "altgraph"
|
||||||
@ -1132,6 +1132,33 @@ files = [
|
|||||||
{file = "python_osc-1.8.3-py3-none-any.whl", hash = "sha256:6fa7e5cf7690057712c26e5f67a747126e6a5f481a40792aad0f25ac3edee815"},
|
{file = "python_osc-1.8.3-py3-none-any.whl", hash = "sha256:6fa7e5cf7690057712c26e5f67a747126e6a5f481a40792aad0f25ac3edee815"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pywin32"
|
||||||
|
version = "308"
|
||||||
|
description = "Python for Window Extensions"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"},
|
||||||
|
{file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"},
|
||||||
|
{file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"},
|
||||||
|
{file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"},
|
||||||
|
{file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"},
|
||||||
|
{file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"},
|
||||||
|
{file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"},
|
||||||
|
{file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"},
|
||||||
|
{file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"},
|
||||||
|
{file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"},
|
||||||
|
{file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"},
|
||||||
|
{file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"},
|
||||||
|
{file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"},
|
||||||
|
{file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"},
|
||||||
|
{file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"},
|
||||||
|
{file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"},
|
||||||
|
{file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"},
|
||||||
|
{file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pywin32-ctypes"
|
name = "pywin32-ctypes"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -1376,4 +1403,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "~3.11.0"
|
python-versions = "~3.11.0"
|
||||||
content-hash = "a5a2cd400fbd53843fa8e6ab36a824d78db95d6c3a2a893bfd977ed8a040a013"
|
content-hash = "900227c49cb6159dea132a9419222e1eb7202576533a56b98db12d2637b95629"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user