From 748282f22cf8b084df6b15115479a99dd0ad8115 Mon Sep 17 00:00:00 2001 From: Patrick Wspanialy Date: Tue, 23 Jan 2024 15:30:18 -0500 Subject: [PATCH 1/2] update to 1.3 --- CHANGELOG.md | 15 ++ examples/seekcamera-opencv_record.py | 250 +++++++++++++++++++++++++++ seekcamera/__init__.py | 1 + seekcamera/_clib.py | 196 +++++++++++++++++++-- seekcamera/camera.py | 233 ++++++++++++++++++++++++- 5 files changed, 679 insertions(+), 16 deletions(-) create mode 100644 examples/seekcamera-opencv_record.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f54a8827..e06f933c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ # CHANGELOG +# v1.3.0 + +Fourth stable release of the Python language bindings for the Seek Thermal SDK 4.X. + +Released on 06/06/2023. + +Highlights +* New APIs to wrap Pipeline APIs added to the C SDK (Seek Vision, Legacy, Lite) +* Adjust API to wrap Linear AGC Min & Max APIs modified in the C SDK +* New API to wrap the Sharpening Filter added to the C SDK +* New API to wrap the HistEQ ROI APIs added to the C SDK +* New example added which includes Video Recording + ## v1.2.0 Third stable release of the Python language bindings for the Seek Thermal SDK 4.X. +Released on 07/06/2022 + Highlights * New APIs to wrap AGC APIs added to the C SDK (HistEQ, Linear) * New APIs to wrap frame locking APIs added to the C SDK diff --git a/examples/seekcamera-opencv_record.py b/examples/seekcamera-opencv_record.py new file mode 100644 index 00000000..fd2c75fb --- /dev/null +++ b/examples/seekcamera-opencv_record.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# Copyright 2021 Seek Thermal Inc. +# +# Original author: Michael S. Mead +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from threading import Condition + +import cv2 +import numpy +import PIL.Image +import os +import glob + +from PIL import Image, ImageFont, ImageDraw + +from seekcamera import ( + SeekCameraIOType, + SeekCameraColorPalette, + SeekCameraManager, + SeekCameraManagerEvent, + SeekCameraFrameFormat, + SeekCameraShutterMode, + SeekCamera, + SeekFrame, +) + + +class Renderer: + """Contains camera and image data required to render images to the screen.""" + + def __init__(self): + self.busy = False + self.frame = SeekFrame() + self.camera = SeekCamera() + self.frame_condition = Condition() + self.first_frame = True + + +def on_frame(_camera, camera_frame, renderer): + """Async callback fired whenever a new frame is available. + + Parameters + ---------- + _camera: SeekCamera + Reference to the camera for which the new frame is available. + camera_frame: SeekCameraFrame + Reference to the class encapsulating the new frame (potentially + in multiple formats). + renderer: Renderer + User defined data passed to the callback. This can be anything + but in this case it is a reference to the renderer object. + """ + + # Acquire the condition variable and notify the main thread + # that a new frame is ready to render. This is required since + # all rendering done by OpenCV needs to happen on the main thread. + with renderer.frame_condition: + renderer.frame = camera_frame.color_argb8888 + renderer.frame_condition.notify() + + +def on_event(camera, event_type, event_status, renderer): + """Async callback fired whenever a camera event occurs. + + Parameters + ---------- + camera: SeekCamera + Reference to the camera on which an event occurred. + event_type: SeekCameraManagerEvent + Enumerated type indicating the type of event that occurred. + event_status: Optional[SeekCameraError] + Optional exception type. It will be a non-None derived instance of + SeekCameraError if the event_type is SeekCameraManagerEvent.ERROR. + renderer: Renderer + User defined data passed to the callback. This can be anything + but in this case it is a reference to the Renderer object. + """ + print("{}: {}".format(str(event_type), camera.chipid)) + + if event_type == SeekCameraManagerEvent.CONNECT: + if renderer.busy: + return + + # Claim the renderer. + # This is required in case of multiple cameras. + renderer.busy = True + renderer.camera = camera + + # Indicate the first frame has not come in yet. + # This is required to properly resize the rendering window. + renderer.first_frame = True + + # Set a custom color palette. + # Other options can set in a similar fashion. + camera.color_palette = SeekCameraColorPalette.TYRIAN + + # Start imaging and provide a custom callback to be called + # every time a new frame is received. + camera.register_frame_available_callback(on_frame, renderer) + camera.capture_session_start(SeekCameraFrameFormat.COLOR_ARGB8888) + + elif event_type == SeekCameraManagerEvent.DISCONNECT: + # Check that the camera disconnecting is one actually associated with + # the renderer. This is required in case of multiple cameras. + if renderer.camera == camera: + # Stop imaging and reset all the renderer state. + camera.capture_session_stop() + renderer.camera = None + renderer.frame = None + renderer.busy = False + + elif event_type == SeekCameraManagerEvent.ERROR: + print("{}: {}".format(str(event_status), camera.chipid)) + + elif event_type == SeekCameraManagerEvent.READY_TO_PAIR: + return + +def bgra2rgb( bgra ): + row, col, ch = bgra.shape + + assert ch == 4, 'ARGB image has 4 channels.' + + rgb = numpy.zeros( (row, col, 3), dtype='uint8' ) + # convert to rgb expected to generate the jpeg image + rgb[:,:,0] = bgra[:,:,2] + rgb[:,:,1] = bgra[:,:,1] + rgb[:,:,2] = bgra[:,:,0] + + return rgb + +def main(): + window_name = "Seek Thermal - Python OpenCV Sample" + cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) + fileName = "image" + counter = 100000 + capture = False + record = False + ts_first = 0 + ts_last = 0 + frame_count = 0 + + from PIL import Image + from pathlib import Path + for f in glob.glob(fileName + '*.jpg'): + os.remove(f) + + print("\nuser controls:") + print("c: capture") + print("r: record") + print("q: quit") + + # Create a context structure responsible for managing all connected USB cameras. + # Cameras with other IO types can be managed by using a bitwise or of the + # SeekCameraIOType enum cases. + with SeekCameraManager(SeekCameraIOType.USB) as manager: + # Start listening for events. + renderer = Renderer() + manager.register_event_callback(on_event, renderer) + + while True: + # Wait a maximum of 150ms for each frame to be received. + # A condition variable is used to synchronize the access to the renderer; + # it will be notified by the user defined frame available callback thread. + with renderer.frame_condition: + if renderer.frame_condition.wait(150.0 / 1000.0): + img = renderer.frame.data + + # Resize the rendering window. + if renderer.first_frame: + (height, width, _) = img.shape + cv2.resizeWindow(window_name, width * 2, height * 2) + renderer.first_frame = False + + # Render the image to the window. + cv2.imshow(window_name, img) + + # if capture or recording, convert the frame image + # to RGB and generate the file. + # Currently counter is a big number to allow easy ordering + # of frames when recording. + if capture or record: + rgbimg = bgra2rgb(img) + frame_count += 1 + im = Image.fromarray(rgbimg).convert('RGB') + jpgName = Path('.', fileName + str(counter)).with_suffix('.jpg') + im.save(jpgName) + counter += 1 + capture = False + if record: + ts_last = renderer.frame.header.timestamp_utc_ns + if ts_first == 0: + ts_first = renderer.frame.header.timestamp_utc_ns + # Process key events. + key = cv2.waitKey(1) + if key == ord("q"): + break + + if key == ord("c"): + capture = True + + if key == ord("r"): + if record == False: + record = True + renderer.camera.shutter_mode = SeekCameraShutterMode.MANUAL + print("\nRecording! Press 'r' to stop recording") + print("Note: shutter is disabled while recording...so keep the videos relatively short") + else: + # Stop the recording and squish all the jpeg files together + # and generate the .avi file. + record = False + renderer.camera.shutter_mode = SeekCameraShutterMode.AUTO + + time_s = (ts_last - ts_first)/1000000000 + + print("\nRecording stopped and video is in myVideo.avi") + img_array = [] + for filename in glob.glob('image*.jpg'): + img = cv2.imread(filename) + height, width, layers = img.shape + size = (width,height) + img_array.append(img) + out = cv2.VideoWriter('myVideo.avi', cv2.VideoWriter_fourcc(*'DIVX'), frame_count/time_s, size) + + frame_count = ts_first = ts_last = 0 + + for i in range(len(img_array)): + out.write(img_array[i]) + out.release() + + # Check if the window has been closed manually. + if not cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE): + break + + cv2.destroyWindow(window_name) + + +if __name__ == "__main__": + main() diff --git a/seekcamera/__init__.py b/seekcamera/__init__.py index 89d027d0..fc26ae68 100644 --- a/seekcamera/__init__.py +++ b/seekcamera/__init__.py @@ -21,6 +21,7 @@ SeekCameraAppResourcesRegion, SeekCameraColorPalette, SeekCameraColorPaletteData, + SeekCameraPipelineMode, SeekCameraAGCMode, SeekCameraLinearAGCLockMode, SeekCameraShutterMode, diff --git a/seekcamera/_clib.py b/seekcamera/_clib.py index 27c100d7..64710725 100644 --- a/seekcamera/_clib.py +++ b/seekcamera/_clib.py @@ -320,6 +320,17 @@ def assert_runtime_version_met(version, min_version, name): ctypes.c_int32, ctypes.POINTER(CSeekCameraColorPaletteDataEntry * 256), ] + + # seekcamera_get_pipeline_mode + _cdll.seekcamera_get_pipeline_mode.restype = ctypes.c_int32 + _cdll.seekcamera_get_pipeline_mode.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_pipeline_mode + _cdll.seekcamera_set_pipeline_mode.restype = ctypes.c_int32 + _cdll.seekcamera_set_pipeline_mode.argtypes = [ctypes.c_void_p, ctypes.c_int32] # seekcamera_get_agc_mode _cdll.seekcamera_get_agc_mode.restype = ctypes.c_int32 @@ -454,6 +465,76 @@ def assert_runtime_version_met(version, min_version, name): ctypes.c_void_p, ctypes.c_float, ] + + # seekcamera_get_histeq_agc_roi_left + _cdll.seekcamera_get_histeq_agc_roi_left.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_left.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_left + _cdll.seekcamera_set_histeq_agc_roi_left.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_left.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_top + _cdll.seekcamera_get_histeq_agc_roi_top.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_top.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_top + _cdll.seekcamera_set_histeq_agc_roi_top.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_top.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_width + _cdll.seekcamera_get_histeq_agc_roi_width.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_width.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_width + _cdll.seekcamera_set_histeq_agc_roi_width.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_width.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_height + _cdll.seekcamera_get_histeq_agc_roi_height.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_height.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_height + _cdll.seekcamera_set_histeq_agc_roi_height.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_height.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_enable + _cdll.seekcamera_get_histeq_agc_roi_enable.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_enable.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_bool), + ] + + # seekcamera_set_histeq_agc_roi_enable + _cdll.seekcamera_set_histeq_agc_roi_enable.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_enable.argtypes = [ + ctypes.c_void_p, + ctypes.c_bool, + ] # seekcamera_get_linear_agc_lock_mode _cdll.seekcamera_get_linear_agc_lock_mode.restype = ctypes.c_int32 @@ -473,28 +554,28 @@ def assert_runtime_version_met(version, min_version, name): _cdll.seekcamera_get_linear_agc_lock_min.restype = ctypes.c_int32 _cdll.seekcamera_get_linear_agc_lock_min.argtypes = [ ctypes.c_void_p, - ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_float), ] # seekcamera_set_linear_agc_lock_min _cdll.seekcamera_set_linear_agc_lock_min.restype = ctypes.c_int32 _cdll.seekcamera_set_linear_agc_lock_min.argtypes = [ ctypes.c_void_p, - ctypes.c_uint32, + ctypes.c_float, ] # seekcamera_get_linear_agc_lock_max _cdll.seekcamera_get_linear_agc_lock_max.restype = ctypes.c_int32 _cdll.seekcamera_get_linear_agc_lock_max.argtypes = [ ctypes.c_void_p, - ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_float), ] # seekcamera_set_linear_agc_lock_max _cdll.seekcamera_set_linear_agc_lock_max.restype = ctypes.c_int32 _cdll.seekcamera_set_linear_agc_lock_max.argtypes = [ ctypes.c_void_p, - ctypes.c_uint32, + ctypes.c_float, ] # seekcamera_get_shutter_mode @@ -687,11 +768,12 @@ class CSeekCameraFrameHeader(ctypes.Structure): ("histeq_agc_bin_width", ctypes.c_uint16), ("histeq_agc_gain_limit_factor", ctypes.c_float), ("histeq_agc_reserved", ctypes.c_uint8 * 64), - ("linear_agc_min", ctypes.c_uint32), - ("linear_agc_max", ctypes.c_uint32), + ("linear_agc_min", ctypes.c_float), + ("linear_agc_max", ctypes.c_float), ("linear_agc_reserved", ctypes.c_uint8 * 32), ("gradient_correction_filter_state", ctypes.c_uint8), ("flat_scene_correction_filter_state", ctypes.c_uint8), + ("sharpen_correction_filter_state", ctypes.c_uint8), ("reserved", ctypes.c_uint8 * 1798), ] @@ -1005,6 +1087,16 @@ def cseekcamera_set_color_palette_data(camera, palette, palette_data): ) +def cseekcamera_get_pipeline_mode(camera): + mode = ctypes.c_int32() + status = _cdll.seekcamera_get_pipeline_mode(camera.pointer, ctypes.byref(mode)) + return mode, status + + +def cseekcamera_set_pipeline_mode(camera, mode): + return _cdll.seekcamera_set_pipeline_mode(camera.pointer, ctypes.c_int32(mode)) + + def cseekcamera_get_agc_mode(camera): mode = ctypes.c_int32() status = _cdll.seekcamera_get_agc_mode(camera.pointer, ctypes.byref(mode)) @@ -1147,6 +1239,75 @@ def cseekcamera_set_histeq_agc_trim_right(camera, trim): camera.pointer, ctypes.c_float(trim) ) +def cseekcamera_set_histeq_agc_roi_left(camera, left): + return _cdll.seekcamera_set_histeq_agc_roi_left( + camera.pointer, ctypes.c_int32(left) + ) + +def cseekcamera_get_histeq_agc_roi_left(camera): + left = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_left( + camera.pointer, ctypes.byref(left) + ) + + return left, status + + +def cseekcamera_set_histeq_agc_roi_top(camera, top): + return _cdll.seekcamera_set_histeq_agc_roi_top( + camera.pointer, ctypes.c_int32(top) + ) + +def cseekcamera_get_histeq_agc_roi_top(camera): + top = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_top( + camera.pointer, ctypes.byref(top) + ) + + return top, status + + +def cseekcamera_set_histeq_agc_roi_width(camera, width): + return _cdll.seekcamera_set_histeq_agc_roi_width( + camera.pointer, ctypes.c_int32(width) + ) + +def cseekcamera_get_histeq_agc_roi_width(camera): + width = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_width( + camera.pointer, ctypes.byref(width) + ) + + return width, status + + +def cseekcamera_set_histeq_agc_roi_height(camera, height): + return _cdll.seekcamera_set_histeq_agc_roi_height( + camera.pointer, ctypes.c_int32(height) + ) + +def cseekcamera_get_histeq_agc_roi_height(camera): + height = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_height( + camera.pointer, ctypes.byref(height) + ) + + return height, status + + +def cseekcamera_set_histeq_agc_roi_enable(camera, enable): + return _cdll.seekcamera_set_histeq_agc_roi_enable( + camera.pointer, ctypes.c_bool(enable) + ) + +def cseekcamera_get_histeq_agc_roi_enable(camera): + enable = ctypes.c_bool() + status = _cdll.seekcamera_get_histeq_agc_roi_enable( + camera.pointer, ctypes.byref(enable) + ) + + return enable, status + def cseekcamera_get_linear_agc_lock_mode(camera): mode = ctypes.c_int32() @@ -1164,7 +1325,7 @@ def cseekcamera_set_linear_agc_lock_mode(camera, mode): def cseekcamera_get_linear_agc_lock_min(camera): - lock_min = ctypes.c_uint32() + lock_min = ctypes.c_float() status = _cdll.seekcamera_get_linear_agc_lock_min( camera.pointer, ctypes.byref(lock_min) ) @@ -1174,12 +1335,12 @@ def cseekcamera_get_linear_agc_lock_min(camera): def cseekcamera_set_linear_agc_lock_min(camera, lock_min): return _cdll.seekcamera_set_linear_agc_lock_min( - camera.pointer, ctypes.c_uint32(lock_min) + camera.pointer, ctypes.c_float(lock_min) ) def cseekcamera_get_linear_agc_lock_max(camera): - lock_max = ctypes.c_uint32() + lock_max = ctypes.c_float() status = _cdll.seekcamera_get_linear_agc_lock_max( camera.pointer, ctypes.byref(lock_max) ) @@ -1189,7 +1350,7 @@ def cseekcamera_get_linear_agc_lock_max(camera): def cseekcamera_set_linear_agc_lock_max(camera, lock_max): return _cdll.seekcamera_set_linear_agc_lock_max( - camera.pointer, ctypes.c_uint32(lock_max) + camera.pointer, ctypes.c_float(lock_max) ) @@ -1277,6 +1438,21 @@ def cseekcamera_set_flat_scene_correction_filter_enable(camera, enable): ) +def cseekcamera_get_sharpen_correction_filter_enable(camera): + enable = ctypes.c_bool() + status = _cdll.seekcamera_get_sharpen_correction_filter_enable( + camera.pointer, ctypes.byref(enable) + ) + + return enable, status + + +def cseekcamera_set_sharpen_correction_filter_enable(camera, enable): + return _cdll.seekcamera_set_sharpen_correction_filter_enable( + camera.pointer, ctypes.c_bool(enable) + ) + + def cseekcamera_set_filter_state(camera, filter_type, filter_state): return _cdll.seekcamera_set_filter_state( camera.pointer, ctypes.c_int32(filter_type), ctypes.c_int32(filter_state) diff --git a/seekcamera/camera.py b/seekcamera/camera.py index aac02066..13baf340 100644 --- a/seekcamera/camera.py +++ b/seekcamera/camera.py @@ -352,6 +352,19 @@ def __repr__(self): return "SeekCameraLinearAGCLockMode({})".format(self.value) +class SeekCameraPipelineMode(IntEnum): + + LITE = 0 + LEGACY = 1 + SEEKVISION = 2 + + + def __str__(self): + return self.name + + def __repr__(self): + return "SeekCameraPipelineMode({})".format(self.value) + class SeekCameraShutterMode(IntEnum): """Types of shutter modes. @@ -491,10 +504,13 @@ class SeekCameraFilter(IntEnum): FLAT_SCENE_CORRECTION: int Filter responsible for correcting non-uniformities on all data pipelines. It is stored explicitly by the user apriori. + SHARPEN_CORRECTION: int + Filter responsible for sharpening. """ GRADIENT_CORRECTION = 0 FLAT_SCENE_CORRECTION = 1 + SHARPEN_CORRECTION = 2 def __str__(self): return self.name @@ -757,11 +773,21 @@ class SeekCamera(object): Gets/sets the histogram left trim percentage used for HistEQ AGC. histeq_agc_trim_right: float Gets/sets the histogram right trim percentage used for HistEQ AGC. + histeq_agc_roi_left: int + Gets/sets the left roi used for HistEQ AGC. + histeq_agc_roi_top: int + Gets/sets the top roi used for HistEQ AGC. + histeq_agc_roi_width: int + Gets/sets the width roi used for HistEQ AGC. + histeq_agc_roi_height: int + Gets/sets the height roi used for HistEQ AGC. + histeq_agc_roi_enable: int + Gets/sets the enable roi used for HistEQ AGC. linear_agc_lock_mode: SeekCameraLinearAGCLock Mode Gets/sets the lock mode used for Linear AGC. - linear_agc_lock_min: int + linear_agc_lock_min: float Gets/sets the minimum lock value used for Linear AGC. - linear_agc_lock_max: int + linear_agc_lock_max: float Gets/sets the maximum lock value used for Linear AGC. shutter_mode: SeekCameraShutterMode Gets/sets the shutter mode. @@ -1365,6 +1391,38 @@ def color_palette(self, palette): if is_error(status): raise error_from_status(status) + @property + def pipeline_mode(self): + """Gets/sets the active pipeline mode. + + Settings are refreshed between frames. This method can only be performed after + a capture session has started. + + Returns + ------- + SeekCameraPipelineMode + Active pipeline mode used by the camera. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + mode, status = _clib.cseekcamera_get_pipeline_mode(self._camera) + if is_error(status): + raise error_from_status(status) + + return SeekCameraPipelineMode(mode.value) + + @pipeline_mode.setter + def pipeline_mode(self, mode): + if not isinstance(mode, SeekCameraPipelineMode): + raise SeekCameraInvalidParameterError + + status = _clib.cseekcamera_set_pipeline_mode(self._camera, mode) + if is_error(status): + raise error_from_status(status) + @property def agc_mode(self): """Gets/sets the active AGC mode. @@ -1687,6 +1745,156 @@ def histeq_agc_trim_right(self, trim_left): if is_error(status): raise error_from_status(status) + @property + def histeq_agc_roi_left(self): + """Gets/sets the left ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Left ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + left, status = _clib.cseekcamera_get_histeq_agc_roi_left(self._camera) + + if is_error(status): + raise error_from_status(status) + + return left.value + + @histeq_agc_roi_left.setter + def histeq_agc_roi_left(self, left): + status = _clib.cseekcamera_set_histeq_agc_roi_left(self._camera, left) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_top(self): + """Gets/sets the top ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Top ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + top, status = _clib.cseekcamera_get_histeq_agc_roi_top(self._camera) + + if is_error(status): + raise error_from_status(status) + + return top.value + + @histeq_agc_roi_top.setter + def histeq_agc_roi_top(self, top): + status = _clib.cseekcamera_set_histeq_agc_roi_top(self._camera, top) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_width(self): + """Gets/sets the width ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Width ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + width, status = _clib.cseekcamera_get_histeq_agc_roi_width(self._camera) + + if is_error(status): + raise error_from_status(status) + + return width.value + + @histeq_agc_roi_width.setter + def histeq_agc_roi_width(self, width): + status = _clib.cseekcamera_set_histeq_agc_roi_width(self._camera, width) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_height(self): + """Gets/sets the height ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Height ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + height, status = _clib.cseekcamera_get_histeq_agc_roi_height(self._camera) + + if is_error(status): + raise error_from_status(status) + + return height.value + + @histeq_agc_roi_height.setter + def histeq_agc_roi_height(self, height): + status = _clib.cseekcamera_set_histeq_agc_roi_height(self._camera, height) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_enable(self): + """Gets/sets the enable ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Enable ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + enable, status = _clib.cseekcamera_get_histeq_agc_roi_enable(self._camera) + + if is_error(status): + raise error_from_status(status) + + return enable.value + + @histeq_agc_roi_enable.setter + def histeq_agc_roi_enable(self, enable): + status = _clib.cseekcamera_set_histeq_agc_roi_enable(self._camera, enable) + + if is_error(status): + raise error_from_status(status) + @property def linear_agc_lock_mode(self): """Gets/sets the lock mode used for Linear AGC. @@ -2411,15 +2619,17 @@ class SeekCameraFrameHeader(object): Gets the coordinates and value of the spot thermography pixel. agc_mode: SeekCameraAGCMode AGC mode used to process the image. + pipeline_mode: SeekCameraPiplineMode + Pipeline mode used to process the image. histeq_agc_num_bins: int Number of bins in the HistEQ AGC histogram. histeq_agc_bin_width: int Number of counts per bin in the HistEQ AGC histogram. histeq_agc_gain_limit_factor: float Multiplier of the HistEQ gain limit. - linear_agc_min: int + linear_agc_min: float Minimum count value in the frame when using Linear AGC. - linear_agc_max: int + linear_agc_max: float Maximum count value in the frame when using Linear AGC. gradient_correction_filter_state : SeekCameraFilterState State of the gradient correction filter. @@ -2772,7 +2982,7 @@ def linear_agc_min(self): Returns ------- - int + float Minimum count value in the frame when using Linear AGC. """ return self._header.linear_agc_min @@ -2783,7 +2993,7 @@ def linear_agc_max(self): Returns ------- - int + float Minimum count value in the frame when using Linear AGC. """ return self._header.linear_agc_max @@ -2809,6 +3019,17 @@ def flat_scene_correction_filter_state(self): State of the flat scene correction filter. """ return SeekCameraFilterState(self._header.flat_scene_correction_filter_state) + + @property + def sharpen_correction_filter_state(self): + """Gets the state of the sharpen correction filter. + + Returns + ------- + SeekCameraFilterState + State of the sharpen correction filter. + """ + return SeekCameraFilterState(self._header.sharpen_correction_filter_state) class SeekFrame: From 384e23816af21744a43ccb6a1119546d0cc3376d Mon Sep 17 00:00:00 2001 From: Patrick Wspanialy Date: Tue, 23 Jan 2024 15:30:18 -0500 Subject: [PATCH 2/2] update to 1.3 Signed-off-by: Patrick Wspanialy --- CHANGELOG.md | 15 ++ examples/seekcamera-opencv_record.py | 250 +++++++++++++++++++++++++++ seekcamera/__init__.py | 1 + seekcamera/_clib.py | 196 +++++++++++++++++++-- seekcamera/camera.py | 233 ++++++++++++++++++++++++- 5 files changed, 679 insertions(+), 16 deletions(-) create mode 100644 examples/seekcamera-opencv_record.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f54a8827..e06f933c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ # CHANGELOG +# v1.3.0 + +Fourth stable release of the Python language bindings for the Seek Thermal SDK 4.X. + +Released on 06/06/2023. + +Highlights +* New APIs to wrap Pipeline APIs added to the C SDK (Seek Vision, Legacy, Lite) +* Adjust API to wrap Linear AGC Min & Max APIs modified in the C SDK +* New API to wrap the Sharpening Filter added to the C SDK +* New API to wrap the HistEQ ROI APIs added to the C SDK +* New example added which includes Video Recording + ## v1.2.0 Third stable release of the Python language bindings for the Seek Thermal SDK 4.X. +Released on 07/06/2022 + Highlights * New APIs to wrap AGC APIs added to the C SDK (HistEQ, Linear) * New APIs to wrap frame locking APIs added to the C SDK diff --git a/examples/seekcamera-opencv_record.py b/examples/seekcamera-opencv_record.py new file mode 100644 index 00000000..fd2c75fb --- /dev/null +++ b/examples/seekcamera-opencv_record.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# Copyright 2021 Seek Thermal Inc. +# +# Original author: Michael S. Mead +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from threading import Condition + +import cv2 +import numpy +import PIL.Image +import os +import glob + +from PIL import Image, ImageFont, ImageDraw + +from seekcamera import ( + SeekCameraIOType, + SeekCameraColorPalette, + SeekCameraManager, + SeekCameraManagerEvent, + SeekCameraFrameFormat, + SeekCameraShutterMode, + SeekCamera, + SeekFrame, +) + + +class Renderer: + """Contains camera and image data required to render images to the screen.""" + + def __init__(self): + self.busy = False + self.frame = SeekFrame() + self.camera = SeekCamera() + self.frame_condition = Condition() + self.first_frame = True + + +def on_frame(_camera, camera_frame, renderer): + """Async callback fired whenever a new frame is available. + + Parameters + ---------- + _camera: SeekCamera + Reference to the camera for which the new frame is available. + camera_frame: SeekCameraFrame + Reference to the class encapsulating the new frame (potentially + in multiple formats). + renderer: Renderer + User defined data passed to the callback. This can be anything + but in this case it is a reference to the renderer object. + """ + + # Acquire the condition variable and notify the main thread + # that a new frame is ready to render. This is required since + # all rendering done by OpenCV needs to happen on the main thread. + with renderer.frame_condition: + renderer.frame = camera_frame.color_argb8888 + renderer.frame_condition.notify() + + +def on_event(camera, event_type, event_status, renderer): + """Async callback fired whenever a camera event occurs. + + Parameters + ---------- + camera: SeekCamera + Reference to the camera on which an event occurred. + event_type: SeekCameraManagerEvent + Enumerated type indicating the type of event that occurred. + event_status: Optional[SeekCameraError] + Optional exception type. It will be a non-None derived instance of + SeekCameraError if the event_type is SeekCameraManagerEvent.ERROR. + renderer: Renderer + User defined data passed to the callback. This can be anything + but in this case it is a reference to the Renderer object. + """ + print("{}: {}".format(str(event_type), camera.chipid)) + + if event_type == SeekCameraManagerEvent.CONNECT: + if renderer.busy: + return + + # Claim the renderer. + # This is required in case of multiple cameras. + renderer.busy = True + renderer.camera = camera + + # Indicate the first frame has not come in yet. + # This is required to properly resize the rendering window. + renderer.first_frame = True + + # Set a custom color palette. + # Other options can set in a similar fashion. + camera.color_palette = SeekCameraColorPalette.TYRIAN + + # Start imaging and provide a custom callback to be called + # every time a new frame is received. + camera.register_frame_available_callback(on_frame, renderer) + camera.capture_session_start(SeekCameraFrameFormat.COLOR_ARGB8888) + + elif event_type == SeekCameraManagerEvent.DISCONNECT: + # Check that the camera disconnecting is one actually associated with + # the renderer. This is required in case of multiple cameras. + if renderer.camera == camera: + # Stop imaging and reset all the renderer state. + camera.capture_session_stop() + renderer.camera = None + renderer.frame = None + renderer.busy = False + + elif event_type == SeekCameraManagerEvent.ERROR: + print("{}: {}".format(str(event_status), camera.chipid)) + + elif event_type == SeekCameraManagerEvent.READY_TO_PAIR: + return + +def bgra2rgb( bgra ): + row, col, ch = bgra.shape + + assert ch == 4, 'ARGB image has 4 channels.' + + rgb = numpy.zeros( (row, col, 3), dtype='uint8' ) + # convert to rgb expected to generate the jpeg image + rgb[:,:,0] = bgra[:,:,2] + rgb[:,:,1] = bgra[:,:,1] + rgb[:,:,2] = bgra[:,:,0] + + return rgb + +def main(): + window_name = "Seek Thermal - Python OpenCV Sample" + cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) + fileName = "image" + counter = 100000 + capture = False + record = False + ts_first = 0 + ts_last = 0 + frame_count = 0 + + from PIL import Image + from pathlib import Path + for f in glob.glob(fileName + '*.jpg'): + os.remove(f) + + print("\nuser controls:") + print("c: capture") + print("r: record") + print("q: quit") + + # Create a context structure responsible for managing all connected USB cameras. + # Cameras with other IO types can be managed by using a bitwise or of the + # SeekCameraIOType enum cases. + with SeekCameraManager(SeekCameraIOType.USB) as manager: + # Start listening for events. + renderer = Renderer() + manager.register_event_callback(on_event, renderer) + + while True: + # Wait a maximum of 150ms for each frame to be received. + # A condition variable is used to synchronize the access to the renderer; + # it will be notified by the user defined frame available callback thread. + with renderer.frame_condition: + if renderer.frame_condition.wait(150.0 / 1000.0): + img = renderer.frame.data + + # Resize the rendering window. + if renderer.first_frame: + (height, width, _) = img.shape + cv2.resizeWindow(window_name, width * 2, height * 2) + renderer.first_frame = False + + # Render the image to the window. + cv2.imshow(window_name, img) + + # if capture or recording, convert the frame image + # to RGB and generate the file. + # Currently counter is a big number to allow easy ordering + # of frames when recording. + if capture or record: + rgbimg = bgra2rgb(img) + frame_count += 1 + im = Image.fromarray(rgbimg).convert('RGB') + jpgName = Path('.', fileName + str(counter)).with_suffix('.jpg') + im.save(jpgName) + counter += 1 + capture = False + if record: + ts_last = renderer.frame.header.timestamp_utc_ns + if ts_first == 0: + ts_first = renderer.frame.header.timestamp_utc_ns + # Process key events. + key = cv2.waitKey(1) + if key == ord("q"): + break + + if key == ord("c"): + capture = True + + if key == ord("r"): + if record == False: + record = True + renderer.camera.shutter_mode = SeekCameraShutterMode.MANUAL + print("\nRecording! Press 'r' to stop recording") + print("Note: shutter is disabled while recording...so keep the videos relatively short") + else: + # Stop the recording and squish all the jpeg files together + # and generate the .avi file. + record = False + renderer.camera.shutter_mode = SeekCameraShutterMode.AUTO + + time_s = (ts_last - ts_first)/1000000000 + + print("\nRecording stopped and video is in myVideo.avi") + img_array = [] + for filename in glob.glob('image*.jpg'): + img = cv2.imread(filename) + height, width, layers = img.shape + size = (width,height) + img_array.append(img) + out = cv2.VideoWriter('myVideo.avi', cv2.VideoWriter_fourcc(*'DIVX'), frame_count/time_s, size) + + frame_count = ts_first = ts_last = 0 + + for i in range(len(img_array)): + out.write(img_array[i]) + out.release() + + # Check if the window has been closed manually. + if not cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE): + break + + cv2.destroyWindow(window_name) + + +if __name__ == "__main__": + main() diff --git a/seekcamera/__init__.py b/seekcamera/__init__.py index 89d027d0..fc26ae68 100644 --- a/seekcamera/__init__.py +++ b/seekcamera/__init__.py @@ -21,6 +21,7 @@ SeekCameraAppResourcesRegion, SeekCameraColorPalette, SeekCameraColorPaletteData, + SeekCameraPipelineMode, SeekCameraAGCMode, SeekCameraLinearAGCLockMode, SeekCameraShutterMode, diff --git a/seekcamera/_clib.py b/seekcamera/_clib.py index 27c100d7..64710725 100644 --- a/seekcamera/_clib.py +++ b/seekcamera/_clib.py @@ -320,6 +320,17 @@ def assert_runtime_version_met(version, min_version, name): ctypes.c_int32, ctypes.POINTER(CSeekCameraColorPaletteDataEntry * 256), ] + + # seekcamera_get_pipeline_mode + _cdll.seekcamera_get_pipeline_mode.restype = ctypes.c_int32 + _cdll.seekcamera_get_pipeline_mode.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_pipeline_mode + _cdll.seekcamera_set_pipeline_mode.restype = ctypes.c_int32 + _cdll.seekcamera_set_pipeline_mode.argtypes = [ctypes.c_void_p, ctypes.c_int32] # seekcamera_get_agc_mode _cdll.seekcamera_get_agc_mode.restype = ctypes.c_int32 @@ -454,6 +465,76 @@ def assert_runtime_version_met(version, min_version, name): ctypes.c_void_p, ctypes.c_float, ] + + # seekcamera_get_histeq_agc_roi_left + _cdll.seekcamera_get_histeq_agc_roi_left.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_left.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_left + _cdll.seekcamera_set_histeq_agc_roi_left.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_left.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_top + _cdll.seekcamera_get_histeq_agc_roi_top.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_top.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_top + _cdll.seekcamera_set_histeq_agc_roi_top.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_top.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_width + _cdll.seekcamera_get_histeq_agc_roi_width.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_width.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_width + _cdll.seekcamera_set_histeq_agc_roi_width.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_width.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_height + _cdll.seekcamera_get_histeq_agc_roi_height.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_height.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int32), + ] + + # seekcamera_set_histeq_agc_roi_height + _cdll.seekcamera_set_histeq_agc_roi_height.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_height.argtypes = [ + ctypes.c_void_p, + ctypes.c_int32, + ] + + # seekcamera_get_histeq_agc_roi_enable + _cdll.seekcamera_get_histeq_agc_roi_enable.restype = ctypes.c_int32 + _cdll.seekcamera_get_histeq_agc_roi_enable.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_bool), + ] + + # seekcamera_set_histeq_agc_roi_enable + _cdll.seekcamera_set_histeq_agc_roi_enable.restype = ctypes.c_int32 + _cdll.seekcamera_set_histeq_agc_roi_enable.argtypes = [ + ctypes.c_void_p, + ctypes.c_bool, + ] # seekcamera_get_linear_agc_lock_mode _cdll.seekcamera_get_linear_agc_lock_mode.restype = ctypes.c_int32 @@ -473,28 +554,28 @@ def assert_runtime_version_met(version, min_version, name): _cdll.seekcamera_get_linear_agc_lock_min.restype = ctypes.c_int32 _cdll.seekcamera_get_linear_agc_lock_min.argtypes = [ ctypes.c_void_p, - ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_float), ] # seekcamera_set_linear_agc_lock_min _cdll.seekcamera_set_linear_agc_lock_min.restype = ctypes.c_int32 _cdll.seekcamera_set_linear_agc_lock_min.argtypes = [ ctypes.c_void_p, - ctypes.c_uint32, + ctypes.c_float, ] # seekcamera_get_linear_agc_lock_max _cdll.seekcamera_get_linear_agc_lock_max.restype = ctypes.c_int32 _cdll.seekcamera_get_linear_agc_lock_max.argtypes = [ ctypes.c_void_p, - ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_float), ] # seekcamera_set_linear_agc_lock_max _cdll.seekcamera_set_linear_agc_lock_max.restype = ctypes.c_int32 _cdll.seekcamera_set_linear_agc_lock_max.argtypes = [ ctypes.c_void_p, - ctypes.c_uint32, + ctypes.c_float, ] # seekcamera_get_shutter_mode @@ -687,11 +768,12 @@ class CSeekCameraFrameHeader(ctypes.Structure): ("histeq_agc_bin_width", ctypes.c_uint16), ("histeq_agc_gain_limit_factor", ctypes.c_float), ("histeq_agc_reserved", ctypes.c_uint8 * 64), - ("linear_agc_min", ctypes.c_uint32), - ("linear_agc_max", ctypes.c_uint32), + ("linear_agc_min", ctypes.c_float), + ("linear_agc_max", ctypes.c_float), ("linear_agc_reserved", ctypes.c_uint8 * 32), ("gradient_correction_filter_state", ctypes.c_uint8), ("flat_scene_correction_filter_state", ctypes.c_uint8), + ("sharpen_correction_filter_state", ctypes.c_uint8), ("reserved", ctypes.c_uint8 * 1798), ] @@ -1005,6 +1087,16 @@ def cseekcamera_set_color_palette_data(camera, palette, palette_data): ) +def cseekcamera_get_pipeline_mode(camera): + mode = ctypes.c_int32() + status = _cdll.seekcamera_get_pipeline_mode(camera.pointer, ctypes.byref(mode)) + return mode, status + + +def cseekcamera_set_pipeline_mode(camera, mode): + return _cdll.seekcamera_set_pipeline_mode(camera.pointer, ctypes.c_int32(mode)) + + def cseekcamera_get_agc_mode(camera): mode = ctypes.c_int32() status = _cdll.seekcamera_get_agc_mode(camera.pointer, ctypes.byref(mode)) @@ -1147,6 +1239,75 @@ def cseekcamera_set_histeq_agc_trim_right(camera, trim): camera.pointer, ctypes.c_float(trim) ) +def cseekcamera_set_histeq_agc_roi_left(camera, left): + return _cdll.seekcamera_set_histeq_agc_roi_left( + camera.pointer, ctypes.c_int32(left) + ) + +def cseekcamera_get_histeq_agc_roi_left(camera): + left = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_left( + camera.pointer, ctypes.byref(left) + ) + + return left, status + + +def cseekcamera_set_histeq_agc_roi_top(camera, top): + return _cdll.seekcamera_set_histeq_agc_roi_top( + camera.pointer, ctypes.c_int32(top) + ) + +def cseekcamera_get_histeq_agc_roi_top(camera): + top = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_top( + camera.pointer, ctypes.byref(top) + ) + + return top, status + + +def cseekcamera_set_histeq_agc_roi_width(camera, width): + return _cdll.seekcamera_set_histeq_agc_roi_width( + camera.pointer, ctypes.c_int32(width) + ) + +def cseekcamera_get_histeq_agc_roi_width(camera): + width = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_width( + camera.pointer, ctypes.byref(width) + ) + + return width, status + + +def cseekcamera_set_histeq_agc_roi_height(camera, height): + return _cdll.seekcamera_set_histeq_agc_roi_height( + camera.pointer, ctypes.c_int32(height) + ) + +def cseekcamera_get_histeq_agc_roi_height(camera): + height = ctypes.c_int32() + status = _cdll.seekcamera_get_histeq_agc_roi_height( + camera.pointer, ctypes.byref(height) + ) + + return height, status + + +def cseekcamera_set_histeq_agc_roi_enable(camera, enable): + return _cdll.seekcamera_set_histeq_agc_roi_enable( + camera.pointer, ctypes.c_bool(enable) + ) + +def cseekcamera_get_histeq_agc_roi_enable(camera): + enable = ctypes.c_bool() + status = _cdll.seekcamera_get_histeq_agc_roi_enable( + camera.pointer, ctypes.byref(enable) + ) + + return enable, status + def cseekcamera_get_linear_agc_lock_mode(camera): mode = ctypes.c_int32() @@ -1164,7 +1325,7 @@ def cseekcamera_set_linear_agc_lock_mode(camera, mode): def cseekcamera_get_linear_agc_lock_min(camera): - lock_min = ctypes.c_uint32() + lock_min = ctypes.c_float() status = _cdll.seekcamera_get_linear_agc_lock_min( camera.pointer, ctypes.byref(lock_min) ) @@ -1174,12 +1335,12 @@ def cseekcamera_get_linear_agc_lock_min(camera): def cseekcamera_set_linear_agc_lock_min(camera, lock_min): return _cdll.seekcamera_set_linear_agc_lock_min( - camera.pointer, ctypes.c_uint32(lock_min) + camera.pointer, ctypes.c_float(lock_min) ) def cseekcamera_get_linear_agc_lock_max(camera): - lock_max = ctypes.c_uint32() + lock_max = ctypes.c_float() status = _cdll.seekcamera_get_linear_agc_lock_max( camera.pointer, ctypes.byref(lock_max) ) @@ -1189,7 +1350,7 @@ def cseekcamera_get_linear_agc_lock_max(camera): def cseekcamera_set_linear_agc_lock_max(camera, lock_max): return _cdll.seekcamera_set_linear_agc_lock_max( - camera.pointer, ctypes.c_uint32(lock_max) + camera.pointer, ctypes.c_float(lock_max) ) @@ -1277,6 +1438,21 @@ def cseekcamera_set_flat_scene_correction_filter_enable(camera, enable): ) +def cseekcamera_get_sharpen_correction_filter_enable(camera): + enable = ctypes.c_bool() + status = _cdll.seekcamera_get_sharpen_correction_filter_enable( + camera.pointer, ctypes.byref(enable) + ) + + return enable, status + + +def cseekcamera_set_sharpen_correction_filter_enable(camera, enable): + return _cdll.seekcamera_set_sharpen_correction_filter_enable( + camera.pointer, ctypes.c_bool(enable) + ) + + def cseekcamera_set_filter_state(camera, filter_type, filter_state): return _cdll.seekcamera_set_filter_state( camera.pointer, ctypes.c_int32(filter_type), ctypes.c_int32(filter_state) diff --git a/seekcamera/camera.py b/seekcamera/camera.py index aac02066..13baf340 100644 --- a/seekcamera/camera.py +++ b/seekcamera/camera.py @@ -352,6 +352,19 @@ def __repr__(self): return "SeekCameraLinearAGCLockMode({})".format(self.value) +class SeekCameraPipelineMode(IntEnum): + + LITE = 0 + LEGACY = 1 + SEEKVISION = 2 + + + def __str__(self): + return self.name + + def __repr__(self): + return "SeekCameraPipelineMode({})".format(self.value) + class SeekCameraShutterMode(IntEnum): """Types of shutter modes. @@ -491,10 +504,13 @@ class SeekCameraFilter(IntEnum): FLAT_SCENE_CORRECTION: int Filter responsible for correcting non-uniformities on all data pipelines. It is stored explicitly by the user apriori. + SHARPEN_CORRECTION: int + Filter responsible for sharpening. """ GRADIENT_CORRECTION = 0 FLAT_SCENE_CORRECTION = 1 + SHARPEN_CORRECTION = 2 def __str__(self): return self.name @@ -757,11 +773,21 @@ class SeekCamera(object): Gets/sets the histogram left trim percentage used for HistEQ AGC. histeq_agc_trim_right: float Gets/sets the histogram right trim percentage used for HistEQ AGC. + histeq_agc_roi_left: int + Gets/sets the left roi used for HistEQ AGC. + histeq_agc_roi_top: int + Gets/sets the top roi used for HistEQ AGC. + histeq_agc_roi_width: int + Gets/sets the width roi used for HistEQ AGC. + histeq_agc_roi_height: int + Gets/sets the height roi used for HistEQ AGC. + histeq_agc_roi_enable: int + Gets/sets the enable roi used for HistEQ AGC. linear_agc_lock_mode: SeekCameraLinearAGCLock Mode Gets/sets the lock mode used for Linear AGC. - linear_agc_lock_min: int + linear_agc_lock_min: float Gets/sets the minimum lock value used for Linear AGC. - linear_agc_lock_max: int + linear_agc_lock_max: float Gets/sets the maximum lock value used for Linear AGC. shutter_mode: SeekCameraShutterMode Gets/sets the shutter mode. @@ -1365,6 +1391,38 @@ def color_palette(self, palette): if is_error(status): raise error_from_status(status) + @property + def pipeline_mode(self): + """Gets/sets the active pipeline mode. + + Settings are refreshed between frames. This method can only be performed after + a capture session has started. + + Returns + ------- + SeekCameraPipelineMode + Active pipeline mode used by the camera. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + mode, status = _clib.cseekcamera_get_pipeline_mode(self._camera) + if is_error(status): + raise error_from_status(status) + + return SeekCameraPipelineMode(mode.value) + + @pipeline_mode.setter + def pipeline_mode(self, mode): + if not isinstance(mode, SeekCameraPipelineMode): + raise SeekCameraInvalidParameterError + + status = _clib.cseekcamera_set_pipeline_mode(self._camera, mode) + if is_error(status): + raise error_from_status(status) + @property def agc_mode(self): """Gets/sets the active AGC mode. @@ -1687,6 +1745,156 @@ def histeq_agc_trim_right(self, trim_left): if is_error(status): raise error_from_status(status) + @property + def histeq_agc_roi_left(self): + """Gets/sets the left ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Left ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + left, status = _clib.cseekcamera_get_histeq_agc_roi_left(self._camera) + + if is_error(status): + raise error_from_status(status) + + return left.value + + @histeq_agc_roi_left.setter + def histeq_agc_roi_left(self, left): + status = _clib.cseekcamera_set_histeq_agc_roi_left(self._camera, left) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_top(self): + """Gets/sets the top ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Top ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + top, status = _clib.cseekcamera_get_histeq_agc_roi_top(self._camera) + + if is_error(status): + raise error_from_status(status) + + return top.value + + @histeq_agc_roi_top.setter + def histeq_agc_roi_top(self, top): + status = _clib.cseekcamera_set_histeq_agc_roi_top(self._camera, top) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_width(self): + """Gets/sets the width ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Width ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + width, status = _clib.cseekcamera_get_histeq_agc_roi_width(self._camera) + + if is_error(status): + raise error_from_status(status) + + return width.value + + @histeq_agc_roi_width.setter + def histeq_agc_roi_width(self, width): + status = _clib.cseekcamera_set_histeq_agc_roi_width(self._camera, width) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_height(self): + """Gets/sets the height ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Height ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + height, status = _clib.cseekcamera_get_histeq_agc_roi_height(self._camera) + + if is_error(status): + raise error_from_status(status) + + return height.value + + @histeq_agc_roi_height.setter + def histeq_agc_roi_height(self, height): + status = _clib.cseekcamera_set_histeq_agc_roi_height(self._camera, height) + + if is_error(status): + raise error_from_status(status) + + @property + def histeq_agc_roi_enable(self): + """Gets/sets the enable ROI used for HistEQ AGC. + + Settings are refreshed between frames. + + Returns + ------- + int + Enable ROI used for HistEQ AGC. + + Raises + ------ + SeekCameraError + If an error occurs. + """ + enable, status = _clib.cseekcamera_get_histeq_agc_roi_enable(self._camera) + + if is_error(status): + raise error_from_status(status) + + return enable.value + + @histeq_agc_roi_enable.setter + def histeq_agc_roi_enable(self, enable): + status = _clib.cseekcamera_set_histeq_agc_roi_enable(self._camera, enable) + + if is_error(status): + raise error_from_status(status) + @property def linear_agc_lock_mode(self): """Gets/sets the lock mode used for Linear AGC. @@ -2411,15 +2619,17 @@ class SeekCameraFrameHeader(object): Gets the coordinates and value of the spot thermography pixel. agc_mode: SeekCameraAGCMode AGC mode used to process the image. + pipeline_mode: SeekCameraPiplineMode + Pipeline mode used to process the image. histeq_agc_num_bins: int Number of bins in the HistEQ AGC histogram. histeq_agc_bin_width: int Number of counts per bin in the HistEQ AGC histogram. histeq_agc_gain_limit_factor: float Multiplier of the HistEQ gain limit. - linear_agc_min: int + linear_agc_min: float Minimum count value in the frame when using Linear AGC. - linear_agc_max: int + linear_agc_max: float Maximum count value in the frame when using Linear AGC. gradient_correction_filter_state : SeekCameraFilterState State of the gradient correction filter. @@ -2772,7 +2982,7 @@ def linear_agc_min(self): Returns ------- - int + float Minimum count value in the frame when using Linear AGC. """ return self._header.linear_agc_min @@ -2783,7 +2993,7 @@ def linear_agc_max(self): Returns ------- - int + float Minimum count value in the frame when using Linear AGC. """ return self._header.linear_agc_max @@ -2809,6 +3019,17 @@ def flat_scene_correction_filter_state(self): State of the flat scene correction filter. """ return SeekCameraFilterState(self._header.flat_scene_correction_filter_state) + + @property + def sharpen_correction_filter_state(self): + """Gets the state of the sharpen correction filter. + + Returns + ------- + SeekCameraFilterState + State of the sharpen correction filter. + """ + return SeekCameraFilterState(self._header.sharpen_correction_filter_state) class SeekFrame: