From 79533d34e6fbdaae3285daa85ca64b5cb9d84251 Mon Sep 17 00:00:00 2001 From: Alexander Shaliapin Date: Mon, 13 May 2024 19:12:31 +0200 Subject: [PATCH] camera configuration wip --- help/Explanation Overview.vl | 225 ++++++++++++++++++++++- help/HowTo List connected IDS cameras.vl | 138 -------------- help/camera_config.ini | 210 +++++++++++++++++++++ src/Acquisition.cs | 128 ++++++++++++- src/FileConfiguration.cs | 42 +++++ src/IConfiguration.cs | 9 + src/InMemoryConfiguration.cs | 171 +++++++++++++++++ src/Properties/launchSettings.json | 2 +- src/PropertyInfo.cs | 19 ++ src/SaveConfigToFile.cs | 48 +++++ src/VideoIn.cs | 20 +- 11 files changed, 853 insertions(+), 159 deletions(-) delete mode 100644 help/HowTo List connected IDS cameras.vl create mode 100644 help/camera_config.ini create mode 100644 src/FileConfiguration.cs create mode 100644 src/IConfiguration.cs create mode 100644 src/InMemoryConfiguration.cs create mode 100644 src/PropertyInfo.cs create mode 100644 src/SaveConfigToFile.cs diff --git a/help/Explanation Overview.vl b/help/Explanation Overview.vl index 550588f..44b9dfa 100644 --- a/help/Explanation Overview.vl +++ b/help/Explanation Overview.vl @@ -1,6 +1,6 @@  - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/help/camera_config.ini b/help/camera_config.ini new file mode 100644 index 0000000..367ea26 --- /dev/null +++ b/help/camera_config.ini @@ -0,0 +1,210 @@ +[Versions] +uEye_api_64.dll=4.96.3985 +uEye_usb_64.sys=4.96.1328 + + +[Sensor] +Sensor=UI222xSE-C +Sensor bit depth=0 +Sensor source gain=0 +FPN correction mode=0 +Black reference mode=0 +Sensor digital gain=0 + + +[Image size] +Start X=0 +Start Y=0 +Start X absolute=0 +Start Y absolute=0 +Width=768 +Height=576 +Binning=0 +Subsampling=0 + + +[Scaler] +Mode=0 +Factor=0.000000 + + +[Multi AOI] +Enabled=0 +Mode=0 +x1=0 +x2=0 +x3=0 +x4=0 +y1=0 +y2=0 +y3=0 +y4=0 + + +[Shutter] +Mode=2 +Linescan number=0 + + +[Log Mode] +Mode=3 +Manual value=0 +Manual gain=0 + + +[Timing] +Pixelclock=20 +Extended pixelclock range=0 +Framerate=30.009123 +Exposure=33.274100 +Long exposure=0 +Dual exposure ratio=0 + + +[Selected Converter] +IS_SET_CM_RGB32=2 +IS_SET_CM_RGB24=2 +IS_SET_CM_RGB16=2 +IS_SET_CM_RGB15=2 +IS_SET_CM_Y8=2 +IS_SET_CM_RGB8=2 +IS_SET_CM_BAYER=8 +IS_SET_CM_UYVY=2 +IS_SET_CM_UYVY_MONO=2 +IS_SET_CM_UYVY_BAYER=2 +IS_CM_CBYCRY_PACKED=0 +IS_SET_CM_RGBY=8 +IS_SET_CM_RGB30=8 +IS_SET_CM_Y12=8 +IS_SET_CM_BAYER12=8 +IS_SET_CM_Y16=8 +IS_SET_CM_BAYER16=8 +IS_CM_BGR12_UNPACKED=8 +IS_CM_BGRA12_UNPACKED=8 +IS_CM_JPEG=0 +IS_CM_SENSOR_RAW10=8 +IS_CM_MONO10=8 +IS_CM_BGR10_UNPACKED=8 +IS_CM_RGBA8_PACKED=2 +IS_CM_RGB8_PACKED=2 +IS_CM_RGBY8_PACKED=8 +IS_CM_RGB10V2_PACKED=8 +IS_CM_RGB12_UNPACKED=8 +IS_CM_RGBA12_UNPACKED=8 +IS_CM_RGB10_UNPACKED=8 +IS_CM_RGB8_PLANAR=2 + + +[Parameters] +Colormode=11 +Gamma=1.000000 +Hardware Gamma=0 +Blacklevel Mode=1 +Blacklevel Offset=0 +Hotpixel Mode=2 +Hotpixel Threshold=0 +Sensor Hotpixel=0 +Adaptive hotpixel correction enable=0 +Adaptive hotpixel correction mode=0 +Adaptive hotpixel correction sensitivity=3 +GlobalShutter=0 +AllowRawWithLut=0 + + +[Gain] +Master=0 +Red=35 +Green=0 +Blue=25 +GainBoost=0 + + +[Processing] +EdgeEnhancementFactor=0 +RopEffect=0 +Whitebalance=0 +Whitebalance Red=1.000000 +Whitebalance Green=1.000000 +Whitebalance Blue=1.000000 +Color correction=0 +Color_correction_factor=1.000000 +Color_correction_satU=100 +Color_correction_satV=100 +Bayer Conversion=1 +JpegCompression=0 +NoiseMode=0 +ImageEffect=0 +LscModel=0 +WideDynamicRange=0 + + +[Auto features] +Auto Framerate control=0 +Brightness exposure control=0 +Brightness gain control=0 +Auto Framerate Sensor control=0 +Brightness exposure Sensor control=0 +Brightness gain Sensor control=0 +Brightness exposure Sensor control photometry=0 +Brightness gain Sensor control photometry=0 +Brightness control once=0 +Brightness reference=128 +Brightness speed=50 +Brightness max gain=100 +Brightness max exposure=33.274100 +Brightness Aoi Left=0 +Brightness Aoi Top=0 +Brightness Aoi Width=768 +Brightness Aoi Height=576 +Brightness Hysteresis=2 +AutoImageControlMode=2 +AutoImageControlPeakWhiteChannel=0 +AutoImageControlExposureMinimum=0.000000 +AutoImageControlPeakWhiteChannelMode=0 +AutoImageControlPeakWhiteGranularity=0 +Auto WB control=0 +Auto WB type=2 +Auto WB RGB color model=1 +Auto WB RGB color temperature=5000 +Auto WB offsetR=0 +Auto WB offsetB=0 +Auto WB gainMin=0 +Auto WB gainMax=100 +Auto WB speed=50 +Auto WB Aoi Left=0 +Auto WB Aoi Top=0 +Auto WB Aoi Width=768 +Auto WB Aoi Height=576 +Auto WB Once=0 +Auto WB Hysteresis=2 +Brightness Skip Frames Trigger Mode=4 +Brightness Skip Frames Freerun Mode=4 +Auto WB Skip Frames Trigger Mode=4 +Auto WB Skip Frames Freerun Mode=4 + + +[Trigger and Flash] +Trigger mode=0 +Trigger timeout=0 +Trigger delay=0 +Trigger debounce mode=0 +Trigger debounce delay time=0 +Trigger burst size=1 +Trigger prescaler frame=1 +Trigger prescaler line=1 +Trigger input=1 +Flash strobe=0 +Flash delay=0 +Flash duration=40 +Flash auto freerun=0 +PWM mode=0 +PWM frequency=20000000 +PWM dutycycle=20000000 +GPIO state=0 +GPIO direction=0 +GPIO1 Config=0 +GPIO2 Config=0 + + +[Memory] +Camera memory mode=0 diff --git a/src/Acquisition.cs b/src/Acquisition.cs index 847b0e6..e58de52 100644 --- a/src/Acquisition.cs +++ b/src/Acquisition.cs @@ -1,7 +1,10 @@ using CommunityToolkit.HighPerformance; using Microsoft.Extensions.Logging; using peak.core; +using peak.core.nodes; using peak.ipl; +using System.Net.WebSockets; +using System.Text; using VL.Lib.Basics.Resources; using VL.Lib.Basics.Video; @@ -9,7 +12,7 @@ namespace VL.Devices.IDS { internal class Acquisition : IVideoPlayer { - public static Acquisition? Start(VideoIn videoIn, DeviceDescriptor deviceDescriptor, ILogger logger, Int2 resolution, int fps) + public static Acquisition? Start(VideoIn videoIn, DeviceDescriptor deviceDescriptor, ILogger logger, Int2 resolution, int fps, IConfiguration? configuration) { logger.Log(LogLevel.Information, "Starting image acquisition on {device}", deviceDescriptor.DisplayName()); @@ -73,7 +76,7 @@ internal class Acquisition : IVideoPlayer nodeMapRemoteDevice.FindNode("Width").SetValue(Math.Max(Math.Min(maxWidth, resolution.X), minWidth)); nodeMapRemoteDevice.FindNode("Height").SetValue(Math.Max(Math.Min(maxHeight, resolution.Y), minHeight)); nodeMapRemoteDevice.FindNode("AcquisitionFrameRate").SetValue(Math.Max(Math.Min(fps, maxFPS), minFPS)); - + // Get the minimum number of buffers that must be announced var bufferCountMax = dataStream.NumBuffersAnnouncedMinRequired(); @@ -86,27 +89,127 @@ internal class Acquisition : IVideoPlayer } // Lock critical features to prevent them from changing during acquisition - nodeMapRemoteDevice.FindNode("TLParamsLocked").SetValue(1); + nodeMapRemoteDevice.FindNode("TLParamsLocked").SetValue(1); + + //apply static parameters + configuration?.Configure(nodeMapRemoteDevice); + + //collect available properties + var spb = new SpreadBuilder(); + CollectPropertiesInfos(spb, nodeMapRemoteDevice); + videoIn.PropertyInfos = spb.ToSpread(); // Start the acquisition engine in the transport layer dataStream.StartAcquisition(); // Start the acquisition in the Remote Device - nodeMapRemoteDevice.FindNode("AcquisitionStart").Execute(); - nodeMapRemoteDevice.FindNode("AcquisitionStart").WaitUntilDone(); + nodeMapRemoteDevice.FindNode("AcquisitionStart").Execute(); + nodeMapRemoteDevice.FindNode("AcquisitionStart").WaitUntilDone(); - var width = nodeMapRemoteDevice.FindNode("Width").Value(); - var height = nodeMapRemoteDevice.FindNode("Height").Value(); + var width = nodeMapRemoteDevice.FindNode("Width").Value(); + var height = nodeMapRemoteDevice.FindNode("Height").Value(); //return debug info videoIn.Info = "Max. resolution (w x h): " + maxWidth + " x " + maxHeight + $"\r\nMin. resolution (w x h): " + minWidth + " x " + minHeight + $"\r\nCurrent resolution (w x h): " + width + " x " + height - + $"\r\nFramerate range: [{minFPS}, {maxFPS}], current FPS: {nodeMapRemoteDevice.FindNode("AcquisitionFrameRate").Value()}"; + + $"\r\nFramerate range: [{minFPS}, {maxFPS}], current FPS: {nodeMapRemoteDevice.FindNode("AcquisitionFrameRate").Value()}" + + $"\r\n"; + + /* + //debug properites list + string props = ""; + var allProps = nodeMapRemoteDevice.Nodes(); + foreach (var prop in allProps) + { + if (prop.IsFeature() && prop.AccessStatus() == NodeAccessStatus.ReadWrite) + //if (prop.Type() != NodeType.Command) + { + props += $"\r\n{prop.Name()} ({prop.Type()}) Description: {prop.Description()}"; + } + } + videoIn.Info += props + $"\r\n"; + + //debug properties tree + var sb = new StringBuilder(); + foreach (var n in nodeMapRemoteDevice.Nodes()) + { + TraverseCategories(sb, n, ""); + } + videoIn.Info += $"\r\n" + sb.ToString();*/ return new Acquisition(logger, device, dataStream, nodeMapRemoteDevice, new Int2((int)width, (int)height)); } + static void CollectPropertiesInfos(SpreadBuilder spb, NodeMap propertyMap) + { + var props = propertyMap.Nodes() + .Where(x => x.IsFeature()) + .Where(x => x.AccessStatus() == NodeAccessStatus.ReadWrite) + .Where(x => x.Type() != NodeType.Command); + foreach (var p in props) + { + switch (p.Type()) + { + case NodeType.Float: + var f = propertyMap.FindNodeFloat(p.Name()); + spb.Add(new PropertyInfo(f.Name(), f.Value(), f.Description(), f.Minimum(), f.Maximum(), Spread.Empty, f.Type().ToString(), f.AccessStatus().ToString())); + break; + + case NodeType.Integer: + var i = propertyMap.FindNodeInteger(p.Name()); + spb.Add(new PropertyInfo(i.Name(), i.Value(), i.Description(), i.Minimum(), i.Maximum(), Spread.Empty, i.Type().ToString(), i.AccessStatus().ToString())); + break; + + case NodeType.Boolean: + var b = propertyMap.FindNodeBoolean(p.Name()); + spb.Add(new PropertyInfo(b.Name(), b.Value(), b.Description(), false, true, Spread.Empty, b.Type().ToString(), b.AccessStatus().ToString())); + break; + + case NodeType.String: + var s = propertyMap.FindNodeString(p.Name()); + spb.Add(new PropertyInfo(s.Name(), s.Value(), s.Description(), "", "", Spread.Empty, s.Type().ToString(), s.AccessStatus().ToString())); + break; + + case NodeType.Enumeration: + var e = propertyMap.FindNodeEnumeration(p.Name()); + spb.Add(new PropertyInfo(e.Name(), e.CurrentEntry().Name(), e.Description(), "", "", e.Entries().Select(x => x.Name()).ToSpread(), e.Type().ToString(), e.AccessStatus().ToString())); + break; + + default: + // cannot set value + break; + } + } + } + + static void TraverseCategories(StringBuilder sb, Node p, string offset) + { + if (p is CategoryNode c) + { + sb.AppendLine($"{offset}--{c.Name()} ({c.Type()}) Description: {c.Description()}"); + foreach (var cp in c.SubNodes()) + { + //if (!cp.IsFeature() && cp.AccessStatus() == NodeAccessStatus.ReadWrite) + { + if (cp.Type() != NodeType.Category) + { + sb.AppendLine($"{offset} {cp.Name()} ({cp.Type()}) Description: {cp.Description()}"); + } + else + { + TraverseCategories(sb, cp, offset + " "); + } + } + } + } + else + { + sb.AppendLine($"\r\n{offset}{p.Name()} ({p.Type()}) Description: {p.Description()}"); + } + return; + } + private readonly IDisposable _idsPeakLibSubscription; private readonly ILogger _logger; private readonly Device _device; // DO NOT DELETE ME! Otherwise the finalizer will kill the whole graph! @@ -128,8 +231,17 @@ public Acquisition(ILogger logger, Device device, DataStream dataStream, NodeMap public PixelFormat PixelFormat { get; set; } = new PixelFormat(PixelFormatName.BGRa8); + public bool IsDisposed { get; private set; } + + public NodeMap NodeMap => _device.RemoteDevice().NodeMaps()[0]; + public void Dispose() { + if (IsDisposed) + return; + + IsDisposed = true; + _logger.Log(LogLevel.Information, "Stopping image acquisition"); try diff --git a/src/FileConfiguration.cs b/src/FileConfiguration.cs new file mode 100644 index 0000000..b8c7e4a --- /dev/null +++ b/src/FileConfiguration.cs @@ -0,0 +1,42 @@ +using peak.core; +using Path = VL.Lib.IO.Path; + +namespace VL.Devices.IDS +{ + [ProcessNode(Name = "FromFile")] + public class FileConfigurationNode + { + IConfiguration? configuration; + Path? file; + + public IConfiguration Update(Path file) + { + if (file != this.file) + { + this.file = file; + configuration = new FileConfiguration(file); + + } + return configuration!; + } + } + + class FileConfiguration : IConfiguration + { + public Path File { get; } + public FileConfiguration(Path file) + { + File = file; + } + + public void Configure(NodeMap nodeMap) + { + if (File.Exists) + { + //nodeMap.LoadFromFile(File.ToString()); + nodeMap.FindNodeString("UEyeParametersetPath").SetValue(File.ToString()); + nodeMap.FindNodeCommand("UEyeParametersetLoad").Execute(); + } + } + } +} diff --git a/src/IConfiguration.cs b/src/IConfiguration.cs new file mode 100644 index 0000000..a3e5b3c --- /dev/null +++ b/src/IConfiguration.cs @@ -0,0 +1,9 @@ +using peak.core; + +namespace VL.Devices.IDS +{ + public interface IConfiguration + { + void Configure(NodeMap propertyMap); + } +} diff --git a/src/InMemoryConfiguration.cs b/src/InMemoryConfiguration.cs new file mode 100644 index 0000000..46ca22d --- /dev/null +++ b/src/InMemoryConfiguration.cs @@ -0,0 +1,171 @@ +using peak.core; +using peak.core.nodes; +using Microsoft.Extensions.Logging; +using System.Collections.Immutable; + +namespace VL.Devices.IDS +{ + [ProcessNode(Name = "SetProperty")] + public class ConfigNode : IConfiguration + { + private readonly ILogger logger; + + IConfiguration? input; + string? key; + T? value; + FreshConfig? output; + + public ConfigNode([Pin(Visibility = Model.PinVisibility.Hidden)] NodeContext nodeContext) + { + this.logger = nodeContext.GetLogger(); + } + + public IConfiguration Update(IConfiguration input, string key, T value) + { + if (input != this.input || key != this.key || !EqualityComparer.Default.Equals(value, this.value)) + { + this.input = input; + this.key = key; + this.value = value; + output = new FreshConfig(this); + } + return output!; + } + + void IConfiguration.Configure(NodeMap nodeMap) + { + input?.Configure(nodeMap); + + Node p = nodeMap.FindNode(key); + if (p is null) + { + logger.LogError("Property with name {key} not found.", key); + return; + } + + if (p.Type() == NodeType.Float) + { + var f = nodeMap.FindNodeFloat(p.Name()); + if (value is float fv) + { + try + { + f.SetValue((double)fv); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to set value"); + } + } + else + { + logger.LogError("Failed to set value: type missmatch, expecting a float"); + } + } + + if (p.Type() == NodeType.Integer) + { + var i = nodeMap.FindNodeInteger(p.Name()); + if (value is int iv) + { + try + { + i.SetValue(iv); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to set value"); + } + } + else + { + logger.LogError("Failed to set value: type missmatch, expecting an integer"); + } + } + + if (p.Type() == NodeType.Boolean) + { + var b = nodeMap.FindNodeBoolean(p.Name()); + if (value is bool bv) + { + try + { + b.SetValue(bv); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to set value"); + } + } + else + { + logger.LogError("Failed to set value: type missmatch, expecting a boolean"); + } + } + + if (p.Type() == NodeType.String) + { + var s = nodeMap.FindNodeString(p.Name()); + if (value is string sv) + { + try + { + s.SetValue(sv); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to set value"); + } + } + else + { + logger.LogError("Failed to set value: type missmatch, expecting a string"); + } + } + + if (p.Type() == NodeType.Enumeration) + { + var e = nodeMap.FindNodeEnumeration(p.Name()); + if (value is string ev) + { + if (e.Entries().Select(x => x.Name()).Contains(ev)) + { + try + { + e.SetCurrentEntry(e.Entries().FirstOrDefault(x => x.Name() == ev)); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to set value"); + } + } + else + { + logger.LogError("Failed to set value: not a valid enum entry"); + } + + } + else + { + logger.LogError("Failed to set value: type missmatch, expecting a string"); + } + } + } + } + + // Utility so downstream sinks see the change. Forwards the Config call. + internal sealed class FreshConfig : IConfiguration + { + private readonly IConfiguration original; + + public FreshConfig(IConfiguration original) + { + this.original = original; + } + + public void Configure(NodeMap nodeMap) + { + original.Configure(nodeMap); + } + } +} diff --git a/src/Properties/launchSettings.json b/src/Properties/launchSettings.json index 32dcb89..984166a 100644 --- a/src/Properties/launchSettings.json +++ b/src/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "vvvv gamma 5.3": { "commandName": "Executable", - "executablePath": "C:\\Program Files\\vvvv\\vvvv_gamma_6.0-0295-g4d01577ad4\\vvvv.exe", + "executablePath": "C:\\Program Files\\vvvv\\vvvv_gamma_6.3-0015-gd6867805c4\\vvvv.exe", "commandLineArgs": "--package-repositories \"C:\\Users\\alex\\Documents\\libs\" --editable-packages VL.Devices.IDS -o \"C:\\Users\\alex\\Documents\\libs\\VL.Devices.IDS\\help\\Explanation Overview.vl\"" } } diff --git a/src/PropertyInfo.cs b/src/PropertyInfo.cs new file mode 100644 index 0000000..0fd6f80 --- /dev/null +++ b/src/PropertyInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VL.Core.CompilerServices; + +namespace VL.Devices.IDS +{ + /// + /// + /// + /// + /// + public record PropertyInfo(string Name, object CurrentValue, string Description, object Minimum, object Maximum, Spread Entries, string Type, string AccessStatus) + { + } +} diff --git a/src/SaveConfigToFile.cs b/src/SaveConfigToFile.cs new file mode 100644 index 0000000..d2bf951 --- /dev/null +++ b/src/SaveConfigToFile.cs @@ -0,0 +1,48 @@ +using Microsoft.Extensions.Logging; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using Path = VL.Lib.IO.Path; + +namespace VL.Devices.IDS +{ + [ProcessNode] + public class SaveConfigToFile : IDisposable + { + private readonly ILogger logger; + private readonly SerialDisposable serialDisposable = new(); + + public SaveConfigToFile([Pin(Visibility = Model.PinVisibility.Hidden)] NodeContext nodeContext) + { + logger = nodeContext.GetLogger(); + } + + public void Update(VideoIn? videoIn, Path path, bool save) + { + if (videoIn is null) + return; + + if (save) + { + serialDisposable.Disposable = videoIn.AcquisitionStarted.Take(1) + .Subscribe(a => + { + try + { + //a.NodeMap.StoreToFile(path.ToString()); + a.NodeMap.FindNodeString("UEyeParametersetPath").SetValue(path.ToString()); + a.NodeMap.FindNodeCommand("UEyeParametersetSave").Execute(); + } + catch (Exception e) + { + logger.LogError(e, $"Failed to serialize camera configuration."); + } + }); + } + } + + public void Dispose() + { + serialDisposable.Dispose(); + } + } +} diff --git a/src/VideoIn.cs b/src/VideoIn.cs index 74cf042..e596d67 100644 --- a/src/VideoIn.cs +++ b/src/VideoIn.cs @@ -3,6 +3,9 @@ using peak.core; using VL.Lib.Basics.Video; using VL.Model; +using System.Reactive.Subjects; +using System.Reactive.Linq; +using Microsoft.Extensions.Configuration; namespace VL.Devices.IDS { @@ -11,13 +14,16 @@ public class VideoIn : IVideoSource2, IDisposable { private readonly ILogger _logger; private readonly IDisposable _idsPeakLibSubscription; + private readonly BehaviorSubject _aquicitionStarted = new BehaviorSubject(null); private int _changedTicket; private DeviceDescriptor? _device; private Int2 _resolution; private int _fps; + private IConfiguration? _configuration; internal string Info { get; set; } = ""; + internal Spread PropertyInfos { get; set; } = new SpreadBuilder().ToSpread(); public VideoIn([Pin(Visibility = PinVisibility.Hidden)] NodeContext nodeContext) { @@ -26,26 +32,32 @@ public VideoIn([Pin(Visibility = PinVisibility.Hidden)] NodeContext nodeContext) } [return: Pin(Name = "Output")] - public IVideoSource Update( + public VideoIn Update( IDSDevice? device, [DefaultValue("640, 480")] Int2 resolution, [DefaultValue("30")] int fps, + IConfiguration configuration, + out Spread PropertyInfos, out string Info) { // By comparing the descriptor we can be sure that on re-connect of the device we see the change - if (device?.Tag != _device || resolution != _resolution || fps != _fps) + if (device?.Tag != _device || resolution != _resolution || fps != _fps || configuration != _configuration) { _device = device?.Tag as DeviceDescriptor; _resolution = resolution; _fps = fps; + _configuration = configuration; _changedTicket++; } + PropertyInfos = this.PropertyInfos; Info = this.Info; return this; } + internal IObservable AcquisitionStarted => _aquicitionStarted.Where(a => a != null && !a.IsDisposed)!; + IVideoPlayer? IVideoSource2.Start(VideoPlaybackContext ctx) { var device = _device; @@ -54,7 +66,9 @@ public IVideoSource Update( try { - return Acquisition.Start(this, device, _logger, _resolution, _fps); + var result = Acquisition.Start(this, device, _logger, _resolution, _fps, _configuration); + _aquicitionStarted.OnNext(result); + return result; } catch (Exception e) {