C3032
2026-01-08 37aff9db0932e3e274b3c26650060f7d2d336888
LB_VisionProcesses/Cameras/LBCameras/LBCamera.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
@@ -30,91 +31,39 @@
    {
        private IntPtr _cameraHandle = IntPtr.Zero;
        private PHM6000SensorConfig _sensorConfig;
        // 采集回调
        private AcquisitionCallbackZA _acquisitionCallback;
        private AcquisitionCompletedCallback _acquisitionCompletedCallback;
        private bool _isConnected = false;
        private int _frameCount = 0; // 采集帧计数
        // 图像缓冲
        private List<byte[]> _lineDataBuffer = new List<byte[]>();
        private readonly object _bufferLock = new object();
        private int _currentLineCount = 0;
        // 实时显示用的Bitmap
        private Bitmap _currentBitmap = null;
        private byte[] _rawPixelBuffer = null; // 用于存储像素数据,避免频繁LockBits
        private byte[] _rawPixelBuffer = null; // 用于存储整张图的像素数据 (8bpp)
        private int _currentBitmapHeight = 0;
        private int _currentBitmapWidth = 0;
        private object _bitmapLock = new object();
        private DateTime _lastUpdateTime = DateTime.MinValue;
        private int _currentLineCount = 0;
        private object _bufferLock = new object();
        private bool _isBufferReady = false;
        // 显示句柄和函数(与LLSystem示例保持一致)
        private IntPtr _lightPic = IntPtr.Zero;
        private IntPtr _deepPic = IntPtr.Zero;
        private IntPtr _pointPic = IntPtr.Zero;
        private IntPtr _outlinePic = IntPtr.Zero;
        // 显示控制函数结构体
        private PILOT2D_FUNC _func2d;
        private VTK3D_FUNC _func3d;
        // 临时行缓冲,用于接收回调数据
        private byte[] _tempLineBuffer = null;
        private bool _isContinuous = false;
        public LBCamera()
        {
            Brand = CameraBrand.LBCamera;
            _sensorConfig = new PHM6000SensorConfig();
            // 初始化2D显示函数
            _func2d = new PILOT2D_FUNC
            {
                AddBarycentreDataZA = Pilot2D.AddBarycentreDataZA,
                AddDepthData = Pilot2D.AddDepthData,
                AddIntensityData = Pilot2D.AddIntensityData,
                ClearAllPoints = Pilot2D.ClearAllPoints,
                RefreshPilot2D = Pilot2D.RefreshPilot2D,
                SetImageSize = Pilot2D.SetImageSize,
            };
            // 初始化3D显示函数
            _func3d = new VTK3D_FUNC
            {
                AddZAPoints = PHM6000Profiler.AddZAPoints,
                ClearPCLPoints = PHM6000Profiler.ClearPCLPoints,
                GetPointCloudBound = PHM6000Profiler.GetPointCloudBound,
                RenderPCLWindow = PHM6000Profiler.RenderPCLWindow,
                SetLookUpTableRange = PHM6000Profiler.SetLookUpTableRange,
                ShowCubeAxes = PHM6000Profiler.ShowCubeAxes,
                ShowLookUpTable = PHM6000Profiler.ShowLookUpTable,
                UpdatePCLPointColors = PHM6000Profiler.UpdatePCLPointColors,
            };
        }
        #region ICamera Implementation
        public override bool InitDevice(string sn, object handle = null)
        {
            // 如果已连接,仅检查是否需要更新显示句柄
            // 如果已连接,直接返回true
            if (_isConnected && _cameraHandle != IntPtr.Zero)
            {
                if (handle != null && handle is IntPtr hPtr && hPtr != IntPtr.Zero)
                {
                    // 销毁旧的显示句柄
                    if (_lightPic != IntPtr.Zero)
                    {
                        Pilot2D.ClearAllPoints(_lightPic);
                        Pilot2D.DestroyPilot2DEntry(_lightPic);
                    }
                    // 创建新的显示句柄
                    _lightPic = Pilot2D.CreatePilot2DEntry(hPtr);
                    if (_lightPic != IntPtr.Zero)
                    {
                        var config = GetSensorConfig();
                        Pilot2D.SetImageSize(_lightPic, 4096, config.ScanLineCount > 0 ? config.ScanLineCount : 5000);
                        // 更新相机绑定的显示句柄
                        PHM6000Profiler.SetShowHandles(_cameraHandle, _lightPic, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                        AsyncLogHelper.Info($"LBCamera[{SN}]: Display handle updated");
                    }
                }
                return true;
            }
@@ -148,9 +97,8 @@
                    {
                        string currentSn = Encoding.UTF8.GetString(serialNumberBytes).TrimEnd('\0');
                        
                        // 如果传入的 sn 是 IP 地址,则直接尝试匹配 IP
                        // 或者匹配序列号
                        if (currentSn == sn || sn.Contains(currentSn)) // 简单匹配逻辑
                        // 匹配SN或IP
                        if (currentSn == sn || sn.Contains(currentSn))
                        {
                            byte[] addressBytes = new byte[64];
                            int port = 0;
@@ -165,17 +113,15 @@
                    }
                }
                // 销毁临时句柄
                PHM6000Profiler.DestroyCameraEntry(tempHandle);
                tempHandle = IntPtr.Zero;
                if (!found)
                {
                    // 如果没找到但 sn 本身看起来像 IP,尝试直接连接
                    if (System.Net.IPAddress.TryParse(sn, out _))
                    {
                        targetIp = sn;
                        targetPort = 5577; // 默认端口
                        targetPort = 5577;
                    }
                    else
                    {
@@ -197,35 +143,18 @@
                    // 加载相机当前参数到 _sensorConfig
                    SyncConfigFromCamera();
                    // 创建亮度图显示句柄(类似LLSystem示例)
                    IntPtr hPtr = IntPtr.Zero;
                    if (handle is IntPtr p) hPtr = p;
                    if (hPtr != IntPtr.Zero)
                    {
                        _lightPic = Pilot2D.CreatePilot2DEntry(hPtr);
                        if (_lightPic != IntPtr.Zero)
                        {
                            // 设置图像尺寸,宽度4096,高度根据配置
                            var config = GetSensorConfig();
                            Pilot2D.SetImageSize(_lightPic, 4096, config.ScanLineCount > 0 ? config.ScanLineCount : 5000);
                        }
                    }
                    // 初始化并注册采集回调 (获取数据用)
                    _acquisitionCallback = new AcquisitionCallbackZA(OnAcquisitionCallbackZA);
                    PHM6000Profiler.SetAcquisitionCallbackZA(_cameraHandle, _acquisitionCallback, IntPtr.Zero);
                    // 初始化回调
                    // 初始化并注册采集完成回调 (状态通知用)
                    _acquisitionCompletedCallback = new AcquisitionCompletedCallback(OnAcquisitionCompleted);
                    // 注册采集完成回调(类似LLSystem示例)
                    PHM6000Profiler.RegisterAcquisitionCompletedCallback(_cameraHandle, _acquisitionCompletedCallback, IntPtr.Zero);
                    // 设置2D和3D显示函数(类似LLSystem示例)
                    PHM6000Profiler.SetPilot2dFunc(_cameraHandle, _func2d);
                    PHM6000Profiler.SetVTK3dFunc(_cameraHandle, _func3d);
                    // 强制应用当前配置(确保触发模式等参数正确,避免相机处于未知状态)
                    UpdateSensorConfig(_sensorConfig);
                    // 设置显示句柄(类似LLSystem示例的SetShowHandles)
                    PHM6000Profiler.SetShowHandles(_cameraHandle, _lightPic, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Connected and initialized successfully with SDK auto-display mode");
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Connected and initialized successfully (Manual Data Mode)");
                    return true;
                }
@@ -247,20 +176,10 @@
            if (_isConnected && _cameraHandle != IntPtr.Zero)
            {
                StopGrabbing();
                // 销毁显示句柄
                if (_lightPic != IntPtr.Zero)
                {
                    Pilot2D.ClearAllPoints(_lightPic);
                    Pilot2D.DestroyPilot2DEntry(_lightPic);
                    _lightPic = IntPtr.Zero;
                }
                PHM6000Profiler.DestroyCameraEntry(_cameraHandle);
                _cameraHandle = IntPtr.Zero;
                _isConnected = false;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Closed and cleaned up");
                AsyncLogHelper.Info($"LBCamera[{SN}]: Closed");
            }
            return true;
        }
@@ -282,8 +201,6 @@
                        if (PHM6000Profiler.GetCameraInformation(tempHandle, i, moduleTypeBytes, serialNumberBytes) == 0)
                        {
                            string sn = Encoding.UTF8.GetString(serialNumberBytes).TrimEnd('\0');
                            string type = Encoding.UTF8.GetString(moduleTypeBytes).TrimEnd('\0');
                            // 格式参考:PHM6000[SN123456]
                            if (!string.IsNullOrEmpty(sn))
                            {
                                cameraList.Add(sn);
@@ -294,7 +211,7 @@
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: GetListEnum异常 - {ex.Message}");
                AsyncLogHelper.Error($"LBCamera: 获取设备列表异常 - {ex.Message}");
            }
            finally
            {
@@ -306,132 +223,121 @@
        private void InitBuffer()
        {
            lock (_bitmapLock)
            lock (_bufferLock)
            {
                _currentBitmapHeight = _sensorConfig.ScanLineCount > 0 ? _sensorConfig.ScanLineCount : 5000;
                // 宽度暂时未知,将在第一行数据到达时初始化
                // 宽度在第一行数据到达时确定
                _currentBitmapWidth = 0;
                if (_currentBitmap != null)
                {
                    _currentBitmap.Dispose();
                    _currentBitmap = null;
                }
                _rawPixelBuffer = null;
                _currentLineCount = 0;
                _isBufferReady = false;
            }
        }
        public override bool StartGrabbing()
        {
            // 线扫相机默认使用连续采集模式
            // 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
            // 默认连续模式
            return StartSingleGrab();
        }
        /// <summary>
        /// 单次采集模式(适用于线扫相机)
        /// 参考LLSystem示例的StartScan方法:SetAcquisitionMode(1, 0)
        /// 使用SDK自动显示模式,不手动处理数据
        /// </summary>
        public bool StartSingleGrab()
        {
            if (!_isConnected) return false;
            _isContinuous = false;
            InitBuffer();
            AsyncLogHelper.Info($"LBCamera[{SN}]: 开始单次采集");
            AsyncLogHelper.Info($"LBCamera[{SN}]: Starting single grab mode");
            // 使用SDK自动显示模式,类似LLSystem示例
            // 不设置AcquisitionCallbackZA,让SDK自动处理图像显示
            PHM6000Profiler.SetAcquisitionCallbackZA(_cameraHandle, IntPtr.Zero, IntPtr.Zero);
            // 设置采集模式:1=扫描模式,0=单次模式(与LLSystem示例保持一致)
            // 1=扫描模式, 0=单次
            PHM6000Profiler.SetAcquisitionMode(_cameraHandle, 1, 0);
            int result = PHM6000Profiler.StartAcquisition(_cameraHandle, 0, 0, 0.0);
            if (result == 0)
            {
                isGrabbing = true;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Single grab started successfully");
                return true;
            }
            else
            {
                AsyncLogHelper.Error($"LBCamera[{SN}]: Failed to start single grab, result={result}");
            }
            return false;
        }
        /// <summary>
        /// 连续采集模式(适用于线扫相机)
        /// 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
        /// 使用SDK自动显示模式,不手动处理数据
        /// </summary>
        public override bool StartContinuousGrab()
        {
            if (!_isConnected) return false;
            AsyncLogHelper.Info($"LBCamera[{SN}]: Starting continuous grab mode");
            _isContinuous = true;
            InitBuffer();
            AsyncLogHelper.Info($"LBCamera[{SN}]:开始连续采集");
            // 使用SDK自动显示模式,类似LLSystem示例
            // 不设置AcquisitionCallbackZA,让SDK自动处理图像显示
            PHM6000Profiler.SetAcquisitionCallbackZA(_cameraHandle, IntPtr.Zero, IntPtr.Zero);
            // 设置采集模式:1=扫描模式,1=连续模式(与LLSystem示例保持一致)
            // 1=扫描模式, 1=连续
            PHM6000Profiler.SetAcquisitionMode(_cameraHandle, 1, 1);
            int result = PHM6000Profiler.StartAcquisition(_cameraHandle, 0, 0, 0.0);
            if (result == 0)
            {
                isGrabbing = true;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Continuous grab started successfully");
                return true;
            }
            else
            {
                AsyncLogHelper.Error($"LBCamera[{SN}]: Failed to start continuous grab, result={result}");
            }
            return false;
        }
        public override bool StopGrabbing()
        {
            _isContinuous = false;
            if (!_isConnected) return true;
            PHM6000Profiler.StopAcquisition(_cameraHandle);
            // 停止时如果有未显示的缓存数据,将其显示出来(支持显示不完整的帧)
            lock (_bufferLock)
            {
                if (_currentLineCount > 0)
                {
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Flushing partial buffer ({_currentLineCount} lines) on stop");
                    CreateAndFireBitmap();
                    _currentLineCount = 0;
                }
            }
            isGrabbing = false;
            return true;
        }
        public override bool StartWith_SoftTriggerModel() => StartContinuousGrab();
        public override bool StartWith_SoftTriggerModel()
        {
            // 对于LBCamera(线扫相机),软件触发连续采集
            // 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
            return StartContinuousGrab();
        }
        public override bool StartWith_HardTriggerModel(TriggerSource hardtriggeritem = TriggerSource.Line0) => StartSingleGrab();
        public override bool StartWith_HardTriggerModel(TriggerSource hardtriggeritem = TriggerSource.Line0)
        {
            // 对于LBCamera(线扫相机),硬件触发也使用连续采集模式
            // 参考LLSystem示例的ContinuousScan方法:SetAcquisitionMode(1, 1)
            // 外部硬件信号会触发相机开始采集
            return StartSingleGrab();
        }
        public override bool SoftTrigger()
        {
            // 线扫相机通常不需要传统软触发,但在某些模式下可模拟
            return true;
        }
        public override bool SoftTrigger() => true;
        #region 参数设置映射
        public override bool SetExpouseTime(double value) => SetParam(EnumNameId.ExposureTime, (float)value);
        public override bool GetExpouseTime(out double value) { float v; bool r = GetParam(EnumNameId.ExposureTime, out v); value = v; return r; }
        public override bool SetGain(double gain) => SetParam(EnumNameId.AnalogGain, (float)gain);
        public override bool GetGain(out double gain) { float v; bool r = GetParam(EnumNameId.AnalogGain, out v); gain = v; return r; }
        public override bool SetGain(double gain) => SetParam(EnumNameId.AnalogGain, (int)gain);
        public override bool GetGain(out double gain) { int v; bool r = GetParam(EnumNameId.AnalogGain, out v); gain = v; return r; }
        // 其他接口占位实现
        public override bool SetTriggerMode(TriggerMode mode, TriggerSource triggerEnum = TriggerSource.Line0) => true;
        public override bool GetTriggerMode(out TriggerMode mode, out TriggerSource source) { mode = TriggerMode.Off; source = TriggerSource.Software; return true; }
        public override bool SetTriggerMode(TriggerMode mode, TriggerSource triggerEnum = TriggerSource.Line0)
        {
            if (!_isConnected) return false;
            if (triggerEnum == TriggerSource.Software)
            {
                _sensorConfig.LineScanTriggerSource = EnumLineScanTriggerSource.固定频率;
                _sensorConfig.DataAcquisitionTriggerSource = EnumDataAcquisitionTriggerSource.软触发;
            }
            else
            {
                _sensorConfig.LineScanTriggerSource = EnumLineScanTriggerSource.编码器;
                _sensorConfig.DataAcquisitionTriggerSource = EnumDataAcquisitionTriggerSource.外部触发;
            }
            UpdateSensorConfig(_sensorConfig);
            return true;
        }
        public override bool GetTriggerMode(out TriggerMode mode, out TriggerSource source)
        {
            mode = TriggerMode.On;
            source = _sensorConfig.DataAcquisitionTriggerSource == EnumDataAcquisitionTriggerSource.软触发 ? TriggerSource.Software : TriggerSource.Line0;
            return true;
        }
        public override bool SetTriggerPolarity(TriggerPolarity polarity) => true;
        public override bool GetTriggerPolarity(out TriggerPolarity polarity) { polarity = TriggerPolarity.RisingEdge; return true; }
        public override bool SetTriggerFliter(double flitertime) => true;
@@ -442,58 +348,63 @@
        public override bool SetLineStatus(IOLines line, LineStatus linestatus) => true;
        public override bool GetLineStatus(IOLines line, out LineStatus lineStatus) { lineStatus = LineStatus.Low; return true; }
        public override bool AutoBalanceWhite() => true;
        // 不实现的方法
        public override void SetCamConfig(CameraConfig config) { }
        public override void GetCamConfig(out CameraConfig config) { config = new CameraConfig(null); }
        public override bool GetImage(out Bitmap bitmap, int outtime = 3000) { bitmap = null; return false; }
        public override bool GetImageWithSoftTrigger(out Bitmap bitmap, int outtime = 3000)
        public override bool GetImageWithSoftTrigger(out Bitmap bitmap, int outtime = 3000)
        {
            // 简单实现:软触发等待
            bitmap = null;
            if (!_isConnected) return false;
            if(!_isConnected) return false;
            // 对于线扫相机,使用SDK自动显示模式
            // 注意:SDK自动显示模式下,我们无法直接获取Bitmap
            // 但SDK会自动将图像显示到_lightPic句柄对应的控件上
            // 我们只需要启动单次采集,等待采集完成即可
            // 计算理论最小耗时 (仅当使用固定频率触发时)
            int minTime = 0;
            if (_sensorConfig.LineScanTriggerSource == EnumLineScanTriggerSource.固定频率)
            {
                float rate = _sensorConfig.SoftwareTriggerRate > 0 ? _sensorConfig.SoftwareTriggerRate : 1000f;
                int lines = _sensorConfig.ScanLineCount > 0 ? _sensorConfig.ScanLineCount : 5000;
                minTime = (int)((lines / rate) * 1000);
            }
            // 如果传入超时时间不够,自动延长
            int actualTimeout = outtime;
            if (actualTimeout < minTime + 2000)
            {
                actualTimeout = minTime + 3000; // 预留3秒余量
                AsyncLogHelper.Warn($"LBCamera: Provided timeout {outtime}ms is too short for {minTime}ms scan. Extended to {actualTimeout}ms.");
            }
            using (AutoResetEvent waitHandle = new AutoResetEvent(false))
            {
                bool captured = false;
                EventHandler<CameraEventArgs> handler = (s, e) =>
                {
                    // 对于线扫相机,仅当IsComplete为true时才表示采集完成
                    if (e is LBCameraEventArgs args && args.IsComplete)
                    {
                        captured = true;
                        waitHandle.Set();
                Bitmap res = null;
                EventHandler<CameraEventArgs> handler = (s, e) => {
                    if(e.Bitmap != null) {
                         res = e.Bitmap.Clone() as Bitmap;
                         waitHandle.Set();
                    }
                };
                this.ImageGrabbed += handler;
                try
                ImageGrabbed += handler;
                if (StartSingleGrab())
                {
                    // 使用单次采集模式(与LLSystem示例保持一致)
                    if (StartSingleGrab())
                    if (!waitHandle.WaitOne(actualTimeout))
                    {
                        // 等待采集完成
                        if (waitHandle.WaitOne(outtime))
                        {
                            // 由于SDK自动显示,我们无法返回Bitmap
                            // 但采集已完成,图像已显示到界面上
                            bitmap = null;  // SDK自动显示模式下不返回Bitmap
                            return captured;
                        }
                        AsyncLogHelper.Error($"LBCamera: GetImageWithSoftTrigger timeout after {actualTimeout}ms");
                    }
                }
                finally
                else
                {
                    this.ImageGrabbed -= handler;
                    StopGrabbing();
                    AsyncLogHelper.Error("LBCamera: StartSingleGrab failed");
                }
                ImageGrabbed -= handler;
                // 确保停止采集
                StopGrabbing();
                bitmap = res;
                return bitmap != null;
            }
            return false;
        }
        public PHM6000SensorConfig GetSensorConfig()
@@ -505,27 +416,16 @@
        public void UpdateSensorConfig(PHM6000SensorConfig config)
        {
            _sensorConfig = config;
            if (!_isConnected) return;
            // 基础图像参数
            SetParam(EnumNameId.ExposureTime, (float)config.ExposureTime);
            SetParam(EnumNameId.AnalogGain, (float)config.AnalogGain);
            // 扫描控制参数 - 决定单次采集的数据量
            PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.ScanLineCount, config.ScanLineCount, 0, 0);
            // 触发设置 - 决定采集速度
            PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.LineScanTriggerSource, 0, 0, (int)config.LineScanTriggerSource);
            PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.DataAcquisitionTriggerSource, 0, 0, (int)config.DataAcquisitionTriggerSource);
            // 如果是软触发(固定频率),设置频率
            if (config.LineScanTriggerSource == EnumLineScanTriggerSource.固定频率)
            {
                PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)EnumNameId.SoftwareTriggerRate, 0, config.SoftwareTriggerRate, 0);
            }
            // 确保参数生效
            PHM6000Profiler.SaveAllParametersToDevice(_cameraHandle);
        }
@@ -533,42 +433,205 @@
        
        #endregion
        #region Private Callback & Helpers
        #region Callbacks
        private void OnAcquisitionCallbackZA(IntPtr pInstance, IntPtr buffer, int points)
        {
            if (buffer == IntPtr.Zero || points <= 0) return;
            lock (_bufferLock)
            {
                // 初始化缓冲区
                if (_rawPixelBuffer == null)
                {
                    _currentBitmapWidth = points;
                    if (_currentBitmapHeight <= 0) _currentBitmapHeight = 2000; // 默认防呆
                    _rawPixelBuffer = new byte[_currentBitmapWidth * _currentBitmapHeight];
                    _currentLineCount = 0;
                }
                if (_currentLineCount >= _currentBitmapHeight) return; // 缓冲区满,忽略多余数据
                // 准备临时缓冲区接收行数据 (LBPointZA = 8 bytes)
                int lineBytes = points * 8;
                if (_tempLineBuffer == null || _tempLineBuffer.Length != lineBytes)
                {
                    _tempLineBuffer = new byte[lineBytes];
                }
                // 拷贝非托管内存到托管数组
                Marshal.Copy(buffer, _tempLineBuffer, 0, lineBytes);
                // 提取灰度(Intensity/Alpha)数据填充到 _rawPixelBuffer
                // LBPointZA结构: float(4) + res(3) + alpha(1). Alpha在偏移7
                int bufferOffset = _currentLineCount * _currentBitmapWidth;
                for (int i = 0; i < points; i++)
                {
                    if (bufferOffset + i < _rawPixelBuffer.Length)
                    {
                        _rawPixelBuffer[bufferOffset + i] = _tempLineBuffer[i * 8 + 7];
                    }
                }
                _currentLineCount++;
                // 如果达到预定高度,生成图像
                if (_currentLineCount >= _currentBitmapHeight)
                {
                    CreateAndFireBitmap();
                    // 重置,准备下一帧 (如果是连续采集)
                    _currentLineCount = 0;
                    // _rawPixelBuffer 可以复用,不需要置空
                }
            }
        }
        private void OnAcquisitionCompleted(IntPtr pInstance, int nOption)
        {
            // nOption含义:
            // 0 = 一批数据结束
            // 1 = 全部采集完成(仅单次模式)
            // 2 = 对应数据处理完成(每次采集完成都会触发)
            // 记录回调信息
            AsyncLogHelper.Info($"LBCamera[{SN}]: OnAcquisitionCompleted called, nOption={nOption}");
            if (nOption == 2)
            // nOption: 0=Batch End, 1=All End(Single), 2=Processing End
            // 此处主要用于日志或状态监控
            // 实际图像生成在 Data Callback 中完成
            if (nOption == 1) // 单次采集结束
            {
                // 连续采集模式下,每完成一帧SDK都会自动继续采集下一帧
                // SDK会自动处理图像显示到_lightPic句柄
                // 我们只需要统计采集次数
                if (_isContinuous && isGrabbing)
                {
                    // 如果在连续模式下收到结束信号,尝试自动重启采集
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Continuous mode frame ended, restarting...");
                    Task.Run(() =>
                    {
                        if (_isContinuous && _isConnected)
                        {
                            PHM6000Profiler.StartAcquisition(_cameraHandle, 0, 0, 0.0);
                        }
                    });
                }
                else
                {
                    isGrabbing = false;
                    AsyncLogHelper.Info($"LBCamera[{SN}]: Single grab completed by SDK");
                    // 单次采集结束时,如果有未显示的缓冲数据,立即生成图像
                    // 防止因数据量不足(小于ScanLineCount)导致GetImageWithSoftTrigger一直等待
                    lock (_bufferLock)
                    {
                        if (_currentLineCount > 0)
                        {
                            AsyncLogHelper.Info($"LBCamera[{SN}]: Flushing partial buffer ({_currentLineCount} lines) on completion");
                            CreateAndFireBitmap();
                            _currentLineCount = 0;
                        }
                    }
                }
            }
        }
        private void CreateAndFireBitmap()
        {
            try
            {
                int width = _currentBitmapWidth;
                int height = _currentLineCount; // 使用实际采集到的行数
                if (width <= 0 || height <= 0 || _rawPixelBuffer == null) return;
                Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
                // 设置灰度调色板
                ColorPalette palette = bmp.Palette;
                for (int i = 0; i < 256; i++)
                {
                    palette.Entries[i] = Color.FromArgb(i, i, i);
                }
                bmp.Palette = palette;
                // 拷贝数据
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
                // 注意:Bitmap Stride 可能不等于 Width,需要逐行拷贝
                int stride = bmpData.Stride;
                IntPtr ptr = bmpData.Scan0;
                for (int y = 0; y < height; y++)
                {
                    // 确保不越界
                    if ((y * width) + width <= _rawPixelBuffer.Length)
                    {
                        Marshal.Copy(_rawPixelBuffer, y * width, ptr + y * stride, width);
                    }
                }
                bmp.UnlockBits(bmpData);
                _frameCount++;
                AsyncLogHelper.Info($"LBCamera[{SN}]: Frame {_frameCount} generated ({width}x{height})");
                AsyncLogHelper.Info($"LBCamera[{SN}]: Frame {_frameCount} completed (continuous mode, SDK auto-display)");
                // 触发事件通知UI更新(但不传递Bitmap,因为SDK自动显示)
                ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, null, true));
                // 异步触发事件,避免阻塞SDK回调线程
                Task.Run(() =>
                {
                    try
                    {
                        ImageGrabbed?.Invoke(this, new LBCameraEventArgs(SN, bmp, true));
                    }
                    catch (Exception ex)
                    {
                        AsyncLogHelper.Error($"LBCamera: Event Invoke error - {ex.Message}");
                        bmp.Dispose(); // 异常时释放资源
                    }
                });
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: CreateBitmap error - {ex.Message}");
            }
        }
        private void SyncConfigFromCamera()
        {
            // 从相机读取所有参数并填充到 _sensorConfig
            foreach (EnumNameId id in Enum.GetValues(typeof(EnumNameId)))
            try
            {
                int iVal = 0; double dVal = 0; int eVal = 0;
                if (PHM6000Profiler.GetProfilerParameter(_cameraHandle, (int)id, ref iVal, ref dVal, ref eVal) == 0)
                if (!_isConnected) return;
                PropertyInfo[] props = _sensorConfig.GetType().GetProperties();
                foreach (PropertyInfo p in props)
                {
                    // 实际项目中应使用反射将值写回 _sensorConfig
                    // 跳过自定义参数
                    var iscustomAttr = p.GetCustomAttribute<IsCustomAttribute>();
                    if (iscustomAttr != null) continue;
                    if (Enum.TryParse(typeof(EnumNameId), p.Name, out object nameIdObj))
                    {
                        EnumNameId nameId = (EnumNameId)nameIdObj;
                        int intValue = 0;
                        double doubleValue = 0;
                        int enumValue = 0;
                        if (PHM6000Profiler.GetProfilerParameter(_cameraHandle, (int)nameId, ref intValue, ref doubleValue, ref enumValue) == 0)
                        {
                            if (p.PropertyType == typeof(int))
                            {
                                p.SetValue(_sensorConfig, intValue);
                            }
                            else if (p.PropertyType == typeof(float))
                            {
                                p.SetValue(_sensorConfig, (float)doubleValue);
                            }
                            else if (p.PropertyType == typeof(double))
                            {
                                p.SetValue(_sensorConfig, doubleValue);
                            }
                            else // Enum or other types
                            {
                                p.SetValue(_sensorConfig, enumValue);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                AsyncLogHelper.Error($"LBCamera: SyncConfigFromCamera error - {ex.Message}");
            }
        }
@@ -576,6 +639,13 @@
        {
            if (!_isConnected) return false;
            return PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)id, 0, value, 0) == 0;
        }
        private bool SetParam(EnumNameId id, int value)
        {
            if (!_isConnected) return false;
            // 对于枚举类型,通常通过 enumValue (最后一个参数) 传递
            return PHM6000Profiler.SetProfilerParameter(_cameraHandle, (int)id, 0, 0, value) == 0;
        }
        private bool GetParam(EnumNameId id, out float value)
@@ -590,6 +660,19 @@
            }
            return false;
        }
        private bool GetParam(EnumNameId id, out int value)
        {
            value = 0;
            if (!_isConnected) return false;
            int iVal = 0; double dVal = 0; int eVal = 0;
            if (PHM6000Profiler.GetProfilerParameter(_cameraHandle, (int)id, ref iVal, ref dVal, ref eVal) == 0)
            {
                value = eVal; // Assuming it returns in enumValue
                return true;
            }
            return false;
        }
        #endregion
    }
}
}