Skip to content

Commit

Permalink
Feat: New FPS/BPS calc: Handle zero division 'n use exponential movin…
Browse files Browse the repository at this point in the history
…g average.

Port over macOS "baudrate" from ETVR.
  • Loading branch information
SnuffSocket committed Oct 22, 2024
1 parent c61c316 commit 5c4e47b
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 79 deletions.
115 changes: 44 additions & 71 deletions BabbleApp/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
from config import BabbleConfig, BabbleSettingsConfig
from utils.misc_utils import get_camera_index_by_name, list_camera_names
from enum import Enum
import sys

WAIT_TIME = 0.1
BUFFER_SIZE = 32768
MAX_RESOLUTION: int = 600

# Serial communication protocol:
# header-begin (2 bytes)
# header-type (2 bytes)
# packet-size (2 bytes)
# packet (packet-size bytes)
ETVR_HEADER = b"\xff\xa0"
ETVR_HEADER_FRAME = b"\xff\xa1"
# header-begin (2 bytes) "\xff\xa0"
# header-type (2 bytes) "\xff\xa1"
# packet-size (2 bytes)
# packet (packet-size bytes)
ETVR_HEADER = b"\xff\xa0\xff\xa1"
ETVR_HEADER_LEN = 6


Expand All @@ -48,8 +48,6 @@ def __init__(
self.settings = settings
self.camera_index = camera_index
self.camera_list = list_camera_names()
# The variable is not used
# self.camera_address = config.capture_source
self.camera_status_outgoing = camera_status_outgoing
self.camera_output_outgoing = camera_output_outgoing
self.capture_event = capture_event
Expand All @@ -59,15 +57,10 @@ def __init__(

self.serial_connection = None
self.last_frame_time = time.time()
self.frame_number = 0
self.fps = 0
self.bps = 0
self.start = True
self.buffer = b""
self.pf_fps = 0
self.prevft = 0
self.newft = 0
self.fl = [0]
self.FRAME_SIZE = [0, 0]

self.error_message = f'{Fore.YELLOW}[{lang._instance.get_string("log.warn")}] {lang._instance.get_string("info.enterCaptureOne")} {{}} {lang._instance.get_string("info.enterCaptureTwo")}{Fore.RESET}'
Expand Down Expand Up @@ -175,28 +168,18 @@ def get_cv2_camera_picture(self, should_push):
raise RuntimeError(lang._instance.get_string("error.frame"))
self.FRAME_SIZE = image.shape
frame_number = self.cv2_camera.get(cv2.CAP_PROP_POS_FRAMES)
# Calculate the fps.
yeah = time.time()
delta_time = yeah - self.last_frame_time
self.last_frame_time = yeah
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
# Calculate FPS
current_frame_time = time.time() # Should be using "time.perf_counter()", not worth ~3x cycles?
delta_time = current_frame_time - self.last_frame_time
self.last_frame_time = current_frame_time
current_fps = 1 / delta_time if delta_time > 0 else 0
# Exponential moving average (EMA). ~1100ns savings, delicious..
self.fps = 0.02 * current_fps + 0.98 * self.fps
self.bps = image.nbytes * self.fps

if should_push:
self.push_image_to_queue(image, frame_number, self.fps)
except Exception as e:
self.push_image_to_queue(image, frame_number + 1, self.fps)
except Exception:
print(
f'{Fore.YELLOW}[{lang._instance.get_string("log.warn")}] {lang._instance.get_string("warn.captureProblem")}{Fore.RESET}'
)
Expand All @@ -207,7 +190,7 @@ def get_next_packet_bounds(self):
beg = -1
while beg == -1:
self.buffer += self.serial_connection.read(2048)
beg = self.buffer.find(ETVR_HEADER + ETVR_HEADER_FRAME)
beg = self.buffer.find(ETVR_HEADER)
# Discard any data before the frame header.
if beg > 0:
self.buffer = self.buffer[beg:]
Expand All @@ -224,11 +207,11 @@ def get_next_jpeg_frame(self):
return jpeg

def get_serial_camera_picture(self, should_push):
conn = self.serial_connection
if conn is None:
# Stop spamming "Serial capture source problem" if connection is lost
if self.serial_connection is None or self.camera_status == CameraState.DISCONNECTED:
return
try:
if conn.in_waiting:
if self.serial_connection.in_waiting:
jpeg = self.get_next_jpeg_frame()
if jpeg:
# Create jpeg frame from byte string
Expand All @@ -240,39 +223,30 @@ def get_serial_camera_picture(self, should_push):
f'{Fore.YELLOW}[{lang._instance.get_string("log.warn")}] {lang._instance.get_string("warn.frameDrop")}{Fore.RESET}'
)
return
# Discard the serial buffer. This is due to the fact that it
# may build up some outdated frames. A bit of a workaround here tbh.
if conn.in_waiting >= 32768:
print(
f'{Fore.CYAN}[{lang._instance.get_string("log.info")}] {lang._instance.get_string("info.discardingSerial")} ({conn.in_waiting} bytes){Fore.RESET}'
)
conn.reset_input_buffer()
self.buffer = b""
# Calculate the fps.
yeah = time.time()
delta_time = yeah - self.last_frame_time
self.last_frame_time = yeah
if delta_time > 0:
self.bps = len(jpeg) / delta_time
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.frame_number = self.frame_number + 1
# Calculate FPS
current_frame_time = time.time() # Should be using "time.perf_counter()", not worth ~3x cycles?
delta_time = current_frame_time - self.last_frame_time
self.last_frame_time = current_frame_time
current_fps = 1 / delta_time if delta_time > 0 else 0
# Exponential moving average (EMA). ~1100ns savings, delicious..
self.fps = 0.02 * current_fps + 0.98 * self.fps
self.bps = len(jpeg) * self.fps

if should_push:
self.push_image_to_queue(image, self.frame_number, self.fps)
self.push_image_to_queue(image, int(current_fps), self.fps)
# Discard the serial buffer. This is due to the fact that it,
# may build up some outdated frames. A bit of a workaround here tbh.
# Do this at the end to give buffer time to refill.
if self.serial_connection.in_waiting >= BUFFER_SIZE:
print(f'{Fore.CYAN}[{lang._instance.get_string("log.info")}] {lang._instance.get_string("info.discardingSerial")} ({self.serial_connection.in_waiting} bytes){Fore.RESET}')
self.serial_connection.reset_input_buffer()
self.buffer = b""

except Exception:
print(
f'{Fore.YELLOW}[{lang._instance.get_string("log.warn")}] {lang._instance.get_string("info.serialCapture")}{Fore.RESET}'
)
conn.close()
self.serial_connection.close()
self.camera_status = CameraState.DISCONNECTED
pass

Expand All @@ -288,11 +262,10 @@ def start_serial_connection(self, port):
if not any(p for p in com_ports if port in p):
return
try:
conn = serial.Serial(
baudrate=3000000, port=port, xonxoff=False, dsrdtr=False, rtscts=False
)
rate = 115200 if sys.platform == "darwin" else 3000000 # Higher baud rate not working on macOS
conn = serial.Serial(baudrate=rate, port=port, xonxoff=False, dsrdtr=False, rtscts=False)
# Set explicit buffer size for serial.
conn.set_buffer_size(rx_size=32768, tx_size=32768)
conn.set_buffer_size(rx_size=BUFFER_SIZE, tx_size=BUFFER_SIZE)

print(
f'{Fore.CYAN}[{lang._instance.get_string("log.info")}] {lang._instance.get_string("info.ETVRConnected")} {port}{Fore.RESET}'
Expand Down
13 changes: 5 additions & 8 deletions BabbleApp/camera_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,18 +262,15 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
self.figure = None
self.is_mouse_up = True
self.in_roi_mode = False
self.movavg_fps_queue = deque(maxlen=120)
self.movavg_bps_queue = deque(maxlen=120)
self.bps = 0

def _movavg_fps(self, next_fps):
self.movavg_fps_queue.append(next_fps)
fps = round(sum(self.movavg_fps_queue) / len(self.movavg_fps_queue))
millisec = round((1 / fps if fps else 0) * 1000)
return f"{fps} FPS {millisec} MS"
# next_fps is already averaged
return f"{round(next_fps)} FPS {round((1 / next_fps if next_fps else 0) * 1000)} ms"

def _movavg_bps(self, next_bps):
self.movavg_bps_queue.append(next_bps)
return f"{sum(self.movavg_bps_queue) / len(self.movavg_bps_queue) * 0.001 * 0.001 * 8:.3f} Mbps"
self.bps = round(0.02 * next_bps + 0.98 * self.bps)
return f"{self.bps * 0.001 * 0.001 * 8:.3f} Mbps"

def started(self):
return not self.cancellation_event.is_set()
Expand Down

0 comments on commit 5c4e47b

Please sign in to comment.