领胜LDS 键盘AOI检测项目
wells.liu
2020-07-01 dbae9c048fa0cd67c2e1161e5b6b693f87064154
Merge branch 'master' of http://gitblit.broconcentric.com:8088/r/M071
11个文件已修改
632 ■■■■ 已修改文件
src/Bro.Common.Device/DeviceBase/CameraBase.cs 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Gocator/Bro.Device.Gocator.csproj 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Gocator/GocatorConfig.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.Gocator/GocatorDriver.cs 263 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Device.HikCamera/HikCameraDriver.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.M071.Process/M071Config.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.M071.Process/M071Models.cs 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.M071.Process/M071Process.cs 195 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Process/ProcessControl.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.UI.Model.Winform/UI/Canvas.cs 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.UI.Model.Winform/UI/CanvasImage.cs 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/Bro.Common.Device/DeviceBase/CameraBase.cs
@@ -71,7 +71,7 @@
        #endregion
        #region 图片相关
        public event Action<CameraBase, Bitmap, string> OnImageUpdated;
        public Action<CameraBase, Bitmap, string> OnImageUpdated;
        public Action<CameraBase, HObject, string> OnHImageOutput { get; set; }
        public event Action OnCameraOpModeChanged;
@@ -141,22 +141,21 @@
        /// </summary>
        /// <returns></returns>
        public abstract void Snapshot();
        public abstract IImageSet Snapshot(IOperationConfig config);
        public virtual ImageSet Snapshot(IOperationConfig config)
        public virtual void InitialImageSet(IImageSet set, ImageSaveOption saveOption)
        {
            OpMode = CameraOpMode.SingleSnapShot;
            ImageSet set = new ImageSet();
            CameraOprerationConfigBase opConfig = config as CameraOprerationConfigBase;
            set.ImageSaveOption.DataFrom(opConfig.ImageSaveOption);
            //CameraOprerationConfigBase opConfig = config as CameraOprerationConfigBase;
            set.ImageSaveOption.DataFrom(saveOption);
            set.IsOriginSaved = !set.ImageSaveOption.IsSaveOriginImage;
            set.IsFitSaved = !set.ImageSaveOption.IsSaveFitImage;
            set.IsAddtionalSaved = !string.IsNullOrWhiteSpace(set.ImageSaveOption.AddtionalSaveType);
            set.OnImageSetTimeout += ImageSet_OnImageSetTimeout;
            _imageSetList.Add(set);
            return set;
            _imageSetList.Add(set);
        }
        /// <summary>
@@ -172,13 +171,13 @@
        #endregion
        #region 基元处理
        public event Action<CameraBase, List<IShapeElement>, string> OnElementsUpdated;
        public Action<CameraBase, List<IShapeElement>, string> OnElementsUpdated;
        #endregion
        #region 图片缓存/保存
        readonly ConcurrentBag<ImageSet> _imageSetList = new ConcurrentBag<ImageSet>();
        public readonly ConcurrentBag<IImageSet> _imageSetList = new ConcurrentBag<IImageSet>();
        public void NewImageSet(ImageSet set)
        public void NewImageSet(IImageSet set)
        {
            set.IsOriginSaved = !set.ImageSaveOption.IsSaveOriginImage;
            set.IsFitSaved = !set.ImageSaveOption.IsSaveFitImage;
@@ -202,7 +201,7 @@
                OnImageUpdated?.Invoke(this, showImage, imgSetId);
            });
            ImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId);
            IImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId);
            if (set == null)
                return;
@@ -225,10 +224,6 @@
                        {
                            LogAsync(DateTime.Now, $"{set.Id}原图保存失败", ex.GetExceptionMessage());
                        }
                        //finally
                        //{
                        //    set.IsOriginSaved = true;
                        //}
                    }
                    set.IsOriginSaved = true;
                    ClearImageSet(set);
@@ -241,7 +236,7 @@
            if (eleList.Count > 0)
                OnElementsUpdated?.BeginInvoke(this, eleList, imgSetId, null, null);
            ImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId);
            IImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId);
            if (set == null)
                return;
@@ -305,7 +300,7 @@
        {
            await Task.Run(() =>
            {
                ImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId);
                IImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId);
                if (set == null)
                    return;
@@ -341,7 +336,7 @@
            });
        }
        private string CheckImageDirectory(string subDir, string prefix)
        protected string CheckImageDirectory(string subDir, string prefix)
        {
            if (string.IsNullOrWhiteSpace(subDir))
            {
@@ -363,7 +358,7 @@
            return imgDir;
        }
        private void SaveImageByNameAndType(Bitmap map, string imageName, ImageFormat imgFormat, string imageDir)
        protected void SaveImageByNameAndType(Bitmap map, string imageName, ImageFormat imgFormat, string imageDir)
        {
            string filePath = Path.Combine(imageDir, $"{imageName}.{imgFormat.ToString().ToLower()}");
            map.Save(filePath, imgFormat);
@@ -371,7 +366,7 @@
        //private Timer clearImageSetTimer = null;
        public void ClearImageSet(ImageSet set)
        public void ClearImageSet(IImageSet set)
        {
            try
            {
@@ -402,7 +397,7 @@
        public void ClearImageSet(string imgSetId)
        {
            ImageSet set = _imageSetList.First(u => u.Id == imgSetId);
            IImageSet set = _imageSetList.First(u => u.Id == imgSetId);
            if (set != null)
                ClearImageSet(set);
        }
@@ -548,6 +543,7 @@
        {
            await Task.Run(() =>
            {
                Thread.Sleep(1000);
                int widthIn4 = (int)Math.Ceiling(width / 4.0) * 4;
                Bitmap bmp = new Bitmap(widthIn4, height, PixelFormat.Format48bppRgb);
@@ -575,15 +571,14 @@
                      {
                          int index = (i + 1) % width + widthIn4 * ((i + 1) / width) - 1;
                          showBitmapBuffer[index * 6] = data[i * 2];
                          showBitmapBuffer[index * 6 + 1] = data[i * 2 + 1];
                          showBitmapBuffer[index * 6] = bitmapBuffer[index * 6] = data[i * 2];
                          showBitmapBuffer[index * 6 + 1] = bitmapBuffer[index * 6 + 1] = data[i * 2 + 1];
                      });
                }
                bmp.UnlockBits(bitmapData);
                showImage.UnlockBits(showImageData);
                //showImage.Save(@"D:\1.bmp", ImageFormat.Bmp);
                SaveOriginImage(bmp, showImage, imgSetId);
            });
        }
@@ -838,7 +833,24 @@
        }
    }
    public class ImageSet : IDisposable
    public interface IImageSet : IDisposable
    {
        DateTime InitialTime { get; set; }
        string Id { get; }
        HImage HImage { get; set; }
        Bitmap Image { get; set; }
        object SaveLock { get; set; }
        ImageSaveOption ImageSaveOption { get; set; }
        bool IsOriginSaved { get; set; }
        bool IsFitSaved { get; set; }
        bool IsAddtionalSaved { get; set; }
        event Action<ImageSet> OnImageSetTimeout;
    }
    public class ImageSet : IImageSet
    {
        public DateTime InitialTime { get; set; } = DateTime.Now;
@@ -873,7 +885,7 @@
        //public bool IsInvokeSaveNG { get; set; } = false;
        public bool IsAddtionalSaved { get; set; } = false;
        public object SaveLock = new object();
        public object SaveLock { get; set; } = new object();
        public event Action<ImageSet> OnImageSetTimeout;
        private Timer autoDisposeTimer = null;
@@ -888,7 +900,7 @@
            autoDisposeTimer = new Timer(OnAutoDispose, null, -1, 10 * 1000);
        }
        public void Dispose()
        public virtual void Dispose()
        {
            autoDisposeTimer.Change(-1, -1);
            autoDisposeTimer.Dispose();
src/Bro.Device.Gocator/Bro.Device.Gocator.csproj
@@ -57,11 +57,16 @@
    <Reference Include="GoSdkNet">
      <HintPath>..\..\libs\gocator\GoSdkNet.dll</HintPath>
    </Reference>
    <Reference Include="halcondotnet, Version=12.0.0.0, Culture=neutral, PublicKeyToken=4973bed59ddbf2b8, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\..\libs\halcon12\halcondotnet.dll</HintPath>
    </Reference>
    <Reference Include="kApiNet">
      <HintPath>..\..\libs\gocator\kApiNet.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
src/Bro.Device.Gocator/GocatorConfig.cs
@@ -24,6 +24,7 @@
        [Category("计算设置")]
        [Description("true:计算时使用加速器,false:不使用加速器")]
        [DefaultValue(false)]
        [Browsable(false)]
        public bool IsUseAccelerator { get; set; } = false;
        [Category("拍摄设置")]
@@ -37,10 +38,10 @@
        [DefaultValue(1000)]
        public int SnapshotTimeout { get; set; } = 1000;
        //[Category("采图模式")]
        //[Description("是否硬触发模式。true:硬触发;false:软触发")]
        //[DisplayName("硬触发")]
        //public bool IsHardwareTrigger { get; set; } = false;
        [Category("采图模式")]
        [Description("异步采图时,是否硬触发模式。true:硬触发;false:软触发")]
        [DisplayName("硬触发")]
        public bool IsHardwareTrigger { get; set; } = false;
        [Category("采图模式")]
        [Description("true:异步/被动采图模式  false:同步/主动采图模式")]
src/Bro.Device.Gocator/GocatorDriver.cs
@@ -1,16 +1,21 @@
using Bro.Common.Base;
using Bro.Common.Helper;
using Bro.Common.Interface;
using HalconDotNet;
using Lmi3d.GoSdk;
using Lmi3d.GoSdk.Messages;
using Lmi3d.Zen;
using Lmi3d.Zen.Io;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Bro.Common.Helper.EnumHelper;
namespace Bro.Device.Gocator
{
@@ -30,14 +35,23 @@
        public override void Snapshot()
        {
            GoDataSet dataSet = null;
            if (IIConfig.IsAsyncMode)
            {
                if (!IIConfig.IsHardwareTrigger)
                {
                    _snapFlag = true;
                    if (!_snapHandle.WaitOne(IIConfig.SnapshotTimeout))
                    {
                        LogAsync(DateTime.Now, $"{Name}获取图像超时", "");
                        return;
                    }
                    dataSet = _currentData;
                }
            }
            else
            {
                GoDataSet dataSet = null;
                try
                {
                    dataSet = system.ReceiveData(IIConfig.SnapshotTimeout);
@@ -47,33 +61,23 @@
                    LogAsync(DateTime.Now, $"{Name}获取图像异常", ex.GetExceptionMessage());
                    return;
                }
                HandleGoData(dataSet);
            }
            HandleGoData(dataSet);
        }
        private void HandleGoData(GoDataSet dataSet)
        private void HandleGoData(GoDataSet dataSet, GoImageSet imgSet = null)
        {
            if (dataSet == null)
            {
                return;
            }
            for (UInt32 i = 0; i < dataSet.Count; i++)
            {
                GoDataMsg dataObj = (GoDataMsg)dataSet.Get(i);
                switch (dataObj.MessageType)
                {
                    //case GoDataMessageType.Stamp:
                    //    {
                    //        GoStampMsg stampMsg = (GoStampMsg)dataObj;
                    //        for (UInt32 j = 0; j < stampMsg.Count; j++)
                    //        {
                    //            GoStamp stamp = stampMsg.Get(j);
                    //            Console.WriteLine("Frame Index = {0}", stamp.FrameIndex);
                    //            Console.WriteLine("Time Stamp = {0}", stamp.Timestamp);
                    //            Console.WriteLine("Encoder Value = {0}", stamp.Encoder);
                    //        }
                    //    }
                    //    break;
                    case GoDataMessageType.Surface:
                        {
                            GoSurfaceMsg surfaceMsg = (GoSurfaceMsg)dataObj;
@@ -82,43 +86,83 @@
                            long bufferSize = width * height;
                            IntPtr bufferPointer = surfaceMsg.Data;
                            //Console.WriteLine("Whole Part Height Map received:");
                            //Console.WriteLine(" Buffer width: {0}", width);
                            //Console.WriteLine(" Buffer height: {0}", height);
                            if (imgSet != null)
                            {
                                imgSet.HImage = new HImage();
                                imgSet.HImage.GenImage1("uint2", (int)width, (int)height, bufferPointer);
                            //short[] ranges = new short[bufferSize];
                            //Marshal.Copy(bufferPointer, ranges, 0, ranges.Length);
                                imgSet.HImage_2 = new HImage();
                                imgSet.HImage_2.GenImage1("uint2", (int)width, (int)height, bufferPointer);
                            }
                            Generate16GrayImageByPointer((int)(width / 1), (int)(height / 1), bufferPointer, "");
                            Generate16GrayImageByPointer((int)width, (int)height, bufferPointer, imgSet?.Id);
                        }
                        break;
                        //case GoDataMessageType.SurfaceIntensity:
                        //    {
                        //        GoSurfaceIntensityMsg surfaceMsg = (GoSurfaceIntensityMsg)dataObj;
                        //        long width = surfaceMsg.Width;
                        //        long height = surfaceMsg.Length;
                        //        long bufferSize = width * height;
                        //        IntPtr bufferPointeri = surfaceMsg.Data;
                        //        //Console.WriteLine("Whole Part Intensity Image received:");
                        //        //Console.WriteLine(" Buffer width: {0}", width);
                        //        //Console.WriteLine(" Buffer height: {0}", height);
                        //        //byte[] ranges = new byte[bufferSize];
                        //        //Marshal.Copy(bufferPointeri, ranges, 0, ranges.Length);
                        //        //GenerateGrayImageByPointer((int)width, (int)height, bufferPointeri, "");
                        //    }
                        //    break;
                }
            }
        }
        public override ImageSet Snapshot(IOperationConfig config)
        public override IImageSet Snapshot(IOperationConfig config)
        {
            ImageSet imgSet = base.Snapshot(config);
            if (!IIConfig.IsAsyncMode)
            {
                if (config is GocatorOperationConfig opConfig)
                {
                    if (opConfig.IsOpenConnection)
                    {
                        system.Start();
                    }
                    else
                    {
                        system.Stop();
                    }
                    if (!opConfig.IsSnapshotAction)
                    {
                        return null;
                    }
                }
            }
            GoImageSet imgSet = new GoImageSet();
            InitialImageSet(imgSet, (config as CameraOprerationConfigBase).ImageSaveOption);
            GoDataSet dataSet = null;
            if (IIConfig.IsAsyncMode)
            {
                if (!IIConfig.IsHardwareTrigger)
                {
                    _snapFlag = true;
                    if (!_snapHandle.WaitOne(IIConfig.SnapshotTimeout))
                    {
                        LogAsync(DateTime.Now, $"{Name}获取图像超时", "");
                        return null;
                    }
                    dataSet = _currentData;
                }
            }
            else
            {
                try
                {
                    dataSet = system.ReceiveData(IIConfig.SnapshotTimeout);
                }
                catch (Exception ex)
                {
                    LogAsync(DateTime.Now, $"{Name}获取图像异常", ex.GetExceptionMessage());
                    return null;
                }
            }
            HandleGoData(dataSet, imgSet);
            if (imgSet.HImage == null)
            {
                LogAsync(DateTime.Now, $"{Name}未能获取HImage图像", "");
            }
            return imgSet;
        }
        float _currentExposure = 0;
        string _currentJob = "";
@@ -193,7 +237,15 @@
            _currentJob = sensor.DefaultJob;
            if (!string.IsNullOrWhiteSpace(IIConfig.DefaultJob) && _currentJob != IIConfig.DefaultJob)
            {
                _currentJob = sensor.DefaultJob = IIConfig.DefaultJob;
                //_currentJob = sensor.DefaultJob = IIConfig.DefaultJob;
                string currentJob = IIConfig.DefaultJob;
                bool isChanged = false;
                sensor.LoadedJob(ref currentJob, ref isChanged);
                if (!isChanged)
                {
                    throw new ProcessException($"{Name}未成功切换至任务{IIConfig.DefaultJob}");
                }
            }
            sensor.Flush();
@@ -240,6 +292,10 @@
            get => InitialConfig as GocatorInitialConfig;
        }
        volatile bool _snapFlag = false;
        readonly ManualResetEvent _snapHandle = new ManualResetEvent(false);
        GoDataSet _currentData = null;
        /// <summary>
        /// 异步模式获取数据
        /// </summary>
@@ -247,6 +303,127 @@
        private void onData(KObject data)
        {
            GoDataSet dataSet = (GoDataSet)data;
            if (IIConfig.IsHardwareTrigger)
            {
                GoImageSet imgSet = new GoImageSet();
                InitialImageSet(imgSet, IConfig.ImageSaveOption);
                HandleGoData(dataSet, imgSet);
                if (imgSet.HImage != null)
                {
                    OnHImageOutput?.BeginInvoke(this, imgSet.HImage, imgSet.Id, null, null);
                }
                else
                {
                    LogAsync(DateTime.Now, $"{Name}获取HImage失败", "");
                }
            }
            else
            {
                if (_snapFlag)
                {
                    _snapFlag = false;
                    _currentData = dataSet.Clone<GoDataSet>();
                    _snapHandle.Set();
                }
            }
        }
        #region 重写图片保存操作
        public override async void SaveOriginImage(Bitmap map, Bitmap showImage, string imgSetId)
        {
            Task.Run(() =>
            {
                OnImageUpdated?.Invoke(this, showImage, imgSetId);
            });
            GoImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId) as GoImageSet;
            if (set == null)
                return;
            set.Image = map;
            await Task.Run(() =>
            {
                lock (set.SaveLock)
                {
                    if (set.ImageSaveOption.IsSaveOriginImage && !set.IsOriginSaved)
                    {
                        string imgDir = CheckImageDirectory(set.ImageSaveOption.ImageSaveSubDirectory, "Origin");
                        try
                        {
                            //SaveImageByNameAndType(map, set.Id, set.ImageSaveOption.ImageFormat, imgDir);
                            string filePath = Path.Combine(imgDir, $"{set.Id}.tif");
                            set.HImage_2.WriteImage("tiff", 0, filePath);
                        }
                        catch (Exception ex)
                        {
                            LogAsync(DateTime.Now, $"{set.Id}原图保存失败", ex.GetExceptionMessage());
                        }
                    }
                    set.IsOriginSaved = true;
                    ClearImageSet(set);
                }
            });
        }
        /// <summary>
        /// 保存特定类型图片,例如NG图片
        /// </summary>
        /// <param name="prefix">图片类型说明 例如“NG”</param>
        /// <param name="imgSetId">图片ID</param>
        public override async void SaveSelectedImage(string prefix, string imgSetId)
        {
            await Task.Run(() =>
            {
                GoImageSet set = _imageSetList.FirstOrDefault(u => u.Id == imgSetId) as GoImageSet;
                if (set == null)
                    return;
                lock (set.SaveLock)
                {
                    if (set.ImageSaveOption.AddtionalSaveType.Contains(prefix) && !set.IsAddtionalSaved)
                    {
                        string imgDir = CheckImageDirectory(set.ImageSaveOption.ImageSaveSubDirectory, prefix);
                        while (set.Image == null)
                        {
                            Thread.Sleep(50);
                        }
                        try
                        {
                            string filePath = Path.Combine(imgDir, $"{set.Id}.tif");
                            set.HImage_2.WriteImage("tiff", 0, filePath);
                        }
                        catch (Exception ex)
                        {
                            LogAsync(DateTime.Now, $"{set.Id}{prefix}图片保存失败", ex.GetExceptionMessage());
                        }
                    }
                    set.IsAddtionalSaved = true;
                    ClearImageSet(set);
                }
            });
        }
        #endregion
    }
    public class GoImageSet : ImageSet
    {
        public HImage HImage_2 { get; set; }
        public override void Dispose()
        {
            base.Dispose();
            HImage_2?.Dispose();
            HImage_2 = null;
        }
    }
}
src/Bro.Device.HikCamera/HikCameraDriver.cs
@@ -392,9 +392,11 @@
            }
        }
        public override ImageSet Snapshot(IOperationConfig config)
        public override IImageSet Snapshot(IOperationConfig config)
        {
            ImageSet set = base.Snapshot(config);
            ImageSet set = new ImageSet();
            InitialImageSet(set, (config as CameraOprerationConfigBase).ImageSaveOption);
            MyCamera.MV_FRAME_OUT frameInfo = new MyCamera.MV_FRAME_OUT();
            nRet = MyCamera.MV_OK;
src/Bro.M071.Process/M071Config.cs
@@ -1,4 +1,5 @@
using Bro.Common.Helper;
using Bro.Common.Base;
using Bro.Common.Helper;
using Bro.Common.Model;
using Bro.Process;
using System;
@@ -62,5 +63,16 @@
        [TypeConverter(typeof(CollectionCountConvert))]
        [Editor(typeof(ComplexCollectionEditor<MeasurementUint>), typeof(UITypeEditor))]
        public List<MeasurementUint> MeasurementUnitCollection { get; set; } = new List<MeasurementUint>();
        [Category("图片保存配置")]
        [Description("单键图片保存配置")]
        [TypeConverter(typeof(ComplexObjectConvert))]
        [Editor(typeof(PropertyObjectEditor), typeof(UITypeEditor))]
        public ImageSaveOption ImageSaveOption { get; set; } = new ImageSaveOption();
        [Category("图片保存配置")]
        [Description("单键图片保存目录路径")]
        [Editor(typeof(FoldDialogEditor),typeof(UITypeEditor))]
        public string ImageSaveFolder { get; set; } = "";
    }
}
src/Bro.M071.Process/M071Models.cs
@@ -3,6 +3,7 @@
using Bro.Common.Helper;
using Bro.Common.Interface;
using Bro.Common.Model;
using HalconDotNet;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -11,6 +12,7 @@
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Bro.M071.Process
@@ -195,18 +197,20 @@
    public class ProductionMeasurement : INotifyPropertyChanged, IDisposable
    {
        public string Barcode { get; set; }
        public string Barcode;
        public List<MeasurementUint> Measurements { get; set; } = new List<MeasurementUint>();
        public List<MeasurementUint> Measurements = new List<MeasurementUint>();
        public List<IShapeElement> ElementList = new List<IShapeElement>();
        public event PropertyChangedEventHandler PropertyChanged;
        public void Dispose()
        {
            Barcode = null;
            Measurements?.ForEach(m => m?.Dispose());
            Measurements = null;
            Barcode = null;
            GC.Collect();
        }
@@ -304,6 +308,10 @@
        [TypeConverter(typeof(KeyUnitResultConverter))]
        public string KeyResult { get; set; } = "";
        public List<HImage> KeyImages = new List<HImage>();
        public volatile int ImageSaveStatus = 0;
        //[Browsable(false)]
        //public NoticedDictionary<string, double?> MeasureValueDict { get; set; } = new NoticedDictionary<string, double?>();
@@ -335,6 +343,18 @@
        public void Dispose()
        {
            SpinWait wait = new SpinWait();
            while (ImageSaveStatus != 0)
            {
                wait.SpinOnce();
            }
            KeyImages?.ForEach(i =>
            {
                i?.Dispose();
                i = null;
            });
            KeyImages = null;
            MeasureValueDict = null;
        }
src/Bro.M071.Process/M071Process.cs
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
@@ -200,11 +201,17 @@
                      if (camera == null)
                          return;
                      ImageSet imgSet = camera.Snapshot(s.CameraOp.OpConfig);
                      if (imgSet == null)
                          return;
                      //IImageSet imgSet = camera.Snapshot(s.CameraOp.OpConfig);
                      //if (imgSet == null)
                      //    return;
                      RunImageHandle(camera, s.CameraOp.OpConfig, imgSet, s.Id, s.Name, pMeasure.Measurements);
                      HImage hImage = CollectHImage(camera, s.CameraOp.OpConfig, out string imgSetId);
                      if (string.IsNullOrWhiteSpace(imgSetId))
                      {
                          return;
                      }
                      RunImageHandle(camera, s.CameraOp.OpConfig, hImage, s.Id, s.Name, pMeasure.Measurements);
                  });
            BarCode = "";
@@ -243,72 +250,163 @@
            _pauseHandle.WaitResult = !_pauseHandle.WaitResult;
            return new ProcessResponse(_pauseHandle.WaitResult);
        }
        #region 私有方法
        private void MeasureProduction_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (sender is ProductionMeasurement pMeasure)
            {
                //检查是否全部完成
                pMeasure.Measurements.ForEach(m =>
                lock (pMeasure)
                {
                    if (m.KeyUnitCollection.All(k => k.IsDone != null))
                    //检查是否全部完成
                    pMeasure.Measurements.ForEach(m =>
                    {
                        if (!m.IsUpdated)
                        if (m.KeyUnitCollection.All(k => k.IsDone != null))
                        {
                            if (m.KeyUnitCollection.Any(k => k.IsDone == false))
                            if (!m.IsUpdated)
                            {
                                m.Spec.ActualValue = -999;
                            }
                            else
                            {
                                string toolKey = m.Id + "|" + m.Spec.AlgorithemPath;
                                if (!_halconToolDict.ContainsKey(toolKey))
                                if (m.KeyUnitCollection.Any(k => k.IsDone == false))
                                {
                                    LogAsync(DateTime.Now, $"{m.GetDisplayText()}{m.Spec.Code}算法未初始化", "");
                                    m.Spec.ActualValue = -999;
                                }
                                else
                                {
                                    var array = m.KeyUnitCollection.SelectMany(u => u.MeasureValueDict.Values.ToList().ConvertAll(v => v ?? -999)).ToArray();
                                    _halconToolDict[toolKey].InputTupleDic["INPUT_Params"] = new HTuple(array);
                                    if (!_halconToolDict[toolKey].RunProcedure(out string error))
                                    string toolKey = m.Id + "|" + m.Spec.AlgorithemPath;
                                    if (!_halconToolDict.ContainsKey(toolKey))
                                    {
                                        LogAsync(DateTime.Now, $"{m.GetDisplayText()}{m.Spec.Code}算法异常,{error}", "");
                                        LogAsync(DateTime.Now, $"{m.GetDisplayText()}{m.Spec.Code}算法未初始化", "");
                                        m.Spec.ActualValue = -999;
                                    }
                                    else
                                    {
                                        m.Spec.ActualValue = _halconToolDict[toolKey].GetResultTuple("OUTPUT_Result").D;
                                        LogAsync(DateTime.Now, $"{m.GetDisplayText()}数据{m.Spec.ActualValue},结果{(m.Spec.MeasureResult == null ? "TBD" : (m.Spec.MeasureResult == true ? "OK" : "NG"))}", "");
                                        var array = m.KeyUnitCollection.SelectMany(u => u.MeasureValueDict.Values.ToList().ConvertAll(v => v ?? -999)).ToArray();
                                        _halconToolDict[toolKey].InputTupleDic["INPUT_Params"] = new HTuple(array);
                                        if (!_halconToolDict[toolKey].RunProcedure(out string error))
                                        {
                                            LogAsync(DateTime.Now, $"{m.GetDisplayText()}{m.Spec.Code}算法异常,{error}", "");
                                            m.Spec.ActualValue = -999;
                                        }
                                        else
                                        {
                                            m.Spec.ActualValue = _halconToolDict[toolKey].GetResultTuple("OUTPUT_Result").D;
                                            LogAsync(DateTime.Now, $"{m.GetDisplayText()}数据{m.Spec.ActualValue},结果{(m.Spec.MeasureResult == null ? "TBD" : (m.Spec.MeasureResult == true ? "OK" : "NG"))}", "");
                                        }
                                    }
                                }
                                //输出图形基元到界面 todo
                                OnElementUpdated?.BeginInvoke(null, null, null);
                                SaveKeyImages(pMeasure.Barcode, m);
                                m.IsUpdated = true;
                            }
                            //输出图形基元到界面 todo
                            OnElementUpdated?.BeginInvoke(null, null, null);
                            m.IsUpdated = true;
                        }
                    });
                    if (!pMeasure.Measurements.All(m => m.IsUpdated))
                    {
                        return;
                    }
                });
                if (!pMeasure.Measurements.All(m => m.IsUpdated))
                {
                    return;
                    //MES输出 todo
                    //Excel报表输出 todo
                    //数据库保存 todo
                    SaveWholeImage(pMeasure);
                    pMeasure.Dispose();
                }
                //MES输出 todo
                //Excel报表输出 todo
                //数据库保存 todo
                //MeasureDict.Remove(pMeasure.Barcode);
                pMeasure.Dispose();
            }
        }
        private async void RunImageHandle(CameraBase camera, IOperationConfig opConfig, ImageSet imgSet, string snapshotId, string snapshotName, List<MeasurementUint> measureList)
        #region 图像保存
        private void SaveWholeImage(ProductionMeasurement pMeasure)
        {
            try
            {
                Bitmap backImage = (Bitmap)Bitmap.FromFile(Config.BackgroundImagePath);
                Bitmap map = new Bitmap(backImage.Width, backImage.Height);
                using (Graphics g = Graphics.FromImage(map))
                {
                    g.DrawImage(backImage, new PointF(0, 0));
                    pMeasure.ElementList.ForEach(e =>
                    {
                        e.Draw(g);
                    });
                }
                string dir = Path.Combine(Config.ImageSaveFolder, "TopView", DateTime.Now.ToString("yyyyMMdd"));
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                map.Save(Path.Combine(dir, $"{pMeasure.Barcode}_{DateTime.Now.ToString("HHmmss")}.bmp"));
            }
            catch (Exception ex)
            {
                LogAsync(DateTime.Now, "整体图片保存异常", ex.GetExceptionMessage());
            }
        }
        private void SaveKeyImages(string barCode, MeasurementUint measureUnit)
        {
            string measureName = measureUnit.GetDisplayText();
            if (Config.ImageSaveOption.IsSaveOriginImage)
            {
                measureUnit.KeyUnitCollection.ForEach(u => u.ImageSaveStatus++);
                string dir = Path.Combine(Config.ImageSaveFolder, "Origin", DateTime.Now.ToString("yyyyMMdd"), barCode, measureUnit.MeasureType);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                SaveKeyImages(measureUnit, measureName, dir);
            }
            string result = (measureUnit.Spec.MeasureResult ?? false) ? "OK" : "NG";
            if (Config.ImageSaveOption.AddtionalSaveType.ToUpper().Contains(result))
            {
                measureUnit.KeyUnitCollection.ForEach(u => u.ImageSaveStatus++);
                string dir = Path.Combine(Config.ImageSaveFolder, result, DateTime.Now.ToString("yyyyMMdd"), barCode, measureUnit.MeasureType);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                SaveKeyImages(measureUnit, measureName, dir);
            }
        }
        private async void SaveKeyImages(MeasurementUint measureUnit, string measureName, string dir)
        {
            await Task.Run(() =>
            {
                measureUnit.KeyUnitCollection.ForEach(u =>
                {
                    int i = 0;
                    u.KeyImages?.ForEach(image =>
                    {
                        string fileName = Path.Combine(dir, $"{measureName}_{u.Key}{(i == 0 ? "" : $"-{i}")}_{DateTime.Now.ToString("HHmmss")}.tiff");
                        image.WriteImage("tiff", 0, fileName);
                        i++;
                    });
                    u.ImageSaveStatus--;
                });
            });
        }
        #endregion
        private async void RunImageHandle(CameraBase camera, IOperationConfig opConfig, HImage hImage, string snapshotId, string snapshotName, List<MeasurementUint> measureList)
        {
            await Task.Run(() =>
             {
@@ -316,7 +414,7 @@
                 var keyBindCollection = measureList.SelectMany(u => u.KeyUnitCollection).Where(u => keys.Any(k => k.Key == u.Key)).ToList();
                 string toolKey = (opConfig as CameraOprerationConfigBase).AlgorithemPath;
                 HObject images = imgSet.HImage;
                 HObject images = hImage;
                 if (!string.IsNullOrWhiteSpace(toolKey))
                 {
@@ -328,7 +426,7 @@
                         return;
                     }
                     _halconToolDict[toolKey].InputImageDic["INPUT_Image"] = imgSet.HImage;
                     _halconToolDict[toolKey].InputImageDic["INPUT_Image"] = hImage;
                     if (!_halconToolDict[toolKey].RunProcedure(out string error))
                     {
                         LogAsync(DateTime.Now, $"{snapshotName}取图算法异常,{error}", "");
@@ -398,14 +496,21 @@
                                 }
                             }
                             keyBindList.ForEach(kb => kb.FillKeyValues(resultDict));
                             keyBindList.ForEach(kb =>
                             {
                                 kb.KeyImages.Add(image.Clone() as HImage);
                                 kb.FillKeyValues(resultDict);
                             });
                         });
                     image.Dispose();
                 });
                 if (count.I != 1)
                     imgSet.HImage.Dispose();
                 {
                     hImage?.Dispose();
                     hImage = null;
                 }
             });
        }
        #endregion
src/Bro.Process/ProcessControl.cs
@@ -581,7 +581,7 @@
        #region 图像处理
        protected HImage CollectHImage(CameraBase camera, IOperationConfig opConfig, out string imgSetId, [CallerMemberName]string methodCode = "")
        {
            ImageSet set = null;
            IImageSet set = null;
            if (IConfig.IsImageOffline)
            {
@@ -628,7 +628,7 @@
                TimeRecordCSV(DateTime.Now, camera.Name, methodCode + "采图", (int)sw.ElapsedMilliseconds);
            }
            imgSetId = set.Id;
            imgSetId = set?.Id;
            return set.HImage;
        }
src/Bro.UI.Model.Winform/UI/Canvas.cs
@@ -77,22 +77,26 @@
            set => stsStatus.Visible = value;
        }
        private void OnMouseLocationUpdated(Point screenPoint, Point imagePoint)
        private async void OnMouseLocationUpdated(Point screenPoint, Point imagePoint, string colorDesc)
        {
            MouseLocationUpdated(screenPoint, imagePoint);
            //await Task.Run(() => tsslLocation.Text = $"屏幕坐标X:{screenPoint.X},Y:{screenPoint.Y}    图片坐标X:{imagePoint.X},Y:{imagePoint.Y}   颜色:{colorDesc}");
            this.Invoke(new Action(() =>
            {
                tsslLocation.Text = $"屏幕坐标X:{screenPoint.X},Y:{screenPoint.Y}    图片坐标X:{imagePoint.X},Y:{imagePoint.Y}   颜色:{colorDesc}";
            }));
        }
        private void MouseLocationUpdated(Point screenPoint, Point imagePoint)
        {
            if (InvokeRequired)
            {
                Invoke(new Action<Point, Point>(MouseLocationUpdated), screenPoint, imagePoint);
            }
            else
            {
                tsslLocation.Text = $"屏幕坐标X:{screenPoint.X},Y:{screenPoint.Y}    图片坐标X:{imagePoint.X},Y:{imagePoint.Y}";
            }
        }
        //private void MouseLocationUpdated(Point screenPoint, Point imagePoint, string colorDesc)
        //{
        //    if (InvokeRequired)
        //    {
        //        Invoke(new Action<Point, Point, string>(MouseLocationUpdated), screenPoint, imagePoint);
        //    }
        //    else
        //    {
        //        tsslLocation.Text = $"屏幕坐标X:{screenPoint.X},Y:{screenPoint.Y}    图片坐标X:{imagePoint.X},Y:{imagePoint.Y}   颜色:{colorDesc}";
        //    }
        //}
        #endregion
        #region 属性
src/Bro.UI.Model.Winform/UI/CanvasImage.cs
@@ -35,7 +35,7 @@
        #region Event
        public Action<MouseState> OnMouseStateChanged;
        public Action<IShapeElement> DrawTemplateChanged = null;
        public Action<Point, Point> OnMouseLocationUpdated;
        public Action<Point, Point, string> OnMouseLocationUpdated;
        #endregion
        private MouseState mouseState = MouseState.Normal;
@@ -540,7 +540,13 @@
            //}
            //DisplayMouseLocation(e.Location);
            OnMouseLocationUpdated?.BeginInvoke(e.Location, ToMapPoint(e.Location), null, null);
            Point mapPoint = ToMapPoint(e.Location);
            Color color = Color.Transparent;
            if (MAP != null && mapPoint.X > 0 && mapPoint.X < MAP.Width && mapPoint.Y > 0 && mapPoint.Y < MAP.Height)
            {
                color = MAP.GetPixel(mapPoint.X, mapPoint.Y);
            }
            OnMouseLocationUpdated?.BeginInvoke(e.Location, mapPoint, color.Name, null, null);
            if (MouseState != MouseState.SelectionZoneDoing)
            {