From b62451d80fba976cb024c71d5ae7f2a778b4ce4b Mon Sep 17 00:00:00 2001 From: yair Date: Sun, 16 Nov 2025 04:58:17 +0200 Subject: [PATCH] Add IS_GET_GAINBOOST support to IDS uEye plugin - Add gain-boost property to gstidsueyesrc (boolean) - Implement is_SetGainBoost() API call in property setter and framerate/exposure function - Add UDP control commands SET_GAIN_BOOST and GET_GAIN_BOOST - Add --gain-boost command-line flag to launch-ids.py - Update camera_control.py with get-gain-boost and set-gain-boost commands - Change parameter defaults to None (exposure, framerate, gain) to respect INI file defaults - Only set properties when explicitly provided by user - INI file is source of truth --- scripts/camera_control.py | 23 ++++++- scripts/launch-ids.py | 121 ++++++++++++++++++++++++++++-------- sys/idsueye/gstidsueyesrc.c | 32 +++++++++- sys/idsueye/gstidsueyesrc.h | 1 + 4 files changed, 149 insertions(+), 28 deletions(-) diff --git a/scripts/camera_control.py b/scripts/camera_control.py index 44e8b9a..62f8801 100644 --- a/scripts/camera_control.py +++ b/scripts/camera_control.py @@ -19,6 +19,8 @@ Usage: uv run scripts/camera_control.py set-auto-exposure 1 # Enable auto-exposure uv run scripts/camera_control.py get-auto-gain # Get auto-gain status uv run scripts/camera_control.py set-auto-gain 1 # Enable auto-gain + uv run scripts/camera_control.py get-gain-boost # Get gain boost status + uv run scripts/camera_control.py set-gain-boost 1 # Enable gain boost uv run scripts/camera_control.py status # Get pipeline status This script provides both individual control commands and full test suite functionality @@ -190,6 +192,8 @@ Examples: %(prog)s set-auto-exposure 1 # Enable auto-exposure %(prog)s get-auto-gain # Get auto-gain status %(prog)s set-auto-gain 1 # Enable auto-gain + %(prog)s get-gain-boost # Get gain boost status + %(prog)s set-gain-boost 1 # Enable gain boost %(prog)s status # Get pipeline status """, formatter_class=argparse.RawDescriptionHelpFormatter @@ -200,7 +204,8 @@ Examples: choices=['test', 'get-exposure', 'get-exposure-range', 'set-exposure', 'get-framerate', 'set-framerate', 'get-gain', 'set-gain', 'get-auto-exposure', 'set-auto-exposure', - 'get-auto-gain', 'set-auto-gain', 'status'], + 'get-auto-gain', 'set-auto-gain', + 'get-gain-boost', 'set-gain-boost', 'status'], help='Command to execute') parser.add_argument('value', @@ -304,6 +309,22 @@ Examples: success = simple_command(f"SET_AUTO_GAIN {ag_value}", f"{'Enabling' if ag_value else 'Disabling'} auto-gain") + elif args.command == 'get-gain-boost': + success = simple_command("GET_GAIN_BOOST", "Getting gain boost status") + + elif args.command == 'set-gain-boost': + if args.value is None: + print("Error: set-gain-boost requires a value (0=off, 1=on)") + parser.print_help() + sys.exit(1) + # Convert to int + gb_value = int(args.value) + if gb_value not in [0, 1]: + print("Error: Gain boost must be 0 (off) or 1 (on)") + sys.exit(1) + success = simple_command(f"SET_GAIN_BOOST {gb_value}", + f"{'Enabling' if gb_value else 'Disabling'} gain boost") + elif args.command == 'status': success = simple_command("STATUS", "Getting pipeline status") diff --git a/scripts/launch-ids.py b/scripts/launch-ids.py index 76fbc47..3209fc4 100644 --- a/scripts/launch-ids.py +++ b/scripts/launch-ids.py @@ -30,6 +30,7 @@ # - Dynamic framerate control (1-20000 fps, default: 750fps) # - Dynamic gain control (0-100, default: 0) # - Auto-gain mode support (--auto-gain flag) +# - Gain boost support (--gain-boost flag) # - Dynamic camera ID selection (0-254, default: 0 for first found) # - Dynamic device ID selection (0-254, system enumeration ID) # - Configurable video cropping (default: crop 3 pixels from bottom) @@ -52,6 +53,8 @@ # GET_AUTO_EXPOSURE - Get auto-exposure status # SET_AUTO_GAIN <0|1> - Enable (1) or disable (0) auto-gain # GET_AUTO_GAIN - Get auto-gain status +# SET_GAIN_BOOST <0|1> - Enable (1) or disable (0) hardware gain boost +# GET_GAIN_BOOST - Get gain boost status # STATUS - Get pipeline status and current settings # # Example Control Usage: @@ -143,7 +146,8 @@ class ControlServer: print(" Commands: SET_EXPOSURE , GET_EXPOSURE, SET_FRAMERATE , GET_FRAMERATE,") print(" SET_CAMERA_ID , GET_CAMERA_ID, SET_DEVICE_ID , GET_DEVICE_ID,") print(" SET_GAIN , GET_GAIN, SET_AUTO_EXPOSURE <0|1>, GET_AUTO_EXPOSURE,") - print(" SET_AUTO_GAIN <0|1>, GET_AUTO_GAIN, STATUS") + print(" SET_AUTO_GAIN <0|1>, GET_AUTO_GAIN, SET_GAIN_BOOST <0|1>, GET_GAIN_BOOST,") + print(" STATUS") while self.running: try: @@ -211,6 +215,10 @@ class ControlServer: return self.handle_set_auto_gain(parts) elif cmd == "GET_AUTO_GAIN": return self.handle_get_auto_gain() + elif cmd == "SET_GAIN_BOOST": + return self.handle_set_gain_boost(parts) + elif cmd == "GET_GAIN_BOOST": + return self.handle_get_gain_boost() elif cmd == "STATUS": return self.handle_status() else: @@ -420,6 +428,33 @@ class ControlServer: except Exception as e: return f"ERROR: {str(e)}" + def handle_set_gain_boost(self, parts): + """Handle SET_GAIN_BOOST command""" + if len(parts) != 2: + return "ERROR INVALID_SYNTAX: Usage: SET_GAIN_BOOST <0|1>" + + try: + value = int(parts[1]) + if value not in [0, 1]: + return "ERROR OUT_OF_RANGE: Gain boost must be 0 (off) or 1 (on)" + + self.src.set_property("gain-boost", bool(value)) + actual = self.src.get_property("gain-boost") + return f"OK {int(actual)}" + + except ValueError: + return "ERROR INVALID_SYNTAX: Gain boost must be 0 or 1" + except Exception as e: + return f"ERROR: {str(e)}" + + def handle_get_gain_boost(self): + """Handle GET_GAIN_BOOST command""" + try: + value = self.src.get_property("gain-boost") + return f"OK {int(value)}" + except Exception as e: + return f"ERROR: {str(e)}" + def handle_status(self): """Handle STATUS command""" try: @@ -430,6 +465,7 @@ class ControlServer: gain = self.src.get_property("gain") auto_exposure = int(self.src.get_property("auto-exposure")) auto_gain = int(self.src.get_property("auto-gain")) + gain_boost = int(self.src.get_property("gain-boost")) # Get pipeline state state = "UNKNOWN" @@ -437,7 +473,7 @@ class ControlServer: _, current_state, _ = self.pipeline.get_state(0) state = current_state.value_nick.upper() - return f"OK exposure={exposure} framerate={framerate} camera_id={camera_id} device_id={device_id} gain={gain} auto_exposure={auto_exposure} auto_gain={auto_gain} state={state}" + return f"OK exposure={exposure} framerate={framerate} camera_id={camera_id} device_id={device_id} gain={gain} auto_exposure={auto_exposure} auto_gain={auto_gain} gain_boost={gain_boost} state={state}" except Exception as e: return f"ERROR: {str(e)}" @@ -478,16 +514,16 @@ Examples: camera_group.add_argument( '--exposure', '-e', type=float, - default=10.0, + default=None, metavar='MS', - help='Camera exposure time in milliseconds (0.015-30000)' + help='Camera exposure time in milliseconds (0.015-30000, default from INI file)' ) camera_group.add_argument( '--framerate', '--fps', '-f', type=float, - default=750.0, + default=None, metavar='HZ', - help='Camera framerate in Hz (1.0-20200.0)' + help='Camera framerate in Hz (1.0-20200.0, default from INI file)' ) camera_group.add_argument( '--camera-id', @@ -506,15 +542,25 @@ Examples: camera_group.add_argument( '--gain', '-g', type=int, - default=0, + default=None, metavar='GAIN', - help='Master gain (0-100, 0 for auto/default)' + help='Master gain (0-100, default from INI file)' ) camera_group.add_argument( '--auto-exposure', action='store_true', help='Enable automatic exposure control' ) + camera_group.add_argument( + '--auto-gain', + action='store_true', + help='Enable automatic gain control' + ) + camera_group.add_argument( + '--gain-boost', + action='store_true', + help='Enable hardware gain boost' + ) # Video processing video_group = parser.add_argument_group('Video Processing') @@ -590,11 +636,11 @@ Examples: args = parser.parse_args() - # Validation - if args.exposure < 1.0 or args.exposure > 30000: - parser.error(f"Exposure must be between 1.0 and 30000 ms, got {args.exposure}") + # Validation - only validate if provided + if args.exposure is not None and (args.exposure < 0.015 or args.exposure > 30000): + parser.error(f"Exposure must be between 0.015 and 30000 ms, got {args.exposure}") - if args.framerate < 1.0 or args.framerate > 20200.0: + if args.framerate is not None and (args.framerate < 1.0 or args.framerate > 20200.0): parser.error(f"Framerate must be between 1.0 and 20200.0 Hz, got {args.framerate}") if args.camera_id < 0 or args.camera_id > 254: @@ -603,7 +649,7 @@ Examples: if args.device_id < 0 or args.device_id > 254: parser.error(f"Device ID must be between 0 and 254, got {args.device_id}") - if args.gain < 0 or args.gain > 100: + if args.gain is not None and (args.gain < 0 or args.gain > 100): parser.error(f"Gain must be between 0 and 100, got {args.gain}") if args.port < 1 or args.port > 65535: @@ -654,21 +700,32 @@ pipeline = Gst.Pipeline() src = Gst.ElementFactory.make("idsueyesrc", "src") src.set_property("config-file", args.config) -# Exposure in milliseconds -src.set_property("exposure", args.exposure) +# Only set properties that were explicitly provided via command line +# The INI config file provides all defaults -# Frame rate -src.set_property("framerate", args.framerate) - -# Camera ID and Device ID +# Camera ID and Device ID - always set as they determine which camera to use src.set_property("camera-id", args.camera_id) src.set_property("device-id", args.device_id) -# Gain (0 for auto/default) -src.set_property("gain", args.gain) +# Only set if explicitly provided by user +if args.exposure is not None: + src.set_property("exposure", args.exposure) -# Auto-exposure -src.set_property("auto-exposure", args.auto_exposure) +if args.framerate is not None: + src.set_property("framerate", args.framerate) + +if args.gain is not None: + src.set_property("gain", args.gain) + +# Boolean flags - only set if explicitly enabled +if args.auto_exposure: + src.set_property("auto-exposure", True) + +if args.auto_gain: + src.set_property("auto-gain", True) + +if args.gain_boost: + src.set_property("gain-boost", True) # Video crop to remove bottom pixels (if enabled) elements_to_link = [src] @@ -811,9 +868,21 @@ if not args.quiet: print(f"Camera config: {args.config}") print(f"Camera ID: {args.camera_id}") print(f"Device ID: {args.device_id}") - print(f"Exposure: {args.exposure} ms") - print(f"Framerate: {args.framerate} Hz") - print(f"Gain: {args.gain} (0=auto)") + if args.exposure is not None: + print(f"Exposure: {args.exposure} ms") + else: + print(f"Exposure: (from INI file)") + if args.framerate is not None: + print(f"Framerate: {args.framerate} Hz") + else: + print(f"Framerate: (from INI file)") + if args.gain is not None: + print(f"Gain: {args.gain}") + else: + print(f"Gain: (from INI file)") + print(f"Auto-exposure: {'enabled' if args.auto_exposure else '(from INI file)'}") + print(f"Auto-gain: {'enabled' if args.auto_gain else '(from INI file)'}") + print(f"Gain boost: {'enabled' if args.gain_boost else '(from INI file)'}") if args.crop_bottom > 0: print(f"Crop bottom: {args.crop_bottom} pixels") else: diff --git a/sys/idsueye/gstidsueyesrc.c b/sys/idsueye/gstidsueyesrc.c index a3a5b72..e9b43c9 100644 --- a/sys/idsueye/gstidsueyesrc.c +++ b/sys/idsueye/gstidsueyesrc.c @@ -76,7 +76,8 @@ enum PROP_FRAMERATE, PROP_GAIN, PROP_AUTO_EXPOSURE, - PROP_AUTO_GAIN + PROP_AUTO_GAIN, + PROP_GAIN_BOOST }; #define DEFAULT_PROP_CAMERA_ID 0 @@ -89,6 +90,7 @@ enum #define DEFAULT_PROP_GAIN 0 #define DEFAULT_PROP_AUTO_EXPOSURE FALSE #define DEFAULT_PROP_AUTO_GAIN FALSE +#define DEFAULT_PROP_GAIN_BOOST FALSE /* pad templates */ @@ -181,6 +183,10 @@ gst_idsueyesrc_class_init (GstIdsueyeSrcClass * klass) g_param_spec_boolean ("auto-gain", "Auto Gain", "Enable automatic gain control", DEFAULT_PROP_AUTO_GAIN, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (gobject_class, PROP_GAIN_BOOST, + g_param_spec_boolean ("gain-boost", "Gain Boost", + "Enable hardware gain boost", DEFAULT_PROP_GAIN_BOOST, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); } static void @@ -218,6 +224,7 @@ gst_idsueyesrc_init (GstIdsueyeSrc * src) src->gain = DEFAULT_PROP_GAIN; src->auto_exposure = DEFAULT_PROP_AUTO_EXPOSURE; src->auto_gain = DEFAULT_PROP_AUTO_GAIN; + src->gain_boost = DEFAULT_PROP_GAIN_BOOST; src->stop_requested = FALSE; src->caps = NULL; @@ -285,6 +292,16 @@ gst_idsueyesrc_set_property (GObject * object, guint property_id, gst_idsueyesrc_set_framerate_exposure (src); } break; + case PROP_GAIN_BOOST: + src->gain_boost = g_value_get_boolean (value); + if (src->is_started) { + INT ret = is_SetGainBoost (src->hCam, src->gain_boost ? IS_SET_GAINBOOST_ON : IS_SET_GAINBOOST_OFF); + if (ret != IS_SUCCESS) { + GST_WARNING_OBJECT (src, "Failed to set gain boost to %d (error %d)", + src->gain_boost, ret); + } + } + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -331,6 +348,9 @@ gst_idsueyesrc_get_property (GObject * object, guint property_id, case PROP_AUTO_GAIN: g_value_set_boolean (value, src->auto_gain); break; + case PROP_GAIN_BOOST: + g_value_set_boolean (value, src->gain_boost); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -1060,6 +1080,16 @@ gst_idsueyesrc_set_framerate_exposure (GstIdsueyeSrc * src) } } + /* Handle gain boost */ + ret = is_SetGainBoost (src->hCam, src->gain_boost ? IS_SET_GAINBOOST_ON : IS_SET_GAINBOOST_OFF); + if (ret != IS_SUCCESS) { + GST_WARNING_OBJECT (src, "Failed to set gain boost to %d (error %d)", + src->gain_boost, ret); + success = FALSE; + } else { + GST_DEBUG_OBJECT (src, "Gain boost %s", src->gain_boost ? "enabled" : "disabled"); + } + return success; } diff --git a/sys/idsueye/gstidsueyesrc.h b/sys/idsueye/gstidsueyesrc.h index abb108f..eefe8d3 100644 --- a/sys/idsueye/gstidsueyesrc.h +++ b/sys/idsueye/gstidsueyesrc.h @@ -60,6 +60,7 @@ struct _GstIdsueyeSrc gint gain; gboolean auto_exposure; gboolean auto_gain; + gboolean gain_boost; GstClockTime acq_start_time; guint32 last_frame_count;