camera send implemenatation, dialog support, ver 3.9.3134

This commit is contained in:
Michal Buzik 2026-07-15 16:12:21 +02:00
parent 34141481e3
commit 71ff74122e
12 changed files with 2401 additions and 235 deletions

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("3.9.3133.0")]
[assembly: AssemblyFileVersion("3.9.3133.0")]
[assembly: AssemblyVersion("3.9.3134.0")]
[assembly: AssemblyFileVersion("3.9.3134.0")]

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using log4net;
@ -70,6 +71,9 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
int lastWmNr1;
Bitmap lastImage; /// non-zoomed image
Bitmap zoomedImage;
private readonly List<string> currentImageGroup = new List<string>();//multiple pictures
private int currentImageGroupIndex;
private bool mouseOverPictureBox;
Thread imageProcessingThread; /// Thread to process images
public delegate void OcrTextObtainedDelegate(int ix, bool isEnd, string text);
@ -96,6 +100,12 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
public TestStartEndForm()
{
InitializeComponent();
KeyPreview = true;
pictureBox1.MouseWheel += pictureBox1_MouseWheel;
pictureBox1.MouseEnter += pictureBox1_MouseEnter;
pictureBox1.MouseLeave += pictureBox1_MouseLeave;
this.Icon = Properties.Resources.TBF_icon;
@ -483,15 +493,46 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
startTextBoxes[i].TextChanged += new System.EventHandler(this.startTextBoxX_TextChanged);
startTextBoxes[i].KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.startTextBoxX_KeyPress);
startTextBoxes[i].MouseDown += new System.Windows.Forms.MouseEventHandler(this.startTextBoxX_MouseDown);
startTextBoxes[i].Enter += startTextBoxX_Enter;
}
private void startTextBoxX_Enter(object sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) { return; }
int physPos = PhysPos(textBox, startTextBoxes);
if (physPos >= 0)
{
LoadImage(sender, Tst.Start, physPos);
}
}
private void EndTextBox_EnableHanders(int i)
{
endTextBoxes[i].AcceptsTabChanged += new System.EventHandler(this.endTextBoxX_AcceptsTabChanged);
endTextBoxes[i].TextChanged += new System.EventHandler(this.endTextBoxX_TextChanged);
endTextBoxes[i].KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.endTextBoxX_KeyPress);
endTextBoxes[i].MouseDown += new System.Windows.Forms.MouseEventHandler(this.endTextBoxX_AcceptsTabChanged);
endTextBoxes[i].Enter += endTextBoxX_Enter;
}
private void endTextBoxX_Enter(object sender, EventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) { return; }
int physPos = PhysPos(textBox, endTextBoxes);
if (physPos >= 0)
{
LoadImage(sender, Tst.End, physPos);
}
}
private void TestStartEndForm_FormClosed(object sender, FormClosedEventArgs e)
{
@ -763,7 +804,7 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
TextBox clickedTextBox = sender as TextBox;
if (clickedTextBox != null)
{
var physPos = PhysPos(clickedTextBox, startTextBoxes);
var physPos = PhysPos(clickedTextBox, endTextBoxes);//startTextBoxes);
if (physPos < 0)
{
@ -1069,7 +1110,7 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
TextBox clickedTextBox = sender as TextBox;
if (clickedTextBox != null)
{
var physPos = PhysPos(clickedTextBox, startTextBoxes);
var physPos = PhysPos(clickedTextBox, endTextBoxes);//startTextBoxes);
if (physPos < 0)
{
@ -1134,65 +1175,479 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
/// </summary>
/// <param name="startOrEnd">Tst.Start or Tst.End</param>
/// <param name="physPos">0..23</param>
void LoadImage(object sender, Tst startOrEnd, int physPos)
{
int wmNr1b = phys2wm[physPos]; /// 0 means no WM
if (wmNr1b <= 0) return;
void LoadImage( object sender, Tst startOrEnd, int physPos)
{
if (physPos < 0 || physPos >= phys2wm.Length)
{
return;
}
wmNr0b = wmNr1b - 1;
int wmNr1b = phys2wm[physPos];
string imageFileName;
if (startOrEnd == Tst.Start && startImages[wmNr0b] != null)
imageFileName = startImages[wmNr0b];
else if (startOrEnd == Tst.End && endImages[wmNr0b] != null)
imageFileName = endImages[wmNr0b];
else
imageFileName = string.Empty;
// Hodnota 0 znamená, že na tejto fyzickej pozícii nie je vodomer.
if (wmNr1b <= 0)
{
return;
}
if (imageFileName == lastImageName)
{
return;
}
wmNr0b = wmNr1b - 1;
lastImageName = imageFileName;
lastWmNr1 = wmNr1b;
string imageFileName = GetBaseImageFileName(
startOrEnd,
wmNr0b);
if (string.IsNullOrEmpty(imageFileName) || !File.Exists(imageFileName))
{
lastImage = null;
}
else
{
using (Bitmap tempBmp = Image.FromFile(imageFileName) as Bitmap)
lastWmNr1 = wmNr1b;
if (string.IsNullOrWhiteSpace(imageFileName))
{
ClearImageGroup();
LoadImageFile(null);
return;
}
/*
* Ak používateľ zvolil iný vodomer alebo Start/End obrázok,
* vytvoríme novú skupinu:
*
* TestAdvance-1-start.bmp
* TestAdvance-1-start_img01_....bmp
* TestAdvance-1-start_img02_....bmp
*/
if (!string.Equals(
imageFileName,
lastImageName,
StringComparison.OrdinalIgnoreCase))
{
lastImageName = imageFileName;
LoadImageGroup(imageFileName);
// Pôvodný kompatibilný obrázok bude zobrazený ako prvý.
currentImageGroupIndex = 0;
}
else
{
/*
* Súbory mohli vzniknúť po prvom otvorení dialógu,
* preto skupinu pri opätovnom výbere obnovíme.
*/
RefreshCurrentImageGroup();
}
if (currentImageGroup.Count == 0)
{
LoadImageFile(null);
return;
}
currentImageGroupIndex = Math.Max(
0,
Math.Min(
currentImageGroupIndex,
currentImageGroup.Count - 1));
LoadImageFile(
currentImageGroup[currentImageGroupIndex]);
}
/// <summary>
/// base name of image file
/// </summary>
/// <param name="startOrEnd"></param>
/// <param name="waterMeterIndex"></param>
/// <returns></returns>
private string GetBaseImageFileName(
Tst startOrEnd,
int waterMeterIndex)
{
if (waterMeterIndex < 0 ||
waterMeterIndex >= WaterMetersCount)
{
return null;
}
if (startOrEnd == Tst.Start)
{
if (startImages == null ||
waterMeterIndex >= startImages.Length)
{
Rectangle rect;
if (tempBmp.Height < tempBmp.Width)
return null;
}
return startImages[waterMeterIndex];
}
if (startOrEnd == Tst.End)
{
if (endImages == null ||
waterMeterIndex >= endImages.Length)
{
return null;
}
return endImages[waterMeterIndex];
}
return null;
}
/// <summary>
/// load group of images
/// </summary>
/// <param name="baseImageFileName"></param>
private void LoadImageGroup(
string baseImageFileName)
{
currentImageGroup.Clear();
currentImageGroupIndex = 0;
if (string.IsNullOrWhiteSpace(baseImageFileName))
{
return;
}
string fullBaseFileName;
try
{
fullBaseFileName =
Path.GetFullPath(baseImageFileName);
}
catch (Exception exc)
{
log.Warn(
$"Cannot create image group from invalid path: " +
$"file='{baseImageFileName}', " +
$"error={exc.Message}");
return;
}
string directory =
Path.GetDirectoryName(fullBaseFileName);
string baseName =
Path.GetFileNameWithoutExtension(fullBaseFileName);
string extension =
Path.GetExtension(fullBaseFileName);
if (string.IsNullOrWhiteSpace(directory) ||
string.IsNullOrWhiteSpace(baseName))
{
return;
}
/*
* Pôvodný obrázok pridáme ako prvý.
*/
if (File.Exists(fullBaseFileName))
{
currentImageGroup.Add(fullBaseFileName);
}
if (!Directory.Exists(directory))
{
return;
}
string searchPattern =
$"{baseName}_img*{extension}";
try
{
List<string> timedImages =
Directory
.GetFiles(
directory,
searchPattern,
SearchOption.TopDirectoryOnly)
.OrderBy(GetTimedImageIndex)
.ThenBy(
fileName => fileName,
StringComparer.OrdinalIgnoreCase)
.ToList();
foreach (string timedImage in timedImages)
{
if (!currentImageGroup.Contains(
timedImage,
StringComparer.OrdinalIgnoreCase))
{
int hght = tempBmp.Height;
int leftMargin = (tempBmp.Width - hght) / 2;
rect = new Rectangle(leftMargin, 0, hght, hght);
}
else
{
int hght = tempBmp.Height;
int wdth = tempBmp.Width;
int margin = (hght - wdth) / 2;
rect = new Rectangle(0, margin, wdth, wdth);
}
//int hght = tempBmp.Height;
//int leftMargin = (tempBmp.Width - hght) / 2;
//lastImage = new Bitmap(tempBmp.Clone(new Rectangle(leftMargin, 0, hght, hght), tempBmp.PixelFormat));
lastImage = new Bitmap(tempBmp.Clone(rect, tempBmp.PixelFormat));
if (isZoomed[wmNr0b])
{
zoomedImage = MakeZoomedImage(lastImage, zoomCenterLoc[wmNr0b]);
currentImageGroup.Add(timedImage);
}
}
}
ShowImage();
}
log.Debug(
$"Image group loaded: " +
$"baseFile='{fullBaseFileName}', " +
$"searchPattern='{searchPattern}', " +
$"imagesCount={currentImageGroup.Count}");
}
catch (Exception exc)
{
log.Warn(
$"Cannot load image group: " +
$"baseFile='{fullBaseFileName}', " +
$"pattern='{searchPattern}', " +
$"error={exc.Message}");
}
}
/// <summary>
/// grouping by time
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private static int GetTimedImageIndex(
string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return int.MaxValue;
}
string nameWithoutExtension =
Path.GetFileNameWithoutExtension(fileName);
Match match = Regex.Match(
nameWithoutExtension,
@"_img(?<index>\d+)_",
RegexOptions.IgnoreCase);
if (!match.Success)
{
return int.MaxValue;
}
int imageIndex;
return int.TryParse(
match.Groups["index"].Value,
out imageIndex)
? imageIndex
: int.MaxValue;
}
/// <summary>
/// refreshing current image group
/// </summary>
private void RefreshCurrentImageGroup()
{
if (string.IsNullOrWhiteSpace(lastImageName))
{
return;
}
string currentlySelectedFile =
currentImageGroupIndex >= 0 &&
currentImageGroupIndex < currentImageGroup.Count
? currentImageGroup[currentImageGroupIndex]
: null;
LoadImageGroup(lastImageName);
if (string.IsNullOrWhiteSpace(currentlySelectedFile))
{
return;
}
int restoredIndex =
currentImageGroup.FindIndex(
fileName =>
string.Equals(
fileName,
currentlySelectedFile,
StringComparison.OrdinalIgnoreCase));
if (restoredIndex >= 0)
{
currentImageGroupIndex = restoredIndex;
}
}
/// --- picture load ---
private void LoadImageFile(
string imageFileName)
{
DisposeLoadedImages();
if (string.IsNullOrWhiteSpace(imageFileName) ||
!File.Exists(imageFileName))
{
log.Debug(
$"Image cannot be displayed because the file does not exist: " +
$"file='{imageFileName}'");
ShowImage();
return;
}
try
{
using (var tempBmp =
new Bitmap(imageFileName))
{
Rectangle rect =
CreateSquareCropRectangle(tempBmp);
using (Bitmap croppedImage =
tempBmp.Clone(
rect,
tempBmp.PixelFormat))
{
lastImage =
new Bitmap(croppedImage);
}
}
if (isZoomed[wmNr0b])
{
zoomedImage =
MakeZoomedImage(
lastImage,
zoomCenterLoc[wmNr0b]);
}
log.Debug(
$"Image displayed from group: " +
$"waterMeter={lastWmNr1}, " +
$"groupIndex={currentImageGroupIndex + 1}/" +
$"{currentImageGroup.Count}, " +
$"file='{imageFileName}'");
}
catch (Exception exc)
{
log.Error(
$"Cannot load image: " +
$"file='{imageFileName}', " +
$"error={exc.Message}",
exc);
DisposeLoadedImages();
}
ShowImage();
}
/// --- block picture Crop help method ---
private static Rectangle CreateSquareCropRectangle( Bitmap bitmap)
{
int size = Math.Min( bitmap.Width, bitmap.Height);
int x = (bitmap.Width - size) / 2;
int y = (bitmap.Height - size) / 2;
return new Rectangle( x, y, size, size);
}
// --- block release old pictures ---
private void DisposeLoadedImages()
{
if (lastImage != null)
{
lastImage.Dispose();
lastImage = null;
}
if (zoomedImage != null)
{
zoomedImage.Dispose();
zoomedImage = null;
}
}
private void ClearImageGroup()
{
currentImageGroup.Clear();
currentImageGroupIndex = 0;
lastImageName = null;
}
// --- block picture exchange ---
private void ShowPreviousImageFromGroup()
{
ShowImageFromGroup(-1);
}
private void ShowNextImageFromGroup()
{
ShowImageFromGroup(1);
}
private void ShowImageFromGroup(
int direction)
{
if (direction == 0 ||
currentImageGroup.Count <= 1)
{
return;
}
int newIndex =
currentImageGroupIndex + direction;
/*
* Cyklické prepínanie:
*
* posledný -> prvý
* prvý -> posledný
*/
if (newIndex < 0)
{
newIndex =
currentImageGroup.Count - 1;
}
else if (newIndex >= currentImageGroup.Count)
{
newIndex = 0;
}
currentImageGroupIndex = newIndex;
LoadImageFile(
currentImageGroup[currentImageGroupIndex]);
}
// --- mouse events ---
private void pictureBox1_MouseEnter(
object sender,
EventArgs e)
{
mouseOverPictureBox = true;
/*
* PictureBox štandardne nemusí mať focus.
* Focus pomôže zachytiť klávesy aj koliesko.
*/
pictureBox1.Focus();
}
private void pictureBox1_MouseLeave(
object sender,
EventArgs e)
{
mouseOverPictureBox = false;
}
private void pictureBox1_MouseWheel(
object sender,
MouseEventArgs e)
{
if (!mouseOverPictureBox ||
currentImageGroup.Count <= 1)
{
return;
}
if (e.Delta > 0)
{
ShowPreviousImageFromGroup();
}
else if (e.Delta < 0)
{
ShowNextImageFromGroup();
}
}
/// <summary>
/// Left click = Zoom, Right click = Unzoom
@ -1276,7 +1731,9 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
{
if (lastImage == null)
{
Image previousImage = pictureBox1.Image;
pictureBox1.Image = new Bitmap(TBF.Properties.Resources.Empty);
previousImage?.Dispose();
return;
}
@ -1312,10 +1769,108 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
Brush brush = (brightness > 160) ? darkBrush : lightBrush;
grph.DrawString(lastWmNr1.ToString(), font, brush, margin, margin);
DrawImageInfo(grph, tempBmp);
}
Image oldPictureBoxImage = pictureBox1.Image;
pictureBox1.Image = tempBmp;
oldPictureBoxImage?.Dispose();
}
private void DrawImageInfo( Graphics graphics, Bitmap bitmap)
{
if (graphics == null)
{
throw new ArgumentNullException(nameof(graphics));
}
if (bitmap == null)
{
throw new ArgumentNullException(nameof(bitmap));
}
string currentImageFileName = GetCurrentImageFileName();
if (string.IsNullOrWhiteSpace(currentImageFileName))
{
return;
}
string fileName = Path.GetFileName(currentImageFileName);
string displayText = fileName;
Match match = Regex.Match( fileName, @"_img(?<index>\d+)_(?<date>\d{8})_(?<time>\d{6})_(?<ms>\d{3})", RegexOptions.IgnoreCase);
if (match.Success)
{
string imageIndex = match.Groups["index"].Value;
string date = match.Groups["date"].Value;
string time = match.Groups["time"].Value;
string milliseconds = match.Groups["ms"].Value;
string formattedDate =
$"{date.Substring(6, 2)}." +
$"{date.Substring(4, 2)}." +
$"{date.Substring(0, 4)}";
string formattedTime =
$"{time.Substring(0, 2)}:" +
$"{time.Substring(2, 2)}:" +
$"{time.Substring(4, 2)}." +
milliseconds;
string baseFileName =
Regex.Replace(
fileName,
@"_img\d+_\d{8}_\d{6}_\d{3}",
string.Empty,
RegexOptions.IgnoreCase);
displayText =
$"{baseFileName} [img{imageIndex}]" +
Environment.NewLine +
$"{formattedDate} {formattedTime}";
}
using (Font font = new Font( "Arial", isZoomed[wmNr0b] ? 8 : 10, FontStyle.Bold))
{
SizeF textSize =
graphics.MeasureString(
displayText,
font);
float margin = 5;
float x = bitmap.Width - textSize.Width - margin;
float y = bitmap.Height - textSize.Height - margin;
graphics.DrawString( displayText, font, Brushes.Black, x + 1, y + 1);
graphics.DrawString( displayText, font, Brushes.White, x, y);
}
}
private string GetCurrentImageFileName()
{
if (currentImageGroup == null ||
currentImageGroup.Count == 0)
{
return lastImageName;
}
if (currentImageGroupIndex < 0 ||
currentImageGroupIndex >= currentImageGroup.Count)
{
return lastImageName;
}
return currentImageGroup[currentImageGroupIndex];
}
private void checkBoxEnableStartBox_CheckStateChanged(object sender, EventArgs e)
@ -1345,5 +1900,35 @@ namespace TBF.Rig.DataEntry.StandartCameraPurchaseOrder
}
}
}
//Page Down/Up evenst
protected override bool ProcessCmdKey(
ref Message msg,
Keys keyData)
{
if (mouseOverPictureBox &&
currentImageGroup.Count > 1)
{
if (keyData == Keys.PageUp)
{
ShowPreviousImageFromGroup();
return true;
}
if (keyData == Keys.PageDown)
{
ShowNextImageFromGroup();
return true;
}
}
return base.ProcessCmdKey(
ref msg,
keyData);
}
}
}

View File

@ -20,6 +20,7 @@ using System.Windows.Forms;
using log4net;
using Renci.SshNet;
using Common;
using Config.Entities;
using FluentNHibernate.Conventions;
using Newtonsoft.Json;
using TBF.Rig.Network.Telnet;
@ -69,10 +70,20 @@ namespace TBF.Rig.Network.Camera.CJMS11
{
get {
if (roiConfiguration!=null) {return roiConfiguration;}
else { roiConfiguration = GetFirstRoiChild(); return roiConfiguration; }
else { roiConfiguration = GetFirstRoiCfgChild(); return roiConfiguration; }
}
set { if (roiConfiguration == null) roiConfiguration = value;}
}
private RoiForFixedStartCJMS11.Roi _roi;
public RoiForFixedStartCJMS11.Roi CamRoi
{
get {
if (_roi!=null) {return _roi;}
else { _roi = GetFirstRoiChild(); return _roi; }
}
set { if (_roi == null) _roi = value;}
}
public IPAddress IPAddress { get { return ipAddress; } }
IPAddress ipAddress;
@ -154,6 +165,14 @@ namespace TBF.Rig.Network.Camera.CJMS11
private bool stopSaveImageListenerFlag;
public bool IsSaveImageListenerThreadAlive { get { return saveImageListenerThread != null && saveImageListenerThread.IsAlive; } }
/// <summary>
/// Send images part
/// </summary>
private Task<IReadOnlyList<TimedCameraImage>> sendImagesTask;
private CancellationTokenSource sendImagesCts;
public bool IsSendImagesInProgress => sendImagesTask != null && !sendImagesTask.IsCompleted;
///
/// ROI related parameters
///
@ -482,7 +501,6 @@ namespace TBF.Rig.Network.Camera.CJMS11
if (completedTask == timeoutTask)
{
client.Close();
throw new TimeoutException( $"Camera connection timeout after 1 second: {ipAddressParam}:{portParam}");
}
@ -928,13 +946,13 @@ namespace TBF.Rig.Network.Camera.CJMS11
}
}
private RoiCfg GetFirstRoiChild()
private RoiCfg GetFirstRoiCfgChild()
{
/// Load the list of components from the database
var session = TBF.DB.CreateSession(DBKind.Config);
var cmptnEntities = session.QueryOver<Config.Entities.Component>().OrderBy(x => x.ItemNr).Asc.List();
List<RoiCfg> cfgs = new List<RoiCfg>();
IList<IComponent> components = new List<Rig.Generic.IComponent>();
foreach (var component in cmptnEntities)
@ -943,25 +961,136 @@ namespace TBF.Rig.Network.Camera.CJMS11
{
IComponentFactory cmpntFactory = Rig.TbfComponents.CmpntFactoryFromClassName(component.ClassName);
if (cmpntFactory == null) continue;
IList<IComponent> tbfComponents = Rig.TbfComponents.LoadComponentsFromDB(cmptnEntities);
IComponentCfg cmpntCfgFromCmpntEntity = cmpntFactory.CmpntCfgFromCmpntEntity(component);
IComponent cmpnt = cmpntFactory.GetComponent(cmpntCfgFromCmpntEntity, components);
RoiForFixedStartCJMS11.RoiCfg roi = cmpntCfgFromCmpntEntity as RoiForFixedStartCJMS11.RoiCfg;
if (roi != null)
{
cfgs.Add(roi);
}
}
}
/// get fist found configuration
return cfgs.First();
}
private RoiForFixedStartCJMS11.Roi GetFirstRoiChild()
{
using (var session = TBF.DB.CreateSession(DBKind.Config))
{
var componentEntities = session
.QueryOver<Config.Entities.Component>()
.OrderBy(x => x.ItemNr)
.Asc
.List();
IList<IComponent> components =
Rig.TbfComponents.LoadComponentsFromDB(componentEntities);
var roi = components
.OfType<RoiForFixedStartCJMS11.Roi>()
.FirstOrDefault(x =>
x.Cfg != null &&
string.Equals(
x.Cfg.ParentName,
Name,
StringComparison.Ordinal));
if (roi == null)
{
log.WarnFormat(
"No ROI child was found for camera '{0}'.",
Name);
return null;
}
var roiCfg = roi.Cfg as RoiForFixedStartCJMS11.RoiCfg;
if (roiCfg == null)
{
log.WarnFormat(
"ROI '{0}' has an invalid configuration type.",
roi.Name);
return roi;
}
LoadProcedureParams(session, roiCfg);
return roi;
}
}
private void LoadProcedureParams(
NHibernate.ISession session,
RoiForFixedStartCJMS11.RoiCfg roiCfg)
{
if (roiCfg == null)
{
return;
}
var currentProcedure = StateMachine.Procedure;
if (currentProcedure == null)
{
log.WarnFormat(
"Cannot load procedure parameters for ROI '{0}': " +
"current procedure is null.",
roiCfg.Name);
return;
}
var dbParams = session
.QueryOver<ComponentProcedure>()
.Where(x => x.CmpntName == roiCfg.Name)
.And(x => x.Procedure == currentProcedure)
.SingleOrDefault();
if (dbParams == null)
{
log.WarnFormat(
"No procedure parameters found for ROI '{0}', procedure '{1}'. " +
"Using default values.",
roiCfg.Name,
currentProcedure);
return;
}
if (roiCfg.ProcParams == null)
{
roiCfg.ProcParams = new ProcedureParams();
}
if (!roiCfg.ProcParams.UpdateFromDbEntity(dbParams))
{
log.WarnFormat(
"Failed to deserialize procedure parameters for ROI '{0}', " +
"procedure '{1}'.",
roiCfg.Name,
currentProcedure);
return;
}
log.DebugFormat(
"Loaded ROI procedure parameters: ROI='{0}', procedure='{1}', " +
"RoiPicCount={2}, RoiDeltaTimeMs={3}.",
roiCfg.Name,
currentProcedure,
roiCfg.ProcParams.RoiPicCount,
roiCfg.ProcParams.RoiDeltaTimeMs);
}
private string getConfigMessage()
{
//CameraCfg
@ -1087,40 +1216,59 @@ namespace TBF.Rig.Network.Camera.CJMS11
}
}
public void SaveImageToFile(Image imageToSave, string fileNamePath)
public void SaveImageToFile(
Image imageToSave,
string fileNamePath)
{
if (imageToSave == null)
{
log.Error("Nothing to save in SaveImageToFile");
return; // nothing to save
throw new ArgumentNullException(nameof(imageToSave));
}
if (fileNamePath == null || fileNamePath.IsEmpty())
if (string.IsNullOrWhiteSpace(fileNamePath))
{
log.Error("Path to save missing!");
return; // nothing to save
throw new ArgumentException(
"Path to save is missing.",
nameof(fileNamePath));
}
try
{
string filePath = fileNamePath;
string path = Path.GetDirectoryName(filePath);
if (path != null && path.IsNotEmpty())
{
Directory.CreateDirectory(path);
}
string directory =
Path.GetDirectoryName(fileNamePath);
using (var clonedImage = CloneImageToBitmap(imageToSave))
{
clonedImage.Save(filePath, ImageFormat.Png); // encoder, encoderParams);
}
log.Debug("Image saved to: " + filePath);
}catch(Exception exc)
if (!string.IsNullOrWhiteSpace(directory))
{
log.Error("Error saving image: " + exc.Message);
throw;
Directory.CreateDirectory(directory);
}
ImageFormat format =
GetImageFormatFromFileName(fileNamePath);
using (var clonedImage = CloneImageToBitmap(imageToSave))
{
clonedImage.Save(fileNamePath, format);
}
log.Debug("Image saved to: " + fileNamePath);
}
private static ImageFormat GetImageFormatFromFileName(
string fileName)
{
string extension =
Path.GetExtension(fileName)?.ToLowerInvariant();
switch (extension)
{
case ".bmp":
return ImageFormat.Bmp;
case ".jpg":
case ".jpeg":
return ImageFormat.Jpeg;
case ".png":
default:
return ImageFormat.Png;
}
}
@ -1555,6 +1703,7 @@ namespace TBF.Rig.Network.Camera.CJMS11
liveCts?.Cancel();
grabCts?.Cancel();
saveCts?.Cancel();
sendImagesCts?.Cancel();
stopLiveStreamListenerFlag = true;
stopGrabImageListenerFlag = true;
@ -2773,45 +2922,48 @@ namespace TBF.Rig.Network.Camera.CJMS11
/// This method is called from within this class when
/// a prompt string was received from the telnet server.
/// </summary>
public static void OnImagesReceived(object sender, int idxImage, Image image, ImageRotation imageRotation, int iImageIdx = 0, int imageCount = 1)
public static void OnImagesReceived(
object sender,
int cameraIndex,
Image image,
int imageIndex,
int imageCount,
DateTimeOffset? frameTime)
{
log.Info($"Images received from camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
if (null != ImageCameraHandler)
if (image == null)
{
try
{
//rotation
ImageUtils.Rotate(image, imageRotation);
//Send image to bridge
ImageCameraHandler(sender, new PromptReceivedImageEventArgs(idxImage, image));
log.Info($"ImageCameraHandler reached camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
}
catch (Exception ex)
{
log.Info("Internal error: PromptReceivedHandler exception: " + ex.Message);
}
return;
}
log.Info( $"Images received from camera: {cameraIndex}, imageIndex={imageIndex}, " +
$"imageCount={imageCount}, frameTime={frameTime:O}");
if (null != ImagesCameraHandler)
EventHandler<PromptReceivedImagesEventArgs> handler = ImagesCameraHandler;
if (handler == null)
{
try
{
//rotation
ImageUtils.Rotate(image, imageRotation);
//Send images to bridge
ImagesCameraHandler(sender, new PromptReceivedImagesEventArgs(idxImage, image,iImageIdx, imageCount));
log.Info($"ImagesCameraHandler reached camera: {idxImage}, iImageIdx: {iImageIdx}, imageCount: {imageCount}");
}
catch (Exception ex)
{
log.Info("Internal error: PromptReceivedHandler exception: " + ex.Message);
}
image.Dispose();
return;
}
try
{
handler( sender,
new PromptReceivedImagesEventArgs(
cameraIndex,
imageIndex,
imageCount,
image,
frameTime));
log.Info( $"ImagesCameraHandler reached camera: {cameraIndex}, imageIndex={imageIndex}, " +
$"imageCount={imageCount}");
}
catch (Exception exc)
{
log.Error( $"ImagesCameraHandler failed: cameraIdx={cameraIndex}, imageIndex={imageIndex}, " +
$"error={exc.Message}", exc);
image.Dispose();
}
}
@ -2934,6 +3086,517 @@ namespace TBF.Rig.Network.Camera.CJMS11
return await readTask;
}
//------------------------------ SEND PART --------------------------------------
public Task<IReadOnlyList<TimedCameraImage>> StartSendImagesPreviewAsync(
int count,
int delayMs,
string taskId)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(count),
"Image count must be greater than zero.");
}
if (delayMs < 0)
{
throw new ArgumentOutOfRangeException(
nameof(delayMs),
"Delay cannot be negative.");
}
if (string.IsNullOrWhiteSpace(taskId))
{
throw new ArgumentException(
"Task ID is required.",
nameof(taskId));
}
if (sendImagesTask != null &&
!sendImagesTask.IsCompleted)
{
log.Warn(
$"Send images preview already running: " +
$"cameraIdx={cameraIdx}, taskId={taskId}");
return sendImagesTask;
}
sendImagesCts?.Cancel();
sendImagesCts?.Dispose();
sendImagesCts =
new CancellationTokenSource();
sendImagesTask =
SendImagesPreviewListenerAsync(
count,
delayMs,
taskId,
sendImagesCts.Token);
return sendImagesTask;
}
public void CancelSendImagesPreview()
{
try
{
sendImagesCts?.Cancel();
}
catch (ObjectDisposedException)
{
}
}
private async Task<IReadOnlyList<TimedCameraImage>> SendImagesPreviewListenerAsync(
int requestedCount,
int delayMs,
string taskId,
CancellationToken ct)
{
bool lockAcquired = false;
TcpClient client = null;
NetworkStream stream = null;
CameraLineReader reader = null;
var imagesByIndex = new Dictionary<int, TimedCameraImage>();
try
{
await cameraOperationLock.WaitAsync(ct).ConfigureAwait(false);
lockAcquired = true;
client = await ConnectCameraAsync( timeoutMs: 5000, ct: ct).ConfigureAwait(false);
stream = client.GetStream();
reader = new CameraLineReader( stream, 64 * 1024);
/*
* Kamera musí byť pripravená.
*/
JmsPacket prepareAnswer = await SendCommandAndWaitToAnswerAsync(
stream,
reader,
CommandM.prepare_camera_,
null,
3000,
ct)
.ConfigureAwait(false);
bool prepared = prepareAnswer != null &&
prepareAnswer.JmsMessage != null &&
prepareAnswer.JmsMessage.Status == MessageStatus.ACK;
bool alreadyOpened =
prepareAnswer != null &&
prepareAnswer.JmsMessage != null &&
prepareAnswer.JmsMessage.Status == MessageStatus.NACK &&
!string.IsNullOrEmpty( prepareAnswer.JmsMessage.Payload) &&
prepareAnswer.JmsMessage.Payload.Contains( "OPENED");
if (!prepared && !alreadyOpened)
{
throw new IOException( $"Prepare camera for send failed: cameraIdx={cameraIdx}, " +
$"answer={FormatPacketForLog(prepareAnswer)}");
}
var request = new SendImagesRequest { Count = requestedCount, Delay = delayMs, TaskId = taskId };
string requestPayload = JsonConvert.SerializeObject( request, Formatting.None);
log.Debug( $"Starting send images preview: cameraIdx={cameraIdx}, count={requestedCount}, " +
$"delay={delayMs}, taskId={taskId}");
/*
* Pri send_ neposielame cez
* SendCommandAndWaitToAnswerAsync(),
* pretože očakávame viac odpovedí.
*/
await SendCommandAsync(
stream,
CommandM.send_,
ct,
requestPayload)
.ConfigureAwait(false);
/*
* Timeout musí zahŕňať plánovaný čas snímania.
*
* count=5, delay=2050:
* približne 4 * 2050 ms medzi prvým a posledným frame
* plus rezerva na prenos Base64 obrázkov.
*/
int totalTimeoutMs = CalculateSendTimeout( requestedCount, delayMs);
using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
timeoutCts.CancelAfter(totalTimeoutMs);
while (imagesByIndex.Count < requestedCount)
{
timeoutCts.Token.ThrowIfCancellationRequested();
string response = await reader.ReadLineAsync( 10000, timeoutCts.Token).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(response))
{
continue;
}
JmsPacket packet;
try
{
packet = new JmsPacket(response);
}
catch (Exception exc)
{
log.Warn( $"Cannot parse send response: cameraIdx={cameraIdx}, error={exc.Message}");
continue;
}
if (packet.JmsMessage == null)
{
continue;
}
if (packet.JmsMessage.Command != CommandM.send_)
{
log.Debug( $"Unexpected packet during send: cameraIdx={cameraIdx}, command={packet.JmsMessage.Command}");
continue;
}
if (packet.JmsMessage.Status != MessageStatus.ACK)
{
log.Warn( $"Send image returned NACK: cameraIdx={cameraIdx}, payload={packet.JmsMessage.Payload}");
continue;
}
string payload = packet.JmsMessage.Payload;
if (string.IsNullOrWhiteSpace(payload))
{
continue;
}
/*
* Posledný sumarizačný packet nemá
* pictureBase64 ani workerId.
*/
if (!PayloadContainsImage(payload))
{
TryLogSendSummary( payload, cameraIdx);
continue;
}
SendImagePayload imagePayload;
try
{
imagePayload = JsonConvert.DeserializeObject<SendImagePayload>(payload);
}
catch (Exception exc)
{
log.Warn( $"Cannot deserialize send image: cameraIdx={cameraIdx}, error={exc.Message}");
continue;
}
if (imagePayload == null || string.IsNullOrWhiteSpace( imagePayload.PictureBase64))
{
continue;
}
if (!string.Equals( imagePayload.Status, "SUCCESSFUL", StringComparison.OrdinalIgnoreCase))
{
log.Warn( $"Send image status is not successful: cameraIdx={cameraIdx}, " +
$"workerId={imagePayload.WorkerId}, status={imagePayload.Status}");
continue;
}
/*
* workerId je podľa logu 1..count.
*/
int imageIndex = imagePayload.WorkerId;
if (imageIndex <= 0)
{
log.Warn( $"Invalid send image index: cameraIdx={cameraIdx}, workerId={imageIndex}");
continue;
}
/*
* Duplicitný packet ignorujeme.
*/
if (imagesByIndex.ContainsKey( imageIndex))
{
log.Warn( $"Duplicate send image: cameraIdx={cameraIdx}, workerId={imageIndex}");
continue;
}
Image image = DecodeBase64Image( imagePayload.PictureBase64);
if (!IsValidGrabbedImage(image))
{
image?.Dispose();
log.Warn(
$"Invalid send image: " +
$"cameraIdx={cameraIdx}, " +
$"workerId={imageIndex}");
continue;
}
DateTimeOffset? frameTime =
ParseFrameTime(
imagePayload.FrameTime);
/*
* Úpravy obrázka dokončíme ešte predtým,
* než ho odovzdáme UI.
*/
ImageUtils.Rotate(
image,
RoiConfiguration.ImageRotation);
DrawOverlay(image);
var timedImage =
new TimedCameraImage
{
Index = imageIndex,
TaskId =
imagePayload.TaskId,
FrameTime = frameTime,
FrameTimeMillis =
imagePayload.FrameTimeMillis,
FrameGap =
imagePayload.FrameGap,
Image = image
};
imagesByIndex.Add(
imageIndex,
timedImage);
/*
* UI dostane klon.
* Originál zostáva vo výslednom zozname.
*/
Image imageForUi = CloneImageToBitmap(image);
OnImagesReceived(
this,
cameraIdx,
imageForUi,
imageIndex,
requestedCount,
frameTime);
log.Debug( $"Send image received: cameraIdx={cameraIdx}, workerId={imageIndex}, " +
$"received={imagesByIndex.Count}/{requestedCount}, frameTime={frameTime:O}, " +
$"size={image.Width}x{image.Height}");
}
}
List<TimedCameraImage> result =
imagesByIndex
.OrderBy(x => x.Key)
.Select(x => x.Value)
.ToList();
log.Debug(
$"Send images preview completed: " +
$"cameraIdx={cameraIdx}, " +
$"received={result.Count}, " +
$"taskId={taskId}");
return result;
}
catch (OperationCanceledException)
{
foreach (TimedCameraImage image
in imagesByIndex.Values)
{
image.Image?.Dispose();
}
log.Debug(
$"Send images preview cancelled: " +
$"cameraIdx={cameraIdx}, " +
$"taskId={taskId}");
throw;
}
catch (Exception exc)
{
foreach (TimedCameraImage image
in imagesByIndex.Values)
{
image.Image?.Dispose();
}
log.Error(
$"Send images preview failed: " +
$"cameraIdx={cameraIdx}, " +
$"taskId={taskId}, " +
$"error={exc.Message}",
exc);
UiBridge.Bridge.OnImage(
cameraIdx,
null);
throw;
}
finally
{
if (stream != null &&
reader != null)
{
try
{
await TryCloseCameraAsync(
stream,
reader)
.ConfigureAwait(false);
}
catch (Exception exc)
{
log.Warn(
$"Send preview cleanup failed: " +
$"cameraIdx={cameraIdx}, " +
$"error={exc.Message}");
}
}
try
{
stream?.Dispose();
}
catch
{
}
try
{
client?.Close();
client?.Dispose();
}
catch
{
}
if (lockAcquired)
{
cameraOperationLock.Release();
}
}
}
private static int CalculateSendTimeout(
int count,
int delayMs)
{
/*
* Medzi count obrázkami je count - 1 intervalov.
* Pridáme 15 sekúnd rezervu na capture,
* Base64 a sieťový prenos.
*/
long captureDuration =
(long)Math.Max(0, count - 1) *
delayMs;
long timeout =
captureDuration + 15000L;
return timeout > int.MaxValue
? int.MaxValue
: (int)timeout;
}
private static Image DecodeBase64Image( string pictureBase64)
{
if (string.IsNullOrWhiteSpace( pictureBase64))
{
return null;
}
try
{
byte[] imageBytes = Convert.FromBase64String( pictureBase64);
using (var stream = new MemoryStream(imageBytes))
using (Image decoded = Image.FromStream( stream, useEmbeddedColorManagement: false, validateImageData: true))
{
/*
* Bitmap odpojí výsledok od MemoryStream.
*/
return new Bitmap(decoded);
}
}
catch
{
return null;
}
}
private static bool PayloadContainsImage( string payload)
{
if (string.IsNullOrWhiteSpace(payload))
{
return false;
}
return payload.IndexOf( "\"pictureBase64\"", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static DateTimeOffset? ParseFrameTime( string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(
value,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.RoundtripKind,
out parsed))
{
return parsed;
}
return null;
}
private static void TryLogSendSummary( string payload, int cameraIdx)
{
try
{
SendSummaryPayload summary = JsonConvert.DeserializeObject<SendSummaryPayload>(payload);
if (summary == null)
{
return;
}
log.Debug( $"Send summary received: cameraIdx={cameraIdx}, picCount={summary.PictureCount}, " +
$"delay={summary.Delay}, timeStamp={summary.TimeStamp}");
}
catch (Exception exc)
{
log.Debug( $"Payload is not a send summary: cameraIdx={cameraIdx}, error={exc.Message}");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
using Newtonsoft.Json;
namespace TBF.Rig.Network.Camera.CJMS11.POJO
{
public class SendImagePayload
{
[JsonProperty("frameGap")]
public int FrameGap { get; set; }
[JsonProperty("frameTime")]
public string FrameTime { get; set; }
[JsonProperty("frameTimeMillis")]
public long FrameTimeMillis { get; set; }
[JsonProperty("pictureBase64")]
public string PictureBase64 { get; set; }
[JsonProperty("picturesComplete")]
public int PicturesComplete { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("taskId")]
public string TaskId { get; set; }
[JsonProperty("taskPath")]
public string TaskPath { get; set; }
[JsonProperty("taskTime")]
public string TaskTime { get; set; }
[JsonProperty("workerId")]
public int WorkerId { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace TBF.Rig.Network.Camera.CJMS11.POJO
{
public class SendImagesRequest
{
[JsonProperty("count")]
public int Count { get; set; }
[JsonProperty("delay")]
public int Delay { get; set; }
[JsonProperty("taskId")]
public string TaskId { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace TBF.Rig.Network.Camera.CJMS11.POJO
{
public class SendSummaryPayload
{
[JsonProperty("delay")]
public int Delay { get; set; }
[JsonProperty("pic_count")]
public int PictureCount { get; set; }
[JsonProperty("timeStamp")]
public string TimeStamp { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Drawing;
namespace TBF.Rig.Network.Camera.CJMS11.POJO
{
public class TimedCameraImage
{
public int Index { get; set; }
public string TaskId { get; set; }
public DateTimeOffset? FrameTime { get; set; }
public long FrameTimeMillis { get; set; }
public int FrameGap { get; set; }
public Image Image { get; set; }
}
}

View File

@ -27,6 +27,12 @@ namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11
/// </summary>
/// <description>delta time for send parameter 500ms is minimum</description>
public int RoiDeltaTimeMs;
/// <summary>
/// count of picture for send > 1 / grabImage == 0|1 parameter - switch method
/// </summary>
/// <description>count of picture for send > 1 / grabImage == 0|1 parameter - switch method</description>
public int RoiPicCount;
string[] paramNames = new string[]
{
@ -35,11 +41,7 @@ namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11
Strings.DelaySendImage,
};
/// <summary>
/// count of picture for send > 1 / grabImage == 0|1 parameter - switch method
/// </summary>
/// <description>count of picture for send > 1 / grabImage == 0|1 parameter - switch method</description>
public int RoiPicCount;
public override void InitializeAll()
{
@ -68,8 +70,8 @@ namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11
switch (i)
{
case 0: PulsesPerLtr = Utils.ParseUDouble(strValue); return CfgUpdateFlags.None;
case 1: PulsesPerLtr = Int32.Parse(strValue); return CfgUpdateFlags.None;
case 2: PulsesPerLtr = Int32.Parse(strValue); return CfgUpdateFlags.None;
case 1: RoiPicCount = Int32.Parse(strValue); return CfgUpdateFlags.None;
case 2: RoiDeltaTimeMs = Int32.Parse(strValue); return CfgUpdateFlags.None;
default: return CfgUpdateFlags.None;
}
}
@ -87,21 +89,19 @@ namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11
if (Utils.TryParseUDouble(strValue, out dummy)) return true;
break;
case 1:
if (Int32.TryParse(strValue, out dummyInt)) return true;
if (dummyInt < 0 || dummyInt < 50)
if (!Int32.TryParse(strValue, out dummyInt) || (dummyInt < 0 || dummyInt > 50))
{
message = ParamName(i) + " is invalid. Param must be between 0 and 50!";
return false;
}
break;
return true;
case 2:
if (Int32.TryParse(strValue, out dummyInt)) return true;
if (dummyInt < 500 || dummyInt < 5500)
if (!Int32.TryParse(strValue, out dummyInt) || (dummyInt < 500 || dummyInt < 5500))
{
message = ParamName(i) + " is invalid. Param must be between 500 and 5500!";
return false;
}
break;
return true;
default:
message = "Invalid index";
return false;

View File

@ -3,6 +3,7 @@
///
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using log4net;
using Common;
@ -41,6 +42,8 @@ namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11
get { return roiCfg.ProcParams.PulsesPerLtr; }
set { roiCfg.ProcParams.PulsesPerLtr = value; }
}
public int RoiDeltaTimeMs { get { return roiCfg.ProcParams.RoiDeltaTimeMs; } }
public int RoiPicCount { get { return roiCfg.ProcParams.RoiPicCount; } }
public double LtrsPerPulse { get { return (PulsesPerLtr <= float.Epsilon) ? 1.0 : (1 / PulsesPerLtr); } }
public string QuantityUnits { get; set; }
@ -123,26 +126,65 @@ namespace TBF.Rig.Network.Camera.RoiForFixedStartCJMS11
return GrabImageOp(imageFileName, roiCfg.BlackAndWhite, roiCfg.LowResolution, roiCfg.ImageRotation);
}
public IOperation GrabImageOp(string imageFileName,
bool blackAndWhite, bool lowResolution, ImageRotation imageRotation)
// Roi.cs
public IOperation GrabImageOp(
string imageFileName,
bool blackAndWhite,
bool lowResolution,
ImageRotation imageRotation)
{
if (NetCamera != null)
if (NetCamera == null)
{
///TODO BUMI - implementing grabing multiple images
/// 1. Create a list of image file names
string[] imageNames = new string[roiCfg.ImageOpacity.Count];
for (int i = 0; i < imageNames.Length; i++)
{
imageNames[i] = imageFileName + string.Format("{0}_{1}",imageNames.Length, i);
}
/// 2. implement count of images to grab
/// 3. implement grabing multiple images
/// TODO: Implement support for sharing one camera by multiple ROI-s
return NetCamera.GrabImagesOp(new string[] { imageFileName }, blackAndWhite, lowResolution, imageRotation);
}
else
return null;
}
int imageCount = Math.Max(1, RoiPicCount);
string[] imageNames = CreateImageFileNames(
imageFileName,
imageCount);
return NetCamera.GrabImagesOp(
imageNames,
blackAndWhite,
lowResolution,
imageRotation);
}
private static string[] CreateImageFileNames(
string originalFileName,
int imageCount)
{
if (imageCount <= 1)
{
return new[] { originalFileName };
}
string directory = Path.GetDirectoryName(originalFileName);
string baseName = Path.GetFileNameWithoutExtension(originalFileName);
string extension = Path.GetExtension(originalFileName);
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
var result = new string[imageCount];
// Prvý obrázok zostáva kompatibilný s dialógom.
result[0] = originalFileName;
for (int i = 1; i < imageCount; i++)
{
string fileName = string.Format(
"{0}_{1}_{2:D2}{3}",
baseName,
timestamp,
i + 1,
extension);
result[i] = string.IsNullOrEmpty(directory)
? fileName
: Path.Combine(directory, fileName);
}
return result;
}
public IValve GetValve()

View File

@ -94,17 +94,24 @@ namespace TBF.Rig.Network.Telnet
public class PromptReceivedImagesEventArgs : EventArgs
{
public Image image; /// server image.
public int idxCamera; /// camera index.
public int idxImage; /// image index.
public int imageCounts; /// number of images.
public PromptReceivedImagesEventArgs(int idxCamera, Image image, int idxImage, int imageCounts)
public int idxCamera;
public int idxImage;
public int imageCount;
public Image image;
public DateTimeOffset? frameTime;
public PromptReceivedImagesEventArgs(
int idxCamera,
int idxImage,
int imageCount,
Image image,
DateTimeOffset? frameTime)
{
this.image = image;
this.idxCamera = idxCamera;
this.idxImage = idxImage;
this.imageCounts = imageCounts;
this.imageCount = imageCount;
this.image = image;
this.frameTime = frameTime;
}
}

View File

@ -1030,6 +1030,10 @@
<Compile Include="Rig\Network\Camera\CJMS11\POJO\MessageStatus.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\POJO\MessageStatusEnum.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\POJO\ParsedImage.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\POJO\SendImagePayload.cs" />
<Compile Include="Rig\Network\Camera\CJMS11\POJO\SendImagesRequest.cs" />
<Compile Include="Rig\Network\Camera\CJMS11\POJO\SendSummaryPayload.cs" />
<Compile Include="Rig\Network\Camera\CJMS11\POJO\TimedCameraImage.cs" />
<Compile Include="Rig\Network\Camera\CJMS11\RoisAndResults.cs"/>
<Compile Include="Rig\Network\Camera\CJMS11\TerminalDlg.cs">
<SubType>Form</SubType>