Skip to content

Commit

Permalink
Clean up state and camera thread; does not block napari
Browse files Browse the repository at this point in the history
  • Loading branch information
kephale committed Sep 28, 2023
1 parent b2611cc commit cda30e4
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 71 deletions.
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = napari-conference
version = 0.0.1
version = 0.1.0
description = A simple plugin that allows you to use napari + your webcam in video calls
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
133 changes: 63 additions & 70 deletions src/napari_conference/_widget.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
"""
This module is an example of a barebones QWidget plugin for napari
It implements the Widget specification.
see: https://napari.org/stable/plugins/guides.html?#widgets
Replace code below according to your needs.
"""
import cv2
import napari
import numpy as np
import pyvirtualcam
from magicgui import magic_factory
from napari.qt.threading import thread_worker

global capturing
capturing = False
import time
import logging

# Configure logging
logging.basicConfig(level=logging.WARNING)
LOGGER = logging.getLogger(__name__)

global cam
cam = None

global update_mode
update_mode = {"filter": "None"}
# Define a class to hold the state
class WebcamState:
def __init__(self):
self.capturing = False
self.cam = None
self.update_mode = {"filter": "None"}
self.trail_param = 0.1

global trail_param
trail_param = 0.1

group_name = "webcam"
# Create an instance of the state
state = WebcamState()


def gameboy_filter(image):
Expand Down Expand Up @@ -67,85 +65,88 @@ def gameboy_filter(image):


def make_layer(layer_name="Conference", viewer=None):
# Make the virtual camera
# cam = pyvirtualcam.Camera(width=2908, height=2032, fps=20)
# cam = pyvirtualcam.Camera(width=960, height=540, fps=20)
global frame_count, out_zarr

out_zarr = None

frame_count = 0
max_frames = 1000
global state
LOGGER.info("Entering make_layer function")

# This function is called on every frame of the real webcam
def update_layer(new_frame):
global cam

global state
try:
viewer.layers[layer_name].data = new_frame
except KeyError:
LOGGER.info("Adding new layer")
viewer.add_image(new_frame, name=layer_name)

screen = viewer.screenshot(flash=False, canvas_only=False)
if cam is None:
cam = pyvirtualcam.Camera(

# Close and reopen the camera with the new frame size if it has changed
if state.cam is not None and (screen.shape[1], screen.shape[0]) != (
state.cam.width,
state.cam.height,
):
state.cam.close()
state.cam = None

if state.cam is None:
LOGGER.info("Initializing virtual camera")
state.cam = pyvirtualcam.Camera(
width=screen.shape[1], height=screen.shape[0], fps=20
)

# Send to virtual camera
# cam.send(screen)
cam.send(screen[:, :, :3])
state.cam.send(screen[:, :, :3])
state.cam.sleep_until_next_frame()

cam.sleep_until_next_frame()

@thread_worker(connect={"yielded": update_layer})
@thread_worker
def frame_updater():
global update_mode, capturing, cam, trail_param
global frame_count, out_zarr
global state
LOGGER.info("Opening webcam")
cap = cv2.VideoCapture(0)

# Check if the webcam is opened correctly
if not cap.isOpened():
LOGGER.error("Cannot open webcam")
raise IOError("Cannot open webcam")

prev_frame = None

while capturing:
LOGGER.info("Starting frame capturing loop")
while state.capturing:
# LOGGER.info("Reading frame from webcam")
ret, frame = cap.read()
if not ret:
LOGGER.error("Failed to read frame from webcam")
continue

frame = cv2.resize(
frame, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA
)
frame = frame[:, :, ::-1]

np_frame = np.array(frame)

if prev_frame is None:
prev_frame = np_frame

# Apply a filter
if update_mode["filter"] == "Blur":
if state.update_mode["filter"] == "Blur":
frame = cv2.blur(frame, (5, 5))
elif update_mode["filter"] == "Laplacian":
elif state.update_mode["filter"] == "Laplacian":
frame = cv2.Laplacian(frame, cv2.CV_8U)
elif update_mode["filter"] == "Gameboy":
elif state.update_mode["filter"] == "Gameboy":
frame = gameboy_filter(frame)

# This adds trails if trail param is 1, then this will never update
frame = np.array(
prev_frame * trail_param + frame * (1.0 - trail_param),
prev_frame * state.trail_param
+ frame * (1.0 - state.trail_param),
dtype=frame.dtype,
)
prev_frame = np.array(frame)

yield frame

# Sleep for 20FPS
time.sleep(1 / 20)
cap.release()
print("Capture device released.")
LOGGER.info("Capture device released.")

# cam.close()
# print("Virtual camera released.")
LOGGER.info("Starting frame updater worker")
worker = frame_updater()
worker.yielded.connect(update_layer)
worker.start()

return frame_updater()
return worker


@magic_factory(
Expand All @@ -159,14 +160,14 @@ def conference_widget(
running=False,
trails_param=0.1,
):
global update_mode, capturing, cam, trail_param

capturing = running
update_mode["filter"] = dropdown
global state

trail_param = trails_param
state.capturing = running
state.update_mode["filter"] = dropdown
state.trail_param = trails_param

if cam is None:
if state.cam is None and state.capturing:
LOGGER.info("Creating layer")
make_layer(layer_name, viewer=viewer)


Expand All @@ -177,13 +178,5 @@ def conference_widget(
widget = conference_widget()

viewer.window.add_dock_widget(widget, name="Conference")
# widget_demo.show()

worker = make_layer("Kyle", viewer=viewer)

try:
worker.start()
except Exception:
print("barf")

napari.run()

0 comments on commit cda30e4

Please sign in to comment.