Files

670 lines
26 KiB
C#
Raw Permalink Normal View History

using JM1Vision;
using MainShell.Common;
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using SemiconductorVisionAlgorithm.SemiParams;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MainShell.Vision
{
/// <summary>
/// Die 定位服务。
/// </summary>
public class FindDieService : IFindDieService
{
private readonly IImageCaptureService _imageCaptureService;
public FindDieService(IImageCaptureService imageCaptureService)
{
_imageCaptureService = imageCaptureService ?? throw new ArgumentNullException(nameof(imageCaptureService));
}
public async Task<VisionProcessResult<FindDieResult>> ProcessAsync(
FindDieRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessImageAsync,
cancellationToken).ConfigureAwait(false);
}
public Task<VisionProcessResult<FindDieResult>> ProcessImageAsync(
MxImage image,
FindDieImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult<FindDieResult>(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindDieResult> result = ExecuteSingleMatch(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindDieService: process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindDieResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindDieService: process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindDieResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex));
}
}
public async Task<VisionProcessResult<FindDiesResult>> ProcessMultipleAsync(
FindDieRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest imageRequest = CreateImageRequest(request);
return await ExecuteWithCaptureAsync(
request.CameraSource,
request.CaptureOptions,
imageRequest,
ProcessMultipleImageAsync,
cancellationToken).ConfigureAwait(false);
}
public Task<VisionProcessResult<FindDiesResult>> ProcessMultipleImageAsync(
MxImage image,
FindDieImageRequest request,
CancellationToken cancellationToken = default(CancellationToken))
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
FindDieImageRequest normalizedRequest = NormalizeRequest(request);
VisionProcessResult validationResult = ValidateRequest(normalizedRequest);
if (validationResult != null)
{
return Task.FromResult(CreateFailureResult<FindDiesResult>(validationResult));
}
try
{
cancellationToken.ThrowIfCancellationRequested();
VisionProcessResult<FindDiesResult> result = ExecuteMultipleMatch(image, normalizedRequest);
if (!result.Succeeded)
{
result.Message.LogSysError();
}
return Task.FromResult(result);
}
catch (OperationCanceledException ex)
{
string message = $"FindDieService: multiple die process image was cancelled. Timeout={normalizedRequest.TimeoutMilliseconds} ms.";
message.LogInfo();
return Task.FromResult(VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex));
}
catch (Exception ex)
{
string message = $"FindDieService: multiple die process image failed. {ex.Message}";
message.LogSysError();
return Task.FromResult(VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex));
}
}
private static FindDieImageRequest NormalizeRequest(FindDieImageRequest request)
{
FindDieImageRequest normalizedRequest = new FindDieImageRequest();
normalizedRequest.TimeoutMilliseconds = request.TimeoutMilliseconds > 0 ? request.TimeoutMilliseconds : 3000;
normalizedRequest.Parameters = CloneParameters(request.Parameters);
return normalizedRequest;
}
private static VisionProcessResult ValidateRequest(FindDieImageRequest request)
{
if (request.Parameters == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindDieService: parameters cannot be null.");
}
if (request.TimeoutMilliseconds <= 0 || request.TimeoutMilliseconds > 60000)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TimeoutInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionTimeoutInvalid,
$"FindDieService: TimeoutMilliseconds {request.TimeoutMilliseconds} is invalid. Expected range is [1, 60000].");
}
if (string.IsNullOrWhiteSpace(request.Parameters.TemplatePath))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.TemplatePathEmpty,
VisionAlarmIds.TemplatePathEmpty,
MessageKey.VisionTemplatePathEmpty,
"FindDieService: TemplatePath cannot be empty.");
}
if (string.IsNullOrWhiteSpace(request.Parameters.SerialNumber))
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindDieService: SerialNumber cannot be empty.");
}
if (request.Parameters.CurrentRuler == null)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
"FindDieService: CurrentRuler cannot be null.");
}
if (request.Parameters.DieWidth <= 0d || request.Parameters.DieHeight <= 0d)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.RequestInvalid,
VisionAlarmIds.RequestInvalid,
MessageKey.VisionRequestInvalid,
$"FindDieService: DieWidth {request.Parameters.DieWidth} and DieHeight {request.Parameters.DieHeight} must be greater than 0.");
}
if (request.Parameters.ScoreThreshold <= 0d || request.Parameters.ScoreThreshold > 1d)
{
return VisionProcessResult.Failure(
VisionFailureCategory.Validation,
VisionErrorCode.MinScoreInvalid,
VisionAlarmIds.TemplateMinScoreInvalid,
MessageKey.VisionTemplateMinScoreInvalid,
$"FindDieService: ScoreThreshold {request.Parameters.ScoreThreshold} is invalid. Expected range is (0, 1].");
}
return null;
}
private static FindDieParameters CloneParameters(FindDieParameters parameters)
{
if (parameters == null)
{
return new FindDieParameters();
}
FindDieParameters clone = new FindDieParameters();
clone.MinScore = parameters.MinScore;
clone.TemplatePath = parameters.TemplatePath;
clone.SerialNumber = parameters.SerialNumber;
clone.CurrentRuler = parameters.CurrentRuler == null
? null
: new Point(parameters.CurrentRuler.X, parameters.CurrentRuler.Y);
clone.OverlapX = parameters.OverlapX;
clone.OverlapY = parameters.OverlapY;
clone.DieWidth = parameters.DieWidth;
clone.DieHeight = parameters.DieHeight;
clone.ScoreThreshold = parameters.ScoreThreshold > 0d ? parameters.ScoreThreshold : parameters.MinScore;
clone.AngleTolerance = parameters.AngleTolerance;
return clone;
}
private static FindDieImageRequest CreateImageRequest(FindDieRequest request)
{
FindDieImageRequest imageRequest = new FindDieImageRequest();
imageRequest.TimeoutMilliseconds = request.TimeoutMilliseconds;
imageRequest.Parameters = CloneParameters(request.Parameters);
return imageRequest;
}
private static VisionProcessResult<FindDieResult> ExecuteSingleMatch(MxImage image, FindDieImageRequest request)
{
VisionProcessResult<FindDiesResult> multipleResult = ExecuteMultipleMatch(image, request);
if (!multipleResult.Succeeded)
{
return CreateFailureResult<FindDieResult>(multipleResult);
}
FindDieResult result = SelectPrimaryResult(multipleResult.Data);
if (result == null)
{
return VisionProcessResult<FindDieResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: no valid die result was found. TemplatePath={request.Parameters.TemplatePath}.");
}
return VisionProcessResult<FindDieResult>.Success(result);
}
private static VisionProcessResult<FindDiesResult> ExecuteMultipleMatch(MxImage image, FindDieImageRequest request)
{
Camera camera = image.Image;
if (camera == null)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
"FindDieService: input image does not contain a valid camera object.");
}
double[] uniqueRows = null;
double[] uniqueCols = null;
double[] uniqueAngles = null;
double[] overlapRows = null;
double[] overlapCols = null;
double[] overlapAngles = null;
double[] pixelRows = null;
double[] pixelCols = null;
double[] pixelAngles = null;
bool succeeded = JM1Manager.Instance.get_target_pos_nodisp(
camera,
request.Parameters.TemplatePath,
request.Parameters.SerialNumber,
request.Parameters.CurrentRuler,
request.Parameters.OverlapX,
request.Parameters.OverlapY,
request.Parameters.DieWidth,
request.Parameters.DieHeight,
request.Parameters.ScoreThreshold,
out uniqueRows,
out uniqueCols,
out uniqueAngles,
out overlapRows,
out overlapCols,
out overlapAngles,
out pixelRows,
out pixelCols,
out pixelAngles);
if (!succeeded)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die match failed. TemplatePath={request.Parameters.TemplatePath}.");
}
if (!HasConsistentResult(uniqueRows, uniqueCols, uniqueAngles) ||
!HasConsistentResult(overlapRows, overlapCols, overlapAngles) ||
!HasConsistentResult(pixelRows, pixelCols, pixelAngles))
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die match returned inconsistent result data. TemplatePath={request.Parameters.TemplatePath}.");
}
if (uniqueRows.Length + overlapRows.Length == 0)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die match returned no results. TemplatePath={request.Parameters.TemplatePath}.");
}
FindDiesResult result = CreateMultipleResult(uniqueRows, uniqueCols, uniqueAngles, overlapRows, overlapCols, overlapAngles, pixelRows, pixelCols, pixelAngles);
ApplyAngleTolerance(result, request.Parameters.AngleTolerance);
if (result.Items.Count == 0)
{
return VisionProcessResult<FindDiesResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateResultInvalid,
VisionAlarmIds.TemplateResultInvalid,
MessageKey.VisionTemplateMatchFailed,
$"FindDieService: die results were empty after angle filtering. TemplatePath={request.Parameters.TemplatePath}.");
}
return VisionProcessResult<FindDiesResult>.Success(result);
}
private static FindDiesResult CreateMultipleResult(
double[] uniqueRows,
double[] uniqueCols,
double[] uniqueAngles,
double[] overlapRows,
double[] overlapCols,
double[] overlapAngles,
double[] pixelRows,
double[] pixelCols,
double[] pixelAngles)
{
FindDiesResult result = new FindDiesResult();
AppendDieResults(result.Items, uniqueRows, uniqueCols, uniqueAngles, false);
AppendDieResults(result.Items, overlapRows, overlapCols, overlapAngles, true);
AppendPixelResults(result.PixelItems, pixelRows, pixelCols, pixelAngles);
return result;
}
private static void AppendDieResults(
IList<FindDieResult> destination,
double[] rows,
double[] cols,
double[] angles,
bool isOverlap)
{
if (destination == null || rows == null || cols == null || angles == null)
{
return;
}
for (int i = 0; i < rows.Length; i++)
{
FindDieResult item = new FindDieResult();
item.CenterX = cols[i];
item.CenterY = rows[i];
item.Angle = angles[i];
item.PixelX = double.NaN;
item.PixelY = double.NaN;
item.IsOverlap = isOverlap;
item.Match = CreateMatchResult(angles[i], isOverlap);
destination.Add(item);
}
}
private static void AppendPixelResults(
IList<FindDiePixelResult> destination,
double[] rows,
double[] cols,
double[] angles)
{
if (destination == null || rows == null || cols == null || angles == null)
{
return;
}
for (int i = 0; i < rows.Length; i++)
{
FindDiePixelResult item = new FindDiePixelResult();
item.PixelX = cols[i];
item.PixelY = rows[i];
item.Angle = angles[i];
destination.Add(item);
}
}
private static VisionMatchResult CreateMatchResult(double angle, bool isOverlap)
{
VisionMatchResult match = new VisionMatchResult();
match.OffsetX = 0d;
match.OffsetY = 0d;
match.Angle = angle;
match.Score = 0d;
match.Tag = isOverlap ? "Overlap" : "Unique";
return match;
}
private static void ApplyAngleTolerance(FindDiesResult result, double? angleTolerance)
{
if (result == null || !angleTolerance.HasValue || angleTolerance.Value <= 0d)
{
return;
}
List<double> itemAngles = new List<double>();
foreach (FindDieResult item in result.Items)
{
itemAngles.Add(item.Angle * 180.0d / Math.PI);
}
if (itemAngles.Count == 0)
{
return;
}
double averageAngle = itemAngles.Average();
List<FindDieResult> filteredItems = new List<FindDieResult>();
foreach (FindDieResult item in result.Items)
{
double angleDegree = item.Angle * 180.0d / Math.PI;
if (Math.Abs(angleDegree - averageAngle) <= angleTolerance.Value)
{
filteredItems.Add(item);
}
}
List<FindDiePixelResult> filteredPixelItems = new List<FindDiePixelResult>();
foreach (FindDiePixelResult item in result.PixelItems)
{
double angleDegree = item.Angle * 180.0d / Math.PI;
if (Math.Abs(angleDegree - averageAngle) <= angleTolerance.Value)
{
filteredPixelItems.Add(item);
}
}
result.Items.Clear();
foreach (FindDieResult item in filteredItems)
{
result.Items.Add(item);
}
result.PixelItems.Clear();
foreach (FindDiePixelResult item in filteredPixelItems)
{
result.PixelItems.Add(item);
}
}
private static FindDieResult SelectPrimaryResult(FindDiesResult result)
{
if (result == null || result.Items == null || result.Items.Count == 0)
{
return null;
}
foreach (FindDieResult item in result.Items)
{
if (!item.IsOverlap)
{
return item;
}
}
return result.Items[0];
}
private static bool HasConsistentResult(double[] rows, double[] cols, double[] angles)
{
if (rows == null || cols == null || angles == null)
{
return false;
}
if (rows.Length != cols.Length || rows.Length != angles.Length)
{
return false;
}
for (int i = 0; i < rows.Length; i++)
{
if (double.IsNaN(rows[i]) || double.IsInfinity(rows[i]) ||
double.IsNaN(cols[i]) || double.IsInfinity(cols[i]) ||
double.IsNaN(angles[i]) || double.IsInfinity(angles[i]))
{
return false;
}
}
return true;
}
private async Task<VisionProcessResult<TResult>> ExecuteWithCaptureAsync<TResult>(
CameraType cameraSource,
CameraCaptureOptions captureOptions,
FindDieImageRequest imageRequest,
Func<MxImage, FindDieImageRequest, CancellationToken, Task<VisionProcessResult<TResult>>> imageProcessor,
CancellationToken cancellationToken)
{
ImageCaptureResult captureResult = null;
MxImage image = null;
try
{
captureResult = await _imageCaptureService.CaptureAsync(
cameraSource,
captureOptions,
cancellationToken).ConfigureAwait(false);
if (captureResult == null)
{
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.NoFrame,
VisionAlarmIds.NoFrame,
MessageKey.VisionNoFrameReturned,
$"FindDieService: image capture returned null result for camera '{cameraSource}'.");
}
if (!captureResult.Succeeded)
{
return CreateFailureResult<TResult>(VisionResultMapper.CreateCaptureFailure(captureResult));
}
image = captureResult.Image;
if (image == null)
{
string message = $"FindDieService: image capture succeeded but returned null image for camera '{cameraSource}'.";
message.LogSysError();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Capture,
VisionErrorCode.ImageNull,
VisionAlarmIds.ImageNull,
MessageKey.VisionImageIsNull,
message);
}
return await imageProcessor(image, imageRequest, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
string message = $"FindDieService: capture orchestration was cancelled for camera '{cameraSource}'.";
message.LogInfo();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Cancelled,
VisionErrorCode.OperationCancelled,
null,
MessageKey.VisionOperationCancelled,
message,
ex);
}
catch (Exception ex)
{
string message = $"FindDieService: capture orchestration failed for camera '{cameraSource}'. {ex.Message}";
message.LogSysError();
return VisionProcessResult<TResult>.Failure(
VisionFailureCategory.Algorithm,
VisionErrorCode.TemplateMatchFailed,
VisionAlarmIds.TemplateMatchFailed,
MessageKey.VisionTemplateMatchFailed,
message,
ex);
}
finally
{
if (image != null)
{
image.Dispose();
}
}
}
private static VisionProcessResult<TData> CreateFailureResult<TData>(VisionProcessResult result)
{
return VisionProcessResult<TData>.Failure(
result.FailureCategory,
result.ErrorCode,
result.AlarmId,
result.UserMessageKey,
result.Message,
result.Exception);
}
}
}