Skip to content

seekcamera.error.SeekCameraInvalidParameterError on object deletion and recreation #36

@adsherman09

Description

@adsherman09

I am trying to integrate a camera into a bigger system, and I am running into issues starting and stopping the camera. Specficially, I can't seem to delete the object and then recreate the object without running into SeekCameraInvalidParameterErrors on the re-created object. I have example code here, running seekcamera-python-1.3.0 on SDK 4.4.2.20. It should run from cli with no arguments. The output is here, with the code afterwards.

sherman:$ python bad_seek.py
Initializing camera E65609010C11
Frame callback registered.
Camera connected: E65609010C11
SeekCameraManager initialized and event callback registered.
Camera stream started in a separate thread. Delaying for 5000 ms
Camera started. You can now call `get_array` independently.
Captured frame at 2025-01-22T14:13:18.533407 -- [1.000, 120.841, 255.000]
Captured frame at 2025-01-22T14:13:18.644191 -- [1.000, 120.841, 255.000]
Captured frame at 2025-01-22T14:13:18.755333 -- [1.000, 121.800, 255.000]
...
Captured frame at 2025-01-22T14:13:23.320362 -- [1.000, 121.994, 255.000]
Captured frame at 2025-01-22T14:13:23.431807 -- [1.000, 122.118, 255.000]
Captured frame at 2025-01-22T14:13:23.543239 -- [1.000, 121.874, 255.000]
Thermal camera stream stopped.
Camera stopped.
Initializing camera E65609010C11
SeekCameraManager initialized and event callback registered.
Unhandled exception:
Traceback (most recent call last):
  File "/home/sherman/bad_seek.py", line 74, in start
    if hasattr(self.renderer.camera, "color_palette"):
  File "/home/sherman/SeekThermal/seekcamera-python-1.3.0/seekcamera-python-1.3.0/seekcamera/camera.py", line 1381, in color_palette
    raise error_from_status(status)
seekcamera.error.SeekCameraInvalidParameterError
Camera started. You can now call `get_array` independently.
Dropped 10 frames
Dropped 20 frames
Dropped 30 frames
...
Dropped 1000 frames
Dropped too many frames, stopping
Camera stopped.

Source code

#seekcameras.py

from collections import deque
from datetime import datetime
from threading import Condition, Thread

import numpy as np
import time
import sys
import traceback

from seekcamera import (
    SeekCameraIOType,
    SeekCameraManager,
    SeekCameraManagerEvent,
    SeekCameraFrameFormat,
    SeekCameraColorPalette,
    SeekCamera,
    SeekFrame,
)

SERIAL = "E65609010C11"


class SeekCameraS(object):
    def __init__(self, serial, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.serial = serial  # Target serial number for filtering

        self.camera = None
        self.manager = None
        self.renderer = None
        self.ready = False
        self.is_init = False
        self.is_start = False
        self.frame_queue = deque(maxlen=100)  # Queue to hold up to 100 frames
        self.thread = None
        self.running = False

        self.config_default = {
            "start_delay": 5000,  # ms
            "acquisition_rate": 9,  # Hz
        }
        self.config_active = self.config_default.copy()
        self.last_frame_time = time.time()

        self.dropped_frames = 0

    def initialize(self, **config):
        """Initialize the Seek camera and set up callbacks."""
        self.config_active.update(config)
        self.frame_interval = 1.0 / self.config_active["acquisition_rate"]

        if self.is_init:
            print(f"Device {self.serial} already initialized")
            self.close()
        else:
            print(f"Initializing camera {self.serial}")

        try:
            self.renderer = Renderer()
            self.manager = SeekCameraManager(SeekCameraIOType.USB)
            self.manager.register_event_callback(self._on_event, self.renderer)
            print("SeekCameraManager initialized and event callback registered.")
            self.ready = True
            self.is_init = True
        except Exception as e:
            print(f"Unhandled exception: {e}")
            traceback.print_exc()
            self.ready = False

    def start(self):
        """Start the camera stream."""
        if self.ready and self.renderer.camera:
            try:
                if hasattr(self.renderer.camera, "color_palette"):
                    if self.renderer.camera.color_palette != SeekCameraColorPalette.TYRIAN:
                        self.renderer.camera.color_palette = SeekCameraColorPalette.TYRIAN
                self.renderer.camera.capture_session_start(SeekCameraFrameFormat.COLOR_ARGB8888)
                self.is_start = True
                self.running = True
                self.thread = Thread(target=self._run_camera, daemon=True)
                self.thread.start()
                print(
                    "Camera stream started in a separate thread. Delaying for {} ms".format(
                        self.config_active["start_delay"]
                    )
                )
                time.sleep(self.config_active["start_delay"] / 1000.0)
            except Exception as e:
                print(f"Unhandled exception: {e}")
                traceback.print_exc()

    def stop(self):
        """Stop the camera stream."""
        if self.is_start and self.renderer.camera:
            try:
                self.running = False
                if self.thread:
                    self.thread.join()
                self.renderer.camera.capture_session_stop()
                self.is_start = False
                print("Thermal camera stream stopped.")
            except Exception as e:
                print(f"Unhandled exception: {e}")
                traceback.print_exc()

    def close(self):
        """Release camera resources."""
        if self.manager:
            try:
                del self.manager  # Ensure the manager is properly released
                print("SeekCameraManager released.")
                self.is_init = False
                self.is_start = False
                self.running = False
            except Exception as e:
                print(f"Unhandled exception: {e}")
                traceback.print_exc()
        self.ready = False

    def get_array(self, force=False):
        """Retrieve the most recent frame from the queue as a NumPy array."""
        # Sleep if the frame interval has not passed, this is our way of throttling the frame rate
        if not force and time.time() - self.last_frame_time < self.frame_interval:
            # print(f"Sleeping for {self.frame_interval - (time.time() - self.last_frame_time)} seconds")
            time.sleep(self.frame_interval - (time.time() - self.last_frame_time))

        if self.frame_queue:
            # print(f"Getting frame from queue")
            frame = self.frame_queue[-1]
            self.last_frame_time = time.time()
            return np.array(frame.data)

        self.dropped_frames += 1
        if self.dropped_frames > 1 and self.dropped_frames % 10 == 0:
            print(f"Dropped {self.dropped_frames} frames")
        return None

    def _run_camera(self):
        """Background thread to process frames."""
        while self.running:
            with self.renderer.frame_condition:
                if self.renderer.frame_condition.wait(10 * self.frame_interval):
                    frame = self.renderer.frame
                    if frame is not None:
                        self.frame_queue.append(frame)

    def _on_event(self, camera, event_type, event_status, renderer):
        """Callback for handling camera events."""
        if event_type == SeekCameraManagerEvent.CONNECT:
            if renderer.busy:
                return

            if self.serial and camera.chipid != self.serial:
                print(f"Ignoring camera with serial: {camera.chipid}")
                return

            renderer.busy = True
            renderer.camera = camera
            renderer.first_frame = True

            try:
                camera.register_frame_available_callback(self._on_frame, renderer)
                print("Frame callback registered.")
            except Exception as e:
                print(f"Failed to register frame callback: {e}")

            print(f"Camera connected: {camera.chipid}")

        elif event_type == SeekCameraManagerEvent.DISCONNECT:
            if renderer.camera == camera:
                camera.capture_session_stop()
                renderer.camera = None
                renderer.frame = None
                renderer.busy = False
                print("Camera disconnected.")

        elif event_type == SeekCameraManagerEvent.ERROR:
            print(f"Camera error: {event_status}")

    def _on_frame(self, _camera, camera_frame, renderer):
        """Callback for handling new frames."""
        if camera_frame.color_argb8888 is not None:
            with renderer.frame_condition:
                renderer.frame = camera_frame.color_argb8888
                renderer.frame_condition.notify()


class Renderer:
    """Renderer class to manage camera and frame data."""

    def __init__(self):
        self.busy = False
        self.frame = SeekFrame()  # Match initialization with the original logic
        self.camera = SeekCamera()
        self.frame_condition = Condition()
        self.first_frame = True


def demo0(duration=None):
    seek_cam = SeekCameraS(serial=SERIAL)
    seek_cam.initialize()

    dropped_frames = 0
    if seek_cam.ready:
        seek_cam.start()
        t0 = time.time()
        print("Camera started. You can now call `get_array` independently.")

        try:
            while duration is None or time.time() - t0 < duration:
                frame = seek_cam.get_array()
                if frame is None:
                    dropped_frames += 1
                    if dropped_frames > 0 and dropped_frames % 100 == 0:
                        print(f"Dropped {dropped_frames} frames")
                    if dropped_frames > 1000:
                        print("Dropped too many frames, stopping")
                        break
                else:
                    print(
                        "Captured frame at {} -- [{:4.3f}, {:4.3f}, {:4.3f}]".format(
                            datetime.now().isoformat(), frame.min(), frame.mean(), frame.max()
                        )
                    )
                # time.sleep(0.1)  # Simulate periodic access
        except KeyboardInterrupt:
            print("Keyboard interrupt detected.")
        finally:
            seek_cam.stop()
            print("Camera stopped.")

    return seek_cam


def demo1():
    print("demo1; running for 5s, deleting seek_cam, then running for 5s again")
    seek_cam = demo0(duration=5)
    del seek_cam
    seek_cam = demo0(duration=5)


if __name__ == "__main__":
    demo1()  # RUN THIS FOR BAD

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions