/* * Copyright (c) 2025 LG Electronics Inc. * SPDX-License-Identifier: LicenseRef-LGE-Proprietary */ using Aurora.Api; using MilCommon; using VisionCommon; using Hermes; using Matrox.MatroxImagingLibrary; using System; using System.Collections.Generic; using Aurora.View; using Newtonsoft.Json; using System.IO; namespace MilPattern { public class LineFinderRunParam : IRunParam, ICloneable { public ePolarity Polarity { get; set; } = ePolarity.Any; public int SubRegionNumber { get; set; } = 8; public double MinEdgeValue { get; set; } = 2.0; public Angle AngleTolerance { get; set; } = Angle.CreateWithDegree(360); // Default Angle 값은 CreateContext시에도 최초에 360도가 적용됨. public MrkParamExt MrkParamExt { get; set; } = new MrkParamExt(); public object Clone() { return new LineFinderRunParam() { Polarity = Polarity, SubRegionNumber = SubRegionNumber, MinEdgeValue = MinEdgeValue, AngleTolerance = AngleTolerance.Clone() as Angle, MrkParamExt = MrkParamExt.Clone() as MrkParamExt, }; } } /// /// 마커의 가장자리가 설정된 강도 전환의 시작과 끝 사이의 회색조 차이를 기준으로 점수를 백분율로 계산 /// 대조의 경우 8비트 이미지의 경우 최대 가능한 대비는 255이며, 이는 에지 값 100에 해당 /// public class ContrastParam : NotifyPropertyChangedBase, ICloneable { private double _Min = 0.0; private double _Max = 255;//MIL.M_MAX_POSSIBLE_VALUE; private double _Low = 0.0; private double _High = 255; //MIL.M_MAX_POSSIBLE_VALUE; public bool ContrastEnable { get; set; } = false; public double ScoreOffset { get; set; } = 0.0; public double Min { get => _Min; set { if (value <= Max) { SetProperty(ref _Min, value); } } }//if (Low < Min) { _Low = Min; } if (High < Min) { _High = Min; } } } public double Max { get => _Max; set { if (Min <= value) { SetProperty(ref _Max, value); } } }//if (High > Max) { _High = Max; } if (Low > Max) { _Low = Max; } } } public double Low => Min;//{ get => _Low; set { if (value<= High && value>= Min) { SetProperty(ref _Low, value); } } } public double High => Max;// { get => _High; set { if (Low <= value&& value <= Max) { SetProperty(ref _High, value); } } } public object Clone() { return new ContrastParam() { ContrastEnable = this.ContrastEnable, Min = this.Min, Max = this.Max, //Low = this.Low, //High = this.High, ScoreOffset = this.ScoreOffset, }; } } /// /// 마커의 가장자리가 설정된 강도 전환의 시작과 끝 사이의 회색조 차이를 기준으로 점수를 백분율로 계산 /// 강도의 경우 최대 가능한 값은 100이며, 이는 에지 값 100에 해당 /// public class StrengthParam : NotifyPropertyChangedBase, ICloneable { private double _Min = 0.0; private double _Max = MIL.M_MAX_POSSIBLE_VALUE; private double _Low = 0.0; private double _High = MIL.M_MAX_POSSIBLE_VALUE; public bool StrengthEnable { get; set; } = false; public double ScoreOffset { get; set; } = 0.0; public double Min { get => _Min; set { if (Min <= Max) { SetProperty(ref _Min, value); } if (Low < Min) { Low = Min; } } } public double Max { get => _Max; set { if (Min <= Max) { SetProperty(ref _Max, value); } if (High > Max) { High = Max; } } } public double Low { get => _Low; set { if (Low <= High && Low >= Min) { SetProperty(ref _Low, value); } } } public double High { get => _High; set { if (Low <= High && High <= Max) { SetProperty(ref _High, value); } } } public object Clone() { return new StrengthParam() { StrengthEnable = this.StrengthEnable, Min = this.Min, Max = this.Max, Low = this.Low, High = this.High, ScoreOffset = this.ScoreOffset, }; } } public class MrkParamExt : ICloneable { private const string FILE_NAME = "MrkParamExt.json"; public bool IsStripe { get; set; } = false; public ROI FindROI { get; set; } = new ROI(); public ContrastParam ContrastParam { get; set; } = new ContrastParam(); public StrengthParam StrengthParam { get; set; } = new StrengthParam(); public MrkParamExt() { } public MrkParamExt(ContrastParam contrastParam, StrengthParam strengthParam) { ContrastParam = contrastParam.Clone() as ContrastParam; StrengthParam = strengthParam.Clone() as StrengthParam; } public object Clone() { return new MrkParamExt() { IsStripe = this.IsStripe, FindROI = FindROI.Clone() as ROI, ContrastParam = this.ContrastParam.Clone() as ContrastParam, StrengthParam = this.StrengthParam.Clone() as StrengthParam, }; } public static bool Exists(string dirPath) { string filePath = Path.Combine(dirPath, FILE_NAME); return File.Exists(filePath); } internal static MrkParamExt Load(string dirPath) { string filePath = Path.Combine(dirPath, FILE_NAME); if (File.Exists(filePath)) { return JsonConvert.DeserializeObject( File.ReadAllText(filePath), typeof(MrkParamExt)) as MrkParamExt; } return new MrkParamExt(); } public static int Save(string dirPath, MrkParamExt mrkParamExt) { string filePath = Path.Combine(dirPath, FILE_NAME); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } using (var fs = File.Create(filePath)) using (var sw = new StreamWriter(fs)) using (var jtw = new JsonTextWriter(sw) { Formatting = Formatting.Indented, Indentation = 2, }) { (new JsonSerializer()).Serialize(jtw, mrkParamExt); } return E1.OK; } } internal static class MilMeasureLine { private static Logger _Logger = new Logger(typeof(MilMeasureLine).Name); public static MIL_ID CreateContext(MIL_ID milSystem) { return MilMeasureCommon.CreateContext(milSystem, MIL.M_EDGE); } public static MIL_ID CreateStripeContext(MIL_ID milSystem) { return MilMeasureCommon.CreateContext(milSystem, MIL.M_STRIPE); } public static IRunParam GetRunParam(MIL_ID contextId) { double angleTolerance = 0; MIL.MmeasInquire(contextId, MIL.M_BOX_ANGLE_TOLERANCE, ref angleTolerance, MIL.M_NULL); LineFinderRunParam runParam = new LineFinderRunParam() { SubRegionNumber = (int)MIL.MmeasInquire(contextId, MIL.M_SUB_REGIONS_NUMBER, MIL.M_NULL, MIL.M_NULL), Polarity = (ePolarity)(int)MIL.MmeasInquire(contextId, MIL.M_POLARITY, MIL.M_NULL, MIL.M_NULL), MinEdgeValue = (int)MIL.MmeasInquire(contextId, MIL.M_EDGEVALUE_MIN, MIL.M_NULL, MIL.M_NULL), AngleTolerance = Angle.CreateWithDegree(angleTolerance), //FindROI = MilMeasureCommon.GetFindROI(contextId), }; return runParam; } public static int SetRunParam(MIL_ID contextId, IRunParam runParam) { if (runParam is LineFinderRunParam) { var param = runParam as LineFinderRunParam; if (param.AngleTolerance.Degree == 0) { param.AngleTolerance = Angle.CreateWithDegree(0.1); } MIL.MmeasSetMarker(contextId, MIL.M_POLARITY, (double)param.Polarity, MIL.M_NULL); MIL.MmeasSetMarker(contextId, MIL.M_SUB_REGIONS_NUMBER, param.SubRegionNumber, MIL.M_NULL); MIL.MmeasSetMarker(contextId, MIL.M_EDGEVALUE_MIN, param.MinEdgeValue, MIL.M_NULL); MIL.MmeasSetMarker(contextId, MIL.M_BOX_ANGLE_TOLERANCE, param.AngleTolerance.Degree, MIL.M_NULL); MilMeasureCommon.SetFindROI(contextId, param.MrkParamExt.FindROI); // Internal Param MIL.MmeasSetMarker(contextId, MIL.M_NUMBER, MIL.M_DEFAULT, MIL.M_NULL); MIL.MmeasSetMarker(contextId, MIL.M_FILTER_TYPE, MIL.M_SHEN, MIL.M_NULL); MIL.MmeasSetMarker(contextId, MIL.M_SEARCH_REGION_INPUT_UNITS, MIL.M_PIXEL, MIL.M_NULL); // SetScore if (param.MrkParamExt.ContrastParam.ContrastEnable) { MIL.MmeasSetScore(contextId, MIL.M_EDGE_CONTRAST_SCORE, param.MrkParamExt.ContrastParam.Min, param.MrkParamExt.ContrastParam.Low, param.MrkParamExt.ContrastParam.High, param.MrkParamExt.ContrastParam.Max, param.MrkParamExt.ContrastParam.ScoreOffset, MIL.M_DEFAULT, MIL.M_DEFAULT); } else { MIL.MmeasSetScore(contextId, MIL.M_EDGE_CONTRAST_SCORE, 0.0, 0.0, 255, 255, 0.0, MIL.M_DEFAULT, MIL.M_DEFAULT); // 사용하지 않을 때에는 Default 값을 넣어준다. } if (param.MrkParamExt.StrengthParam.StrengthEnable) { MIL.MmeasSetScore(contextId, MIL.M_STRENGTH_SCORE, param.MrkParamExt.StrengthParam.Min, param.MrkParamExt.StrengthParam.Low, param.MrkParamExt.StrengthParam.High, param.MrkParamExt.StrengthParam.Max, param.MrkParamExt.StrengthParam.ScoreOffset, MIL.M_DEFAULT, MIL.M_DEFAULT); } else { MIL.MmeasSetScore(contextId, MIL.M_STRENGTH_SCORE, 0.0, 0.0, MIL.M_MAX_POSSIBLE_VALUE, MIL.M_MAX_POSSIBLE_VALUE, 0.0, MIL.M_DEFAULT, MIL.M_DEFAULT); // 사용하지 않을 때에는 Default 값을 넣어준다. } return E1.OK; } return E1.GENERIC; } public static PatternResult Find(MIL_ID contextId, MIL_ID image, IRunParam runParam) { SetRunParam(contextId, runParam); return Find(contextId, image); } public static PatternResult Find(MIL_ID contextId, MIL_ID image, ROI findRoi) { return MilMeasureCommon.Find(contextId, image, ExtractResults, findRoi); } public static PatternResult Find(MIL_ID contextId, MIL_ID image) { return MilMeasureCommon.Find(contextId, image, ExtractResults); } private static PatternResult ExtractResults(MIL_ID contextId, double tactTime) { var results = new List(); MIL_INT numOfResult = 0; MIL.MmeasGetResult(contextId, MIL.M_NUMBER + MIL.M_TYPE_MIL_INT, ref numOfResult); if (0 < numOfResult) { double positionX = 0, positionY = 0; MIL.MmeasGetResult(contextId, MIL.M_POSITION, ref positionX, ref positionY); double orininalAngle = 0; MIL.MmeasGetResult(contextId, MIL.M_ANGLE, ref orininalAngle); var runParam = GetRunParam(contextId) as LineFinderRunParam; var angleTol = runParam.AngleTolerance.Degree; var roiAngle = runParam.MrkParamExt.FindROI.Angle.Degree; var expectedLineAngle = (roiAngle + 90) % 180; var measuredLineAngle = orininalAngle % 180; var diff = expectedLineAngle - measuredLineAngle; if (diff > 90) { diff = Math.Abs(measuredLineAngle + 180 - expectedLineAngle); } else if (diff < -90) { diff = Math.Abs(expectedLineAngle + 180 - measuredLineAngle); } else { diff = Math.Abs(diff); } if (diff <= angleTol) { double contrast = 0; double strength = 0; MIL.MmeasGetResult(contextId, MIL.M_EDGE_CONTRAST, ref contrast); MIL.MmeasGetResult(contextId, MIL.M_EDGE_STRENGTH, ref strength); var item = new LineFinderResult( new ImagePoint(positionX, positionY), Angle.CreateWithDegree(orininalAngle % 180), GetLineEquation(contextId), MilMeasureCommon.GetFindROI(contextId), contrast, strength, contextId); item.Status = eFoundStatus.FoundOK; results.Add(item); } } return new PatternResult() { PatternType = ePatternType.LineFinder, Status = (results.Count > 0) ? eFoundStatus.FoundOK : eFoundStatus.NotFound, Results = results, FindRoi = MilMeasureCommon.GetFindROI(contextId), TactTimeInMilliSec = MathUtil.ConvertUnitSecToMilli(tactTime) }; } private static LineEquation GetLineEquation(MIL_ID contextId) { double lineA = 0, lineB = 0, lineC = 0; MIL.MmeasGetResult(contextId, MIL.M_LINE_A, ref lineA); MIL.MmeasGetResult(contextId, MIL.M_LINE_B, ref lineB); MIL.MmeasGetResult(contextId, MIL.M_LINE_C, ref lineC); return new LineEquation(lineA, lineB, lineC); } public static PatternResult GetNotFoundPatternResult() { return new PatternResult() { Status = eFoundStatus.NotFound, Results = new List() }; } public static void Draw(MIL_ID contextId, PatternResult patternResult, IMilCameraDisplay milCameraDisplay) { MilMeasureCommon.DrawSubPositions(contextId, milCameraDisplay); DrawLine(contextId, milCameraDisplay); DrawText(patternResult, milCameraDisplay); MilMeasureCommon.DrawPosition(contextId, milCameraDisplay); } public static void Draw(MIL_ID contextId, IMilCameraDisplay milCameraDisplay) { MilMeasureCommon.DrawSubPositions(contextId, milCameraDisplay); DrawLine(contextId, milCameraDisplay); MilMeasureCommon.DrawPosition(contextId, milCameraDisplay); } public static void DrawTextForMulti(LineFinderResult lineFinderResult, IMilCameraDisplay milCameraDisplay) { DrawText(lineFinderResult, milCameraDisplay); } [Obsolete] public static void Draw(MIL_ID contextId, LineFinderResult lineFinderResult, IMilCameraDisplay milCameraDisplay) { MilMeasureCommon.DrawSubPositions(contextId, milCameraDisplay); DrawLine(contextId, milCameraDisplay); DrawText(lineFinderResult, milCameraDisplay); MilMeasureCommon.DrawPosition(contextId, milCameraDisplay); } private static void DrawLine(MIL_ID contextId, IMilCameraDisplay milCameraDisplay) { MIL_INT numOfResult = 0; MIL.MmeasGetResult(contextId, MIL.M_NUMBER + MIL.M_TYPE_MIL_INT, ref numOfResult); if (0 < numOfResult) { var imageSize = MilEnvironment.GetImageSize(milCameraDisplay.GetImage()); var lineEquation = GetLineEquation(contextId); var line = MilMeasureCommon.GetMinLengthLine(lineEquation, imageSize); milCameraDisplay.AddLine(GraphicColors.PatternFoundOK, line, false); } } private static void DrawText(PatternResult patternResult, IMilCameraDisplay milCameraDisplay) { if (patternResult.Status == eFoundStatus.FoundOK || patternResult.Status == eFoundStatus.ForceOK) { foreach (BaseResult baseResult in patternResult.Results) { var lineFinderResult = baseResult as LineFinderResult; DrawText(patternResult, lineFinderResult, milCameraDisplay); } } } private static void DrawText(LineFinderResult lineFinderResult, IMilCameraDisplay milCameraDisplay) { if (lineFinderResult != null) { var msgs = new List(); msgs.Add(string.Format("X : {0:0.00}", lineFinderResult.ImagePosition.X)); msgs.Add(string.Format("Y : {0:0.00}", lineFinderResult.ImagePosition.Y)); msgs.Add(string.Format("Angle : {0:0.00}", lineFinderResult.Angle.Degree)); milCameraDisplay.AddTextList( GraphicColors.PatternFoundOK, new ImagePoint(lineFinderResult.ImagePosition.X, lineFinderResult.ImagePosition.Y), msgs); } } private static void DrawText(PatternResult patternResult, LineFinderResult lineFinderResult, IMilCameraDisplay milCameraDisplay) { if (lineFinderResult != null) { var msgs = new List(); msgs.Add(string.Format("X : {0:0.00}", lineFinderResult.ImagePosition.X)); msgs.Add(string.Format("Y : {0:0.00}", lineFinderResult.ImagePosition.Y)); msgs.Add(string.Format("Angle : {0:0.00}", lineFinderResult.Angle.Degree)); msgs.Add(string.Format("Contrast : {0:0.000}", lineFinderResult.Contrast)); msgs.Add(string.Format("Strength : {0:0.000}", lineFinderResult.Strength)); msgs.Add(string.Format("TactTime : {0:F3} ms", patternResult.TactTimeInMilliSec)); msgs.Add(string.Format("ID : {0}_{1}", patternResult.PatternIndex?.PatternId, patternResult.PatternIndex?.PatternSubId)); milCameraDisplay.AddTextList( GraphicColors.PatternFoundOK, new ImagePoint(lineFinderResult.ImagePosition.X, lineFinderResult.ImagePosition.Y), msgs); } } } }