Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions labscript_devices/SpinnakerCamera/blacs_tabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,26 @@
#####################################################################

from labscript_devices.IMAQdxCamera.blacs_tabs import IMAQdxCameraTab
from qtpy import QtWidgets, QtGui


class SpinnakerCameraTab(IMAQdxCameraTab):

# override worker class
worker_class = 'labscript_devices.SpinnakerCamera.blacs_workers.SpinnakerCameraWorker'
# def initialise_GUI(self):
# self.image_label = QtWidgets.QLabel(self)
# self.image_label.setFixedSize(640, 480) # adjust to your camera
# self.layout().addWidget(self.image_label)

# def new_image(self, img_array):
# # Convert NumPy array to QImage
# h, w = img_array.shape
# q_img = QtGui.QImage(img_array.data, w, h, w, QtGui.QImage.Format_Grayscale8)
# pixmap = QtGui.QPixmap.fromImage(q_img)
# self.image_label.setPixmap(pixmap)

# def initialise_workers(self):
# # This is called after the worker is initialized
# # Connect the worker signal 'new_image' to the GUI update
# self.camera_worker.register_event('new_image', self.new_image)
288 changes: 266 additions & 22 deletions labscript_devices/SpinnakerCamera/blacs_workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@
from labscript_utils import dedent
from enum import IntEnum
from time import sleep, perf_counter
from blacs.tab_base_classes import Worker
import threading
import time
import PySpin
import imageio
import zmq

from labscript_utils.ls_zprocess import Context
from labscript_utils.shared_drive import path_to_local
from labscript_utils.properties import set_attributes



from labscript_devices.IMAQdxCamera.blacs_workers import IMAQdxCameraWorker

Expand Down Expand Up @@ -251,26 +263,258 @@ def close(self):
self.camList.Clear()
self.system.ReleaseInstance()

class SpinnakerCameraWorker(Worker):
# def __init__(self, *args, **kwargs):
# # self.camera = self.get_camera()
# # print("Setting attributes...")
# # self.smart_cache = {}
# # self.set_attributes_smart(self.camera_attributes)
# # self.set_attributes_smart(self.manual_mode_camera_attributes)
# # print("Initialisation complete")
# # self.images = None
# # self.n_images = None
# # self.attributes_to_save = None
# # self.exposures = None
# # self.acquisition_thread = None
# # self.h5_filepath = None
# # self.stop_acquisition_timeout = None
# # self.exception_on_failed_shot = None
# # self.continuous_stop = threading.Event()
# # self.continuous_thread = None
# # self.continuous_dt = None
# # self.image_socket = Context().socket(zmq.REQ)
# # self.image_socket.connect(
# # f'tcp://{self.parent_host}:{self.image_receiver_port}'
# # )
# super.__init__(*args,)

def init(self):
"""Initialize the camera object"""
system = PySpin.System.GetInstance()
cam_list = system.GetCameras()
if cam_list.GetSize() == 0:
raise RuntimeError("No cameras detected")
self.camera = cam_list[0] # choose the first camera
self.camera.Init()
self.system = system
self.cam_list = cam_list
self.continuous_stop = threading.Event()
self.continuous_thread = None
self.continuous_dt = None
self.image_socket = Context().socket(zmq.REQ)
self.image_socket.connect(
f'tcp://{self.parent_host}:{self.image_receiver_port}'
)


def init_camera(self, cam):
"""Initialize the camera object"""
self.camera = cam
self.camera.Init()

# def start_continuous(self, dt):
# self.continuous_stop.set()
# if self.camera.IsStreaming():
# self.camera.EndAcquisition()
# self.camera.BeginAcquisition()

# time.sleep(0.05)
# assert self.continuous_thread is None
# # self.camera.configure_acquisition()
# self.continuous_thread = threading.Thread(
# target=self.continuous_loop, args=(dt,), daemon=True
# )
# self.continuous_thread.start()
# self.continuous_dt = dt
def start_continuous(self, dt):
# Ensure fully stopped
self.stop_continuous()

self.continuous_stop.clear()
self.continuous_dt = dt

assert self.continuous_thread is None

# Start thread FIRST
self.continuous_thread = threading.Thread(
target=self.continuous_loop,
args=(dt,),
daemon=True,
)
self.continuous_thread.start()

# Give thread a moment to start blocking on GetNextImage
time.sleep(0.01)

# THEN start acquisition
self.camera.BeginAcquisition()


# def continuous_loop(self, dt):
# """Acquire continuously in a loop, with minimum repetition interval dt"""
# while True:
# if dt is not None:
# t = perf_counter()
# try:
# image = self.camera.GetNextImage(1000)
# img_array = image.GetNDArray()
# self._send_image_to_parent(img_array)
# image.Release()
# except:
# pass
# if dt is None:
# timeout = 0
# else:
# timeout = t + dt - perf_counter()
# if self.continuous_stop.wait(timeout):
# self.continuous_stop.clear()
# break


# self.stop_continuous()

def continuous_loop(self, dt):
try:
while not self.continuous_stop.is_set():
if dt is not None:
t = perf_counter()

try:
image = self.camera.GetNextImage(1000)
except PySpin.SpinnakerException:
continue

if image.IsIncomplete():
image.Release()
continue

class SpinnakerCameraWorker(IMAQdxCameraWorker):
"""Spinnaker API Camera Worker.

Inherits from IMAQdxCameraWorker."""
interface_class = Spinnaker_Camera

#def continuous_loop(self, dt):
# """Acquire continuously in a loop, with minimum repetition interval dt"""
# self.camera.trigger()
# while True:
# if dt is not None:
# t = perf_counter()
# image = self.camera.grab()
# self.camera.trigger()
# self._send_image_to_parent(image)
# if dt is None:
# timeout = 0
# else:
# timeout = t + dt - perf_counter()
# if self.continuous_stop.wait(timeout):
# self.continuous_stop.clear()
# break
img_array = image.GetNDArray()
self._send_image_to_parent(img_array)
image.Release()

if dt is not None:
timeout = max(0, t + dt - perf_counter())
self.continuous_stop.wait(timeout)

finally:
# Stop acquisition HERE, in the acquisition thread
try:
self.camera.EndAcquisition()
except PySpin.SpinnakerException:
pass


# def stop_continuous(self):
# self.continuous_stop.set()
# try:
# self.continuous_thread.join()
# except:
# pass
# self.continuous_thread = None
# if self.camera.IsStreaming():
# self.camera.EndAcquisition()

def stop_continuous(self):
self.continuous_stop.set()

if (
self.continuous_thread is not None
and self.continuous_thread.is_alive()
and threading.current_thread() is not self.continuous_thread
):
self.continuous_thread.join()

self.continuous_thread = None


def snap(self, *args, **kwargs):
if self.camera is None:
raise RuntimeError("Camera not initialized")

try:
if self.camera.IsStreaming():
self.camera.EndAcquisition()
self.camera.BeginAcquisition()

image = self.camera.GetNextImage(1000)
if not image.IsIncomplete():
img_array = image.GetNDArray()
# Send to BLACS GUI
#self.send_image(img_array)
#imageio.imwrite('camera_test/snap.png', img_array)
self._send_image_to_parent(img_array)
image.Release()

self.camera.EndAcquisition()
except PySpin.SpinnakerException as ex:
print(f"Spinnaker exception during snap: {ex}")

def shutdown(self):
"""Clean up camera on BLACS exit"""
try:
self.stop_continuous()
if self.camera is not None:
if self.camera.IsStreaming():
self.camera.EndAcquisition()
self.camera.DeInit()
del self.camera
except PySpin.SpinnakerException as ex:
print(f"Spinnaker exception during shutdown: {ex}")
def program_manual(self, *args, **kwargs):
"""
Called by BLACS when switching to manual mode.
For now, this can be a no-op since we handle snapping via snap().
"""
pass

def abort(self):
"""Called by BLACS when aborting an experiment."""
self.stop_continuous()

def _send_image_to_parent(self, image):
metadata = dict(dtype=str(image.dtype), shape=image.shape)
self.image_socket.send_json(metadata, zmq.SNDMORE)
self.image_socket.send(image, copy=False)
response = self.image_socket.recv()
assert response == b'ok', response


# class SpinnakerCameraWorker(IMAQdxCameraWorker):
# """Spinnaker API Camera Worker.

# Inherits from IMAQdxCameraWorker."""
# interface_class = Spinnaker_Camera

# def init(self):
# self.continuous_thread = None
# print("Spinnaker Worker Called")
# try:
# self.camera = self.interface_class(self.serial_number)
# self.camera.configure_acquisition()
# print(f"Successfully initialized camera {self.serial_number}")
# except Exception as e:
# print(f"Error initializing camera: {e}")
# raise

# def shutdown(self):
# try:
# self.camera.close()
# except Exception as e:
# print(f"Error shutting down camera: {e}")

# def continuous_loop(self, dt):
# """Acquire continuously in a loop, with minimum repetition interval dt"""
# self.camera.trigger()
# while True:
# if dt is not None:
# t = perf_counter()
# image = self.camera.grab()
# self.camera.trigger()
# self._send_image_to_parent(image)
# if dt is None:
# timeout = 0
# else:
# timeout = t + dt - perf_counter()
# if self.continuous_stop.wait(timeout):
# self.continuous_stop.clear()
# break