1123 lines
23 KiB
C#
1123 lines
23 KiB
C#
///
|
|
/// Copyright (c) 2017 Sensus Slovensko a.s.
|
|
///
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Common;
|
|
using Config.Entities;
|
|
using log4net;
|
|
using TBF.Rig.Network.Camera.CJMS11.POJO;
|
|
using TBF.Rig.Network.Telnet;
|
|
|
|
namespace TBF.Rig.Network.Camera.CJMS11
|
|
{
|
|
public class GrabImagesOp : IOperation
|
|
{
|
|
private static readonly ILog log = LogManager.GetLogger(typeof(GrabImagesOp));
|
|
|
|
private enum GrabOperationMode
|
|
{
|
|
None,
|
|
SingleImage,
|
|
SendImages
|
|
}
|
|
|
|
|
|
private readonly Camera camera;
|
|
private readonly string[] imgFileNames;
|
|
private readonly bool blackAndWhite;
|
|
private readonly bool lowResolution;
|
|
private readonly ImageRotation imageRotation;
|
|
|
|
private readonly object imageSync = new object();
|
|
|
|
private bool grabImageCommandSent;
|
|
private bool transferringGrabbedImage;
|
|
|
|
private GrabOperationMode operationMode;
|
|
|
|
private Image grabedImage;
|
|
|
|
private readonly Dictionary<int, Image> grabedImages = new Dictionary<int, Image>();
|
|
|
|
private Task<Image> grabImageTask;
|
|
private Task<IReadOnlyList<TimedCameraImage>> sendImagesTask;
|
|
private Task saveImageTask;
|
|
|
|
private IReadOnlyList<TimedCameraImage> timedCameraImages;
|
|
private readonly List<string> savedFileNames = new List<string>();
|
|
|
|
public override string ToString() { return "GrabImageOp()"; }
|
|
|
|
public GrabImagesOp(
|
|
Camera camera,
|
|
string[] imgFileNames,
|
|
bool blackAndWhite,
|
|
bool lowResolution,
|
|
ImageRotation imageRotation)
|
|
{
|
|
if (camera == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(camera));
|
|
}
|
|
|
|
this.camera = camera;
|
|
this.imgFileNames = imgFileNames;
|
|
this.blackAndWhite = blackAndWhite;
|
|
this.lowResolution = lowResolution;
|
|
this.imageRotation = imageRotation;
|
|
|
|
SubscribeEvents();
|
|
}
|
|
|
|
|
|
|
|
private void SubscribeEvents()
|
|
{
|
|
Camera.ImageCameraHandler -= ImageReceived;
|
|
Camera.ImageCameraHandler += ImageReceived;
|
|
|
|
Camera.ImagesCameraHandler -= ImagesReceived;
|
|
Camera.ImagesCameraHandler += ImagesReceived;
|
|
}
|
|
|
|
private void ImageReceived(
|
|
object sender,
|
|
PromptReceivedImageEventArgs args)
|
|
{
|
|
if (args == null || args.idxCamera != camera.CameraIdx || args.image == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Tento obrázok používame iba pre kompatibilitu
|
|
* s existujúcim eventovým mechanizmom.
|
|
*
|
|
* Hlavným zdrojom výsledku je Task<Image>.
|
|
*/
|
|
lock (imageSync)
|
|
{
|
|
grabedImage = args.image;
|
|
}
|
|
}
|
|
|
|
private void ImagesReceived( object sender, PromptReceivedImagesEventArgs args)
|
|
{
|
|
if (args == null || args.idxCamera != camera.CameraIdx || args.image == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Náhľadové obrázky z eventu nebudeme ukladať
|
|
* na disk. Originálne obrázky dostaneme vo
|
|
* výsledku sendImagesTask.
|
|
*/
|
|
lock (imageSync)
|
|
{
|
|
grabedImage = args.image;
|
|
grabedImages[args.idxImage] = args.image;
|
|
}
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
SubscribeEvents();
|
|
|
|
grabImageCommandSent = false;
|
|
transferringGrabbedImage = false;
|
|
|
|
operationMode = GrabOperationMode.None;
|
|
|
|
grabImageTask = null;
|
|
sendImagesTask = null;
|
|
saveImageTask = null;
|
|
|
|
timedCameraImages = null;
|
|
|
|
lock (imageSync)
|
|
{
|
|
grabedImage = null;
|
|
grabedImages.Clear();
|
|
}
|
|
|
|
savedFileNames.Clear();
|
|
|
|
DeletePreviousOutputFiles();
|
|
}
|
|
|
|
|
|
public Event Run()
|
|
{
|
|
if (!HasValidOutputFileName())
|
|
{
|
|
log.Warn(
|
|
$"No output image file configured: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabPassed;
|
|
}
|
|
|
|
/*
|
|
* 1. Spustenie príslušnej operácie.
|
|
*/
|
|
if (!grabImageCommandSent)
|
|
{
|
|
return StartGrabOperation();
|
|
}
|
|
|
|
/*
|
|
* 2. Spracovanie podľa režimu.
|
|
*/
|
|
switch (operationMode)
|
|
{
|
|
case GrabOperationMode.SingleImage:
|
|
return RunSingleImageMode();
|
|
|
|
case GrabOperationMode.SendImages:
|
|
return RunSendImagesMode();
|
|
|
|
default:
|
|
log.Error(
|
|
$"Grab operation mode was not selected: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
}
|
|
|
|
private Event StartGrabOperation()
|
|
{
|
|
grabImageCommandSent = true;
|
|
|
|
try
|
|
{
|
|
int requestedImageCount =
|
|
GetRequestedImageCount();
|
|
|
|
if (requestedImageCount <= 1)
|
|
{
|
|
operationMode =
|
|
GrabOperationMode.SingleImage;
|
|
|
|
log.Debug(
|
|
$"Starting single asynchronous grab: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
grabImageTask =
|
|
camera.StartGrabImageListenerAsync(
|
|
retryUntilValidImage: true,
|
|
maxAttempts: 5,
|
|
retryDelayMs: 200);
|
|
}
|
|
else
|
|
{
|
|
operationMode =
|
|
GrabOperationMode.SendImages;
|
|
|
|
int delayMs =
|
|
GetRequestedDelayMs();
|
|
|
|
string taskId =
|
|
CreateSendTaskId();
|
|
|
|
log.Debug(
|
|
$"Starting send images operation: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"count={requestedImageCount}, " +
|
|
$"delayMs={delayMs}, " +
|
|
$"taskId={taskId}");
|
|
|
|
sendImagesTask =
|
|
camera.StartSendImagesPreviewAsync(
|
|
count: requestedImageCount,
|
|
delayMs: delayMs,
|
|
taskId: taskId);
|
|
}
|
|
|
|
return Event.CameraBusy;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Error(
|
|
$"Cannot start image operation: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"mode={operationMode}, " +
|
|
$"error={exc.Message}",
|
|
exc);
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
}
|
|
|
|
private Event RunSingleImageMode()
|
|
{
|
|
if (grabImageTask == null)
|
|
{
|
|
log.Error(
|
|
$"Single grab task was not created: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (!grabImageTask.IsCompleted)
|
|
{
|
|
return Event.CameraBusy;
|
|
}
|
|
|
|
if (grabImageTask.IsCanceled)
|
|
{
|
|
log.Warn(
|
|
$"Single grab task was cancelled: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (grabImageTask.IsFaulted)
|
|
{
|
|
log.Error(
|
|
$"Single grab task failed: " +
|
|
$"cameraIdx={camera.CameraIdx}",
|
|
grabImageTask.Exception?.Flatten());
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (!transferringGrabbedImage)
|
|
{
|
|
Image imageToSave;
|
|
|
|
try
|
|
{
|
|
imageToSave =
|
|
grabImageTask
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Error(
|
|
$"Cannot obtain grabbed image: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"error={exc.Message}",
|
|
exc);
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (imageToSave == null)
|
|
{
|
|
log.Error(
|
|
$"Single grab completed without image: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
lock (imageSync)
|
|
{
|
|
grabedImage = imageToSave;
|
|
}
|
|
|
|
string outputFileName =
|
|
imgFileNames[0];
|
|
|
|
try
|
|
{
|
|
saveImageTask =
|
|
camera.StartSaveImageToFileAsync(
|
|
imageToSave,
|
|
outputFileName);
|
|
|
|
savedFileNames.Clear();
|
|
savedFileNames.Add(
|
|
outputFileName);
|
|
|
|
transferringGrabbedImage = true;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Error(
|
|
$"Cannot start single image save: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"file={outputFileName}, " +
|
|
$"error={exc.Message}",
|
|
exc);
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
return Event.CameraBusy;
|
|
}
|
|
|
|
return CheckSaveTaskAndFinish();
|
|
}
|
|
|
|
|
|
|
|
private Event RunSendImagesMode()
|
|
{
|
|
if (sendImagesTask == null)
|
|
{
|
|
log.Error(
|
|
$"Send images task was not created: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (!sendImagesTask.IsCompleted)
|
|
{
|
|
return Event.CameraBusy;
|
|
}
|
|
|
|
if (sendImagesTask.IsCanceled)
|
|
{
|
|
log.Warn(
|
|
$"Send images task was cancelled: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (sendImagesTask.IsFaulted)
|
|
{
|
|
log.Error(
|
|
$"Send images task failed: " +
|
|
$"cameraIdx={camera.CameraIdx}",
|
|
sendImagesTask.Exception?.Flatten());
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (!transferringGrabbedImage)
|
|
{
|
|
try
|
|
{
|
|
timedCameraImages =
|
|
sendImagesTask
|
|
.GetAwaiter()
|
|
.GetResult();
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Error(
|
|
$"Cannot obtain send image results: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"error={exc.Message}",
|
|
exc);
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (timedCameraImages == null ||
|
|
timedCameraImages.Count == 0)
|
|
{
|
|
log.Error(
|
|
$"Send operation returned no images: " +
|
|
$"cameraIdx={camera.CameraIdx}");
|
|
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
int requestedCount =
|
|
GetRequestedImageCount();
|
|
|
|
if (timedCameraImages.Count !=
|
|
requestedCount)
|
|
{
|
|
log.Error(
|
|
$"Send operation returned unexpected image count: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"requested={requestedCount}, " +
|
|
$"received={timedCameraImages.Count}");
|
|
|
|
DisposeTimedImages();
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
IReadOnlyList<TimedCameraImage>
|
|
orderedImages =
|
|
timedCameraImages
|
|
.OrderBy(image => image.Index)
|
|
.ToList();
|
|
|
|
try
|
|
{
|
|
/*
|
|
* Camera.StartSaveImageToFileAsync používa
|
|
* jeden interný save task, preto viac
|
|
* obrázkov uložíme sekvenčne v jednom
|
|
* samostatnom tasku.
|
|
*/
|
|
saveImageTask =
|
|
Task.Run(
|
|
() => SaveTimedImages(
|
|
orderedImages));
|
|
|
|
transferringGrabbedImage = true;
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Error(
|
|
$"Cannot start send images save task: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"error={exc.Message}",
|
|
exc);
|
|
|
|
DisposeTimedImages();
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
return Event.CameraBusy;
|
|
}
|
|
|
|
return CheckSaveTaskAndFinish();
|
|
}
|
|
|
|
private void SaveTimedImages( IReadOnlyList<TimedCameraImage> images)
|
|
{
|
|
if (images == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(images));
|
|
}
|
|
|
|
if (images.Count == 0)
|
|
{
|
|
throw new ArgumentException( "No timed camera images were provided.", nameof(images));
|
|
}
|
|
|
|
if (!HasValidOutputFileName())
|
|
{
|
|
throw new InvalidOperationException( "No base output image file name is configured.");
|
|
}
|
|
|
|
savedFileNames.Clear();
|
|
|
|
IReadOnlyList<TimedCameraImage> orderedImages =
|
|
images
|
|
.Where(image => image != null)
|
|
.OrderBy(image => image.Index)
|
|
.ToList();
|
|
|
|
if (orderedImages.Count != images.Count)
|
|
{
|
|
throw new InvalidOperationException( $"One or more timed camera images are null. cameraIdx={camera.CameraIdx}, " +
|
|
$"expected={images.Count}, valid={orderedImages.Count}");
|
|
}
|
|
|
|
log.Debug( $"Starting timed images save: cameraIdx={camera.CameraIdx}, imagesCount={orderedImages.Count}, " +
|
|
$"baseFile='{imgFileNames[0]}'");
|
|
|
|
foreach (TimedCameraImage timedImage in orderedImages)
|
|
{
|
|
if (timedImage.Image == null)
|
|
{
|
|
throw new InvalidOperationException( $"Timed camera image contains no image data: " +
|
|
$"cameraIdx={camera.CameraIdx}, imageIndex={timedImage.Index}, frameTime={timedImage.FrameTime:O}");
|
|
}
|
|
|
|
/*
|
|
* Každý obrázok uložíme pod unikátnym názvom
|
|
* obsahujúcim index obrázka a časovú značku.
|
|
*/
|
|
string timedFileName = CreateTimedImageFileName( timedImage, orderedImages.Count);
|
|
|
|
log.Debug( $"Saving timed camera image: cameraIdx={camera.CameraIdx}, imageIndex={timedImage.Index}, " +
|
|
$"frameTime={timedImage.FrameTime:O}, file='{timedFileName}'");
|
|
|
|
camera.SaveImageToFile(
|
|
timedImage.Image,
|
|
timedFileName);
|
|
|
|
savedFileNames.Add(
|
|
timedFileName);
|
|
|
|
log.Debug( $"Timed camera image saved successfully: cameraIdx={camera.CameraIdx}, " +
|
|
$"imageIndex={timedImage.Index}, file='{timedFileName}'");
|
|
|
|
/*
|
|
* Prvý obrázok uložíme ešte raz pod pôvodným názvom.
|
|
*
|
|
* Tento súbor používa existujúci dialóg cez
|
|
* startImages[] alebo endImages[].
|
|
*
|
|
* Napríklad:
|
|
* TestAdvance-1-start.bmp
|
|
*/
|
|
if (timedImage.Index == 1)
|
|
{
|
|
string compatibilityFileName =
|
|
imgFileNames[0];
|
|
|
|
log.Debug($"Saving first timed image under compatibility name: cameraIdx={camera.CameraIdx}, " +
|
|
$"imageIndex={timedImage.Index}, frameTime={timedImage.FrameTime:O}, file='{compatibilityFileName}'");
|
|
|
|
camera.SaveImageToFile(timedImage.Image, compatibilityFileName);
|
|
savedFileNames.Add(compatibilityFileName);
|
|
|
|
log.Debug($"First timed image saved under compatibility name: cameraIdx={camera.CameraIdx}, " +
|
|
$"imageIndex={timedImage.Index}, file='{compatibilityFileName}'");
|
|
}
|
|
}
|
|
|
|
log.Debug(
|
|
$"Timed images save completed: cameraIdx={camera.CameraIdx}, receivedImages={orderedImages.Count}, " +
|
|
$"savedFiles={savedFileNames.Count}");
|
|
}
|
|
|
|
private Event CheckSaveTaskAndFinish()
|
|
{
|
|
if (saveImageTask == null)
|
|
{
|
|
log.Error( $"Save image task was not created: cameraIdx={camera.CameraIdx}, mode={operationMode}");
|
|
FinishOperation();
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (!saveImageTask.IsCompleted)
|
|
{
|
|
return Event.CameraBusy;
|
|
}
|
|
|
|
if (saveImageTask.IsCanceled)
|
|
{
|
|
log.Warn( $"Save image task was cancelled: cameraIdx={camera.CameraIdx}, mode={operationMode}");
|
|
|
|
DisposeOperationImages();
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
if (saveImageTask.IsFaulted)
|
|
{
|
|
log.Error( $"Save image task failed: cameraIdx={camera.CameraIdx}, mode={operationMode}",
|
|
saveImageTask.Exception?.Flatten());
|
|
|
|
DisposeOperationImages();
|
|
FinishOperation();
|
|
|
|
return Event.GrabFailed;
|
|
}
|
|
|
|
bool allFilesSaved = VerifySavedFiles();
|
|
|
|
if (allFilesSaved)
|
|
{
|
|
log.Debug( $"Image operation completed successfully: cameraIdx={camera.CameraIdx}, " +
|
|
$"mode={operationMode}, savedFiles={savedFileNames.Count}");
|
|
}
|
|
else
|
|
{
|
|
log.Error( $"One or more image files were not saved: cameraIdx={camera.CameraIdx}, mode={operationMode}");
|
|
}
|
|
|
|
DisposeOperationImages();
|
|
FinishOperation();
|
|
|
|
return allFilesSaved
|
|
? Event.GrabPassed
|
|
: Event.GrabFailed;
|
|
}
|
|
|
|
private bool VerifySavedFiles()
|
|
{
|
|
if (savedFileNames.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (string fileName
|
|
in savedFileNames)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrWhiteSpace(fileName) ||
|
|
!File.Exists(fileName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var fileInfo =
|
|
new FileInfo(fileName);
|
|
|
|
if (fileInfo.Length <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Error(
|
|
$"Cannot verify saved image: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"file={fileName}, " +
|
|
$"error={exc.Message}",
|
|
exc);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private string CreateTimedImageFileName(
|
|
TimedCameraImage timedImage,
|
|
int totalImageCount)
|
|
{
|
|
if (timedImage == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(timedImage));
|
|
}
|
|
|
|
if (!HasValidOutputFileName())
|
|
{
|
|
throw new InvalidOperationException(
|
|
"No base output image file name is configured.");
|
|
}
|
|
|
|
int imagePosition = timedImage.Index;
|
|
|
|
if (imagePosition <= 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Invalid image position: {imagePosition}.");
|
|
}
|
|
|
|
// Prvý obrázok musí zostať pod pôvodným názvom,
|
|
// pretože tento názov používa startImages/endImages.
|
|
if (imagePosition == 1)
|
|
{
|
|
return imgFileNames[0];
|
|
}
|
|
|
|
string configuredFileName = imgFileNames[0];
|
|
|
|
string directory =
|
|
Path.GetDirectoryName(configuredFileName);
|
|
|
|
string baseName =
|
|
Path.GetFileNameWithoutExtension(configuredFileName);
|
|
|
|
string extension =
|
|
Path.GetExtension(configuredFileName);
|
|
|
|
if (string.IsNullOrWhiteSpace(extension))
|
|
{
|
|
extension = ".png";
|
|
}
|
|
|
|
DateTime timestampUtc =
|
|
timedImage.FrameTime.HasValue
|
|
? timedImage.FrameTime.Value.UtcDateTime
|
|
: DateTime.UtcNow;
|
|
|
|
string timestampText =
|
|
timestampUtc.ToString(
|
|
"yyyyMMdd_HHmmss_fff",
|
|
CultureInfo.InvariantCulture);
|
|
|
|
int digits = Math.Max(
|
|
2,
|
|
totalImageCount.ToString(
|
|
CultureInfo.InvariantCulture).Length);
|
|
|
|
string indexText =
|
|
imagePosition.ToString(
|
|
"D" + digits,
|
|
CultureInfo.InvariantCulture);
|
|
|
|
string newFileName =
|
|
$"{baseName}_img{indexText}_{timestampText}{extension}";
|
|
|
|
return string.IsNullOrWhiteSpace(directory)
|
|
? newFileName
|
|
: Path.Combine(directory, newFileName);
|
|
}
|
|
|
|
private string GetConfiguredFileName(
|
|
int zeroBasedIndex)
|
|
{
|
|
if (imgFileNames != null &&
|
|
zeroBasedIndex >= 0 &&
|
|
zeroBasedIndex < imgFileNames.Length &&
|
|
!string.IsNullOrWhiteSpace(
|
|
imgFileNames[zeroBasedIndex]))
|
|
{
|
|
return imgFileNames[zeroBasedIndex];
|
|
}
|
|
|
|
/*
|
|
* Ak nie je pripravený názov pre každý obrázok,
|
|
* použijeme prvý názov ako základ.
|
|
*/
|
|
return imgFileNames[0];
|
|
}
|
|
|
|
private int GetRequestedImageCount()
|
|
{
|
|
var roi = camera.CamRoi;
|
|
|
|
if (roi == null)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return Math.Max(1, roi.RoiPicCount);
|
|
}
|
|
|
|
private int GetRequestedDelayMs()
|
|
{
|
|
const int MinDelayMs = 500;
|
|
|
|
var roi = camera.CamRoi;
|
|
|
|
if (roi == null)
|
|
{
|
|
return MinDelayMs;
|
|
}
|
|
|
|
int configuredDelay = roi.RoiDeltaTimeMs;
|
|
|
|
if (configuredDelay < MinDelayMs)
|
|
{
|
|
log.Warn( $"Configured ROI delay {configuredDelay} ms is below minimum. Using {MinDelayMs} ms instead.");
|
|
}
|
|
|
|
return Math.Max(MinDelayMs, configuredDelay);
|
|
}
|
|
|
|
private string CreateSendTaskId()
|
|
{
|
|
/*
|
|
* Timestamp + kamera + krátka časť GUID.
|
|
* ID je čitateľné a zároveň odolné voči
|
|
* kolíziám pri rýchlom opakovaní.
|
|
*/
|
|
string timestamp = DateTime.UtcNow.ToString( "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture);
|
|
|
|
string uniquePart = Guid.NewGuid() .ToString("N") .Substring(0, 8);
|
|
|
|
return $"{timestamp}_cam{camera.CameraIdx}_" + uniquePart;
|
|
}
|
|
|
|
private bool HasValidOutputFileName()
|
|
{
|
|
return
|
|
imgFileNames != null &&
|
|
imgFileNames.Length > 0 &&
|
|
!string.IsNullOrWhiteSpace(
|
|
imgFileNames[0]);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void Stop()
|
|
{
|
|
try
|
|
{
|
|
switch (operationMode)
|
|
{
|
|
case GrabOperationMode.SingleImage:
|
|
camera.CancelGrabImage();
|
|
break;
|
|
|
|
case GrabOperationMode.SendImages:
|
|
camera.CancelSendImagesPreview();
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Warn( $"Cancel image operation failed: cameraIdx={camera.CameraIdx}, mode={operationMode}, " +
|
|
$"error={exc.Message}");
|
|
}
|
|
finally
|
|
{
|
|
FinishOperation();
|
|
}
|
|
}
|
|
|
|
private void FinishOperation()
|
|
{
|
|
UnsubscribeEvents();
|
|
}
|
|
|
|
private void UnsubscribeEvents()
|
|
{
|
|
Camera.ImageCameraHandler -= ImageReceived;
|
|
Camera.ImagesCameraHandler -= ImagesReceived;
|
|
}
|
|
|
|
private static string AppendTimestampToFileName(
|
|
string originalFileName,
|
|
DateTime timestamp)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(originalFileName))
|
|
{
|
|
throw new ArgumentException(
|
|
"File name is required.",
|
|
nameof(originalFileName));
|
|
}
|
|
|
|
string directory =
|
|
Path.GetDirectoryName(originalFileName);
|
|
|
|
string fileNameWithoutExtension =
|
|
Path.GetFileNameWithoutExtension(originalFileName);
|
|
|
|
string extension =
|
|
Path.GetExtension(originalFileName);
|
|
|
|
string timestampText =
|
|
timestamp
|
|
.ToUniversalTime()
|
|
.ToString(
|
|
"yyyyMMdd_HHmmss_fff",
|
|
System.Globalization.CultureInfo.InvariantCulture);
|
|
|
|
string newFileName =
|
|
$"{fileNameWithoutExtension}_{timestampText}{extension}";
|
|
|
|
return string.IsNullOrWhiteSpace(directory)
|
|
? newFileName
|
|
: Path.Combine(directory, newFileName);
|
|
}
|
|
|
|
|
|
private void DisposeOperationImages()
|
|
{
|
|
if (operationMode ==
|
|
GrabOperationMode.SendImages)
|
|
{
|
|
DisposeTimedImages();
|
|
return;
|
|
}
|
|
|
|
Image imageToDispose = null;
|
|
|
|
lock (imageSync)
|
|
{
|
|
imageToDispose = grabedImage;
|
|
grabedImage = null;
|
|
}
|
|
|
|
try
|
|
{
|
|
imageToDispose?.Dispose();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private void DisposeTimedImages()
|
|
{
|
|
if (timedCameraImages == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (TimedCameraImage timedImage
|
|
in timedCameraImages)
|
|
{
|
|
try
|
|
{
|
|
timedImage?.Image?.Dispose();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
timedCameraImages = null;
|
|
}
|
|
|
|
private void DeletePreviousOutputFiles()
|
|
{
|
|
if (!HasValidOutputFileName())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var filesToDelete = new HashSet<string>(
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (string configuredFileName in imgFileNames)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(configuredFileName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string fullConfiguredFileName;
|
|
|
|
try
|
|
{
|
|
fullConfiguredFileName =
|
|
Path.GetFullPath(configuredFileName);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Warn(
|
|
$"Invalid configured image file name: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"file='{configuredFileName}', " +
|
|
$"error={exc.Message}");
|
|
|
|
continue;
|
|
}
|
|
|
|
string directory =
|
|
Path.GetDirectoryName(fullConfiguredFileName);
|
|
|
|
string baseName =
|
|
Path.GetFileNameWithoutExtension(
|
|
fullConfiguredFileName);
|
|
|
|
string extension =
|
|
Path.GetExtension(
|
|
fullConfiguredFileName);
|
|
|
|
if (string.IsNullOrWhiteSpace(directory) ||
|
|
string.IsNullOrWhiteSpace(baseName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!Directory.Exists(directory))
|
|
{
|
|
log.Debug(
|
|
$"Image output directory does not exist; " +
|
|
$"nothing to delete: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"directory='{directory}'");
|
|
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Pôvodný kompatibilný súbor:
|
|
*
|
|
* TestAdvance-1-start.bmp
|
|
*/
|
|
filesToDelete.Add(
|
|
fullConfiguredFileName);
|
|
|
|
/*
|
|
* Časované súbory:
|
|
*
|
|
* TestAdvance-1-start_img01_20260715_120000_100.bmp
|
|
* TestAdvance-1-start_img02_20260715_120000_600.bmp
|
|
*
|
|
* Časovú značku neanalyzujeme.
|
|
* Hľadáme podľa stabilného prefixu názvu.
|
|
*/
|
|
string searchPattern =
|
|
$"{baseName}_img*{extension}";
|
|
|
|
try
|
|
{
|
|
foreach (string foundFile in
|
|
Directory.GetFiles(
|
|
directory,
|
|
searchPattern,
|
|
SearchOption.TopDirectoryOnly))
|
|
{
|
|
filesToDelete.Add(
|
|
Path.GetFullPath(foundFile));
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Warn(
|
|
$"Cannot search previous timed images: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"directory='{directory}', " +
|
|
$"pattern='{searchPattern}', " +
|
|
$"error={exc.Message}");
|
|
}
|
|
}
|
|
|
|
log.Debug(
|
|
$"Previous image files found for deletion: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"count={filesToDelete.Count}");
|
|
|
|
foreach (string fileName in filesToDelete)
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(fileName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
File.Delete(fileName);
|
|
|
|
log.Debug(
|
|
$"Previous image file deleted: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"file='{fileName}'");
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
log.Warn(
|
|
$"Cannot delete previous image file: " +
|
|
$"cameraIdx={camera.CameraIdx}, " +
|
|
$"file='{fileName}', " +
|
|
$"error={exc.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
} |