hawkeye camera support(not tested)

This commit is contained in:
meelstorm
2025-08-19 12:45:50 +02:00
parent f80b275ad8
commit 6ef6aced8d
170 changed files with 20190 additions and 72 deletions

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\framework\Inspectron.HawkEye\Inspectron.HawkEye.csproj" />
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,133 @@
using Inspectron.HawkEye.Protocol;
using OpenCvSharp;
using Serilog;
using System.Net;
using System.Runtime.InteropServices;
using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.RecipeProcessing;
using CameraSettings = Inspectron.HawkEye.Protocol.CameraSettings;
namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye
{
public class HawkeyeCameraImageSource : IImageSource, IVisionBuilderModule
{
private readonly HawkeyeSettings _settings;
private ImageClientTCP _client;
private CameraSettings _cameraSettings;
public HawkeyeCameraImageSource(HawkeyeSettings settings)
{
_settings = settings;
}
public void Open()
{
_client = new ImageClientTCP(new IPEndPoint(IPAddress.Parse("192.168.3.15"), 27001),
IPAddress.Parse(_settings.Adapter));
_client.ImageReceived += _client_ImageReceived;
_client.SettingsReceived += _client_SettingsReceived;
_client.Connect();
Log.Information("Connected to Hawkeye camera at {Adapter}", _settings.Adapter, 27001);
}
private void _client_SettingsReceived(CameraSettings obj)
{
ApplySettings(obj);
}
public void ApplySettings(CameraSettings obj)
{
_cameraSettings = UICameraSettings.LoadSettingsLocal(_settings.SettingsFile).ToCameraSettings();
_client.ApplySettings(_cameraSettings);
}
public Task<Mat> GetImage(CancellationToken token)
{
_lastImage = null;
if (_imageAquisitionTaskSource != null && !_imageAquisitionTaskSource.Task.IsCanceled)
{
_imageAquisitionTaskSource.SetCanceled(CancellationToken.None);
_imageAquisitionTaskSource = null;
}
_imageAquisitionTaskSource= new TaskCompletionSource<Mat>();
_client.Trigger();
return _imageAquisitionTaskSource.Task;
}
private TaskCompletionSource<Mat>? _imageAquisitionTaskSource;
private Mat _lastImage;
byte[] _flipBuffer = new byte[2000 * 2000];
private void _client_ImageReceived(byte[] obj)
{
Log.Debug("Got image on {adapter}", _settings.Adapter);
FlipLines(obj, _flipBuffer);
obj = _flipBuffer;
var pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned);
var pointer = pinnedArray.AddrOfPinnedObject();
Mat image = new Mat(_cameraSettings.ImageSettings.Lines, _cameraSettings.ImageSettings.SensorWidth,
MatType.CV_8UC1, pointer);
var xCrop = _cameraSettings.ImageSettings.SensorWidth - _cameraSettings.ImageWidth - _cameraSettings.OffsetX;
// crop the image with opencv
_lastImage = image[0, _cameraSettings.ImageSettings.Lines, xCrop,
xCrop + _cameraSettings.ImageWidth].Clone();
if (_cameraSettings.BayerFilter)
{
_lastImage = BayerFilter(_lastImage);
}
_imageAquisitionTaskSource!.SetResult(_lastImage);
pinnedArray.Free();
}
Mat BayerFilter(Mat image)
{
// Assumes input is a single-channel Bayer pattern image (CV_8UC1)
// Output is a 3-channel BGR image (CV_8UC3)
if (image == null || image.Empty())
throw new ArgumentException("Input image is null or empty.", nameof(image));
Mat bgrImage = new Mat();
// Use OpenCV's demosaicing function for Bayer BG pattern
Cv2.CvtColor(image, bgrImage, ColorConversionCodes.BayerBG2BGR);
return bgrImage;
}
protected void FlipLines(byte[] src, byte[] dst)
{
int srcStride = _cameraSettings.ImageSettings.SensorWidth;
int dstStride = _cameraSettings.ImageSettings.SensorWidth;
int pixelSize = 1;
int copySize = srcStride * pixelSize;
var lines = _cameraSettings.ImageSettings.Lines;
for (int line = 0; line < lines; line += 4)
{
for (int i = 0; i < 4; i++)
{
int srcPos = (line + i) * srcStride;
int dstPos = (line + (4 - i)) * dstStride;
for (int j = 0; j < copySize; j++)
{
dst[dstPos + j] = src[srcPos + j];
}
}
}
}
public void InitializeModule()
{
}
}
}

View File

@@ -0,0 +1,25 @@
using Inspectron.Settings;
using Inspectron.Settings.Attributes;
using VisionBuilder.UI.Common;
namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye;
public class HawkeyeSettings(string CameraName): ISettings
{
public string Adapter { get; set; }="192.168.1.100";
[File("*.jcnf")]
public string SettingsFile { get; set; }
public void RegisterSettings(InspectronSettings settings)
{
settings.RegisterSimple(this, () => Adapter, $"{CameraName}/Sources/Hawkeye", nameof(Adapter));
settings.RegisterSimple(this, () => SettingsFile, $"{CameraName}/Sources/Hawkeye", nameof(SettingsFile));
}
}

View File

@@ -0,0 +1,119 @@
using Inspectron.HawkEye.Protocol;
using Newtonsoft.Json;
namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye;
public class UICameraSettings
{
public UICameraSettings()
{
}
public UICameraSettings(CameraSettings cameraSettings)
{
Shutter = cameraSettings.ImageSettings.Shutter;
Gain = cameraSettings.ImageSettings.Gain;
OffsetX = cameraSettings.OffsetX;
SensorWidth = cameraSettings.ImageSettings.SensorWidth;
Width = cameraSettings.ImageWidth;
Lines = cameraSettings.ImageSettings.Lines;
Divider = cameraSettings.ImageSettings.Divider;
Light1 = cameraSettings.LightPwm1;
Light2 = cameraSettings.LightPwm2;
TriggerEnabled = cameraSettings.ImageSettings.UseExternalTrigger == 1;
Name = cameraSettings.Name;
CaptureBuffer = cameraSettings.ImageSettings.CaptureBuffer;
RescaleWidth = cameraSettings.RescaleWidth;
MinorCutoff = cameraSettings.MinorCutoff;
BayerFilter = cameraSettings.BayerFilter;
LaserTrigger = cameraSettings.LaserTrigger;
LaserTriggerDelay = cameraSettings.LaserTriggerDelay;
FlipLines = cameraSettings.FlipLines;
TriggerLights = cameraSettings.TriggerLights;
MirrorX = cameraSettings.MirrorX;
}
public CameraSettings ToCameraSettings()
{
return new CameraSettings()
{
ImageSettings = new ImageSettings()
{
Lines = Lines,
CaptureBuffer = CaptureBuffer,
Divider = Divider,
UseExternalTrigger = TriggerEnabled ? 1 : 0,
Gain = Gain,
Shutter = Shutter,
SensorWidth = SensorWidth
},
LightPwm1 = Light1,
LightPwm2 = Light2,
OffsetX = OffsetX,
ImageWidth = Width,
RescaleWidth = RescaleWidth,
MinorCutoff = MinorCutoff,
BayerFilter = BayerFilter,
LaserTrigger = LaserTrigger,
LaserTriggerDelay = LaserTriggerDelay,
FlipLines = FlipLines,
TriggerLights = TriggerLights,
MirrorX = MirrorX
};
}
public static UICameraSettings LoadSettingsLocal(string path)
{
return JsonConvert.DeserializeObject<UICameraSettings>(File.ReadAllText(path));
}
public int LaserTriggerDelay { get; set; }
public bool LaserTrigger { get; set; }
public bool TriggerEnabled { get; set; }
public int CaptureBuffer { get; set; }
public bool FlipLines { get; set; }
public int Shutter { get; set; }
public int Gain { get; set; }
public int OffsetX { get; set; }
public int SensorWidth { get; set; }
public int Width { get; set; }
public int RescaleWidth { get; set; }
public int Lines { get; set; }
public int Divider { get; set; }
public int Light1 { get; set; }
public int Light2 { get; set; }
public bool TriggerLights { get; set; }
public string Name { get; set; }
public int MinorCutoff { get; set; }
public bool BayerFilter { get; set; }
public bool MirrorX { get; set; }
}

View File

@@ -24,7 +24,6 @@ namespace Inspectron.Camera.UEye
int cameraIdx;
public IDSImageSource(IDSImageSourceSettings settings)
{
_settings = settings;
@@ -68,8 +67,7 @@ namespace Inspectron.Camera.UEye
Byte[] u8img;
_camera.Memory.CopyToArray(idx, out u8img);
//Console.WriteLine($"Image size: {size.Width}x{size.Height}, channels: {channels}({mode}), data size: {u8img.Length}, pitch: {pitch}");
//Console.ReadLine();
Mat mat = new Mat(size.Height,pitch, MatType.CV_8UC(channels));
mat.SetArray(u8img);
return mat;
@@ -82,11 +80,11 @@ namespace Inspectron.Camera.UEye
uEye.Defines.Status statusRet = 0;
// Open _camera
// Open camera
statusRet = _camera.Init(cameraIdx);
if (statusRet != uEye.Defines.Status.Success)
{
throw new Exception("_camera initializing failed: "+statusRet);
throw new Exception("Camera initialization failed: "+statusRet);
}
uEye.Types.SensorInfo info = new uEye.Types.SensorInfo();
_camera.Information.GetSensorInfo(out info);
@@ -101,7 +99,6 @@ namespace Inspectron.Camera.UEye
if (statusRet != uEye.Defines.Status.Success)
{
throw new Exception("Allocate Memory failed");
}
_camera.EventFrame += onFrameEvent;
try

View File

@@ -0,0 +1,14 @@
{
"permissions": {
"allow": [
"Bash(find:*)",
"Bash(for file in UnionOperation.cs HatsOperation.cs OpeningOperation.cs DilationOperation.cs ClosingOperation.cs)",
"Bash(do echo \"=== $file ===\")",
"Bash(tail:*)",
"Bash(done)",
"Bash(dotnet build:*)",
"Bash(ls:*)"
],
"deny": []
}
}

View File

@@ -90,8 +90,9 @@ namespace Hawkeye.VisionBuilder.Workflow
{
return Regex.Replace(typeName.Replace("Operation", ""), "(\\B[A-Z]+?(?=[A-Z][^A-Z])|\\B[A-Z]+?(?=[^A-Z]))", (useSpaces?" ":"")+"$1");
}
[NotForTool]
public bool Result { get; set; } = true;
[NotForTool]
public string ResultString
{
get
@@ -104,7 +105,12 @@ namespace Hawkeye.VisionBuilder.Workflow
[NotForTool]
public string Label { get; set; }
[NotForTool]
public bool Enabled { get; set; } = true;
[NotForTool]
public TimeSpan ExecutionTime { get; set; }
[NotForTool]
public string ExecutionTimeString
{
get
@@ -132,7 +138,7 @@ namespace Hawkeye.VisionBuilder.Workflow
public Dictionary<string, object> GetParameters()
{
var props = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
var props = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x=>!x.HasAttribute(typeof(NotForToolAttribute)));
return props.ToDictionary(x => x.Name, x => x.GetValue(this)!);
}
@@ -142,7 +148,7 @@ namespace Hawkeye.VisionBuilder.Workflow
foreach (KeyValuePair<string, object> pair in parameters)
{
this.GetType().GetProperty(pair.Key,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
BindingFlags.Public | BindingFlags.Instance )
?.SetValue(this,pair.Value);
}
}
@@ -275,6 +281,23 @@ namespace Hawkeye.VisionBuilder.Workflow
Label = br.ReadString();
}
public virtual void Save(Dictionary<string, object> dict)
{
dict[nameof(Id)] = Id.ToString();
dict[nameof(Label)] = Label;
dict[nameof(Enabled)] = Enabled;
}
public virtual void Load(Dictionary<string, object> dict)
{
if (dict.ContainsKey(nameof(Id)))
Id = new Guid(dict[nameof(Id)].ToString());
if (dict.ContainsKey(nameof(Label)))
Label = dict[nameof(Label)].ToString();
if (dict.ContainsKey(nameof(Enabled)))
Enabled = Convert.ToBoolean(dict[nameof(Enabled)]);
}
public void SetError(string eMessage)
{
Status=eMessage;

View File

@@ -9,6 +9,7 @@ public class BaseOperationDecorator
_baseOperation = baseOperation;
}
public bool Result => Operation.Result;
public string ResultString => Operation.ToString();
public string TypeName => Operation.TypeName;
@@ -21,4 +22,10 @@ public class BaseOperationDecorator
public string ExecutionTimeString => Operation.ExecutionTimeString;
public BaseOperation Operation => _baseOperation;
public bool IsEnabled
{
get => _baseOperation.Enabled;
set => _baseOperation.Enabled = value;
}
}

View File

@@ -122,4 +122,17 @@ public class AnomalyAI: BaseOperation
_modelFilePath.Format = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
}
}

View File

@@ -181,4 +181,54 @@ public class ArrayModelMatchingOperation:BaseOperation
SearchArea.Load(br);
ReloadModelInfo();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ReferenceId)] = ReferenceId.ToString();
dict[nameof(Margin)] = Margin;
dict[nameof(Amount)] = Amount;
dict[nameof(ModelName)] = ModelName;
// Save ArrayHorizontalElement SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_BlockSizeX"] = SearchArea.BlockSize.X;
dict["SearchArea_BlockSizeY"] = SearchArea.BlockSize.Y;
dict["SearchArea_BlockCount"] = SearchArea.BlockCount;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ReferenceId)))
ReferenceId = new Guid(dict[nameof(ReferenceId)].ToString());
if (dict.ContainsKey(nameof(Margin)))
Margin = Convert.ToInt32(dict[nameof(Margin)]);
if (dict.ContainsKey(nameof(Amount)))
Amount = Convert.ToInt32(dict[nameof(Amount)]);
if (dict.ContainsKey(nameof(ModelName)))
ModelName = dict[nameof(ModelName)].ToString();
// Load ArrayHorizontalElement SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable") || dict.ContainsKey("SearchArea_LocationX") || dict.ContainsKey("SearchArea_BlockSizeX"))
{
SearchArea = new ArrayHorizontalElement();
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_BlockSizeX") && dict.ContainsKey("SearchArea_BlockSizeY"))
SearchArea.BlockSize = new Vector2(Convert.ToSingle(dict["SearchArea_BlockSizeX"]), Convert.ToSingle(dict["SearchArea_BlockSizeY"]));
if (dict.ContainsKey("SearchArea_BlockCount"))
SearchArea.BlockCount = Convert.ToInt32(dict["SearchArea_BlockCount"]);
}
ReloadModelInfo();
}
}

View File

@@ -120,5 +120,18 @@ public class BackgroundSeparationModelOperation:BaseOperation,IHaveOrigin
ModelName = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ModelName)] = ModelName;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ModelName)))
ModelName = dict[nameof(ModelName)].ToString();
}
public OriginElement Origin { get; set; }
}

View File

@@ -30,4 +30,20 @@ public class Color128BinaryOperation:ColorAIOperation
FilterClasses = br.ReadString();
ModelFilePath.Path = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(FilterClasses)] = FilterClasses;
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(FilterClasses)))
FilterClasses = dict[nameof(FilterClasses)].ToString();
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
}
}

View File

@@ -110,4 +110,20 @@ public class Color128HalfOperation:BaseOperation
FilterClasses = br.ReadString();
ModelFilePath.Path = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(FilterClasses)] = FilterClasses;
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(FilterClasses)))
FilterClasses = dict[nameof(FilterClasses)].ToString();
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
}
}

View File

@@ -33,4 +33,20 @@ public class Color128SimpleOperation: ColorAIOperation
FilterClasses = br.ReadString();
ModelFilePath.Path = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(FilterClasses)] = FilterClasses;
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(FilterClasses)))
FilterClasses = dict[nameof(FilterClasses)].ToString();
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
}
}

View File

@@ -39,4 +39,27 @@ public class ColorModelOperation : ColorAIOperation
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
ModelFilePath.Path = absPath;
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(FilterClasses)] = FilterClasses;
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(FilterClasses)))
FilterClasses = dict[nameof(FilterClasses)].ToString();
if (dict.ContainsKey(nameof(ModelFilePath)))
{
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
// get current directory
var dir = Directory.GetCurrentDirectory();
// get absolute path
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
ModelFilePath.Path = absPath;
}
}
}

View File

@@ -156,4 +156,23 @@ public class ModelAIOperation: BaseOperation
FilterClasses = br.ReadString();
IsCategorical = br.ReadBoolean();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
dict[nameof(FilterClasses)] = FilterClasses;
dict[nameof(IsCategorical)] = IsCategorical;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
if (dict.ContainsKey(nameof(FilterClasses)))
FilterClasses = dict[nameof(FilterClasses)].ToString();
if (dict.ContainsKey(nameof(IsCategorical)))
IsCategorical = Convert.ToBoolean(dict[nameof(IsCategorical)]);
}
}

View File

@@ -145,4 +145,17 @@ public class MultichannelAI: BaseOperation
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
}
}

View File

@@ -147,4 +147,23 @@ public class RawModelAIOperation: BaseOperation
FilterClasses = br.ReadString();
IsCategorical = br.ReadBoolean();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
dict[nameof(FilterClasses)] = FilterClasses;
dict[nameof(IsCategorical)] = IsCategorical;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
if (dict.ContainsKey(nameof(FilterClasses)))
FilterClasses = dict[nameof(FilterClasses)].ToString();
if (dict.ContainsKey(nameof(IsCategorical)))
IsCategorical = Convert.ToBoolean(dict[nameof(IsCategorical)]);
}
}

View File

@@ -139,4 +139,17 @@ public class YoloDetectionOperation:BaseOperation
base.Load(br);
ModelFilePath.Path = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ModelFilePath)] = ModelFilePath.Path;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ModelFilePath)))
ModelFilePath.Path = dict[nameof(ModelFilePath)].ToString();
}
}

View File

@@ -112,4 +112,38 @@ public class YoloPickDetected:BaseOperation
MinArea = br.ReadInt32();
MaxArea = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(SlotName)] = SlotName;
dict[nameof(TypeId)] = TypeId;
dict[nameof(MinWidth)] = MinWidth;
dict[nameof(MaxWidth)] = MaxWidth;
dict[nameof(MinHeight)] = MinHeight;
dict[nameof(MaxHeight)] = MaxHeight;
dict[nameof(MinArea)] = MinArea;
dict[nameof(MaxArea)] = MaxArea;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(SlotName)))
SlotName = dict[nameof(SlotName)].ToString();
if (dict.ContainsKey(nameof(TypeId)))
TypeId = Convert.ToInt32(dict[nameof(TypeId)]);
if (dict.ContainsKey(nameof(MinWidth)))
MinWidth = Convert.ToInt32(dict[nameof(MinWidth)]);
if (dict.ContainsKey(nameof(MaxWidth)))
MaxWidth = Convert.ToInt32(dict[nameof(MaxWidth)]);
if (dict.ContainsKey(nameof(MinHeight)))
MinHeight = Convert.ToInt32(dict[nameof(MinHeight)]);
if (dict.ContainsKey(nameof(MaxHeight)))
MaxHeight = Convert.ToInt32(dict[nameof(MaxHeight)]);
if (dict.ContainsKey(nameof(MinArea)))
MinArea = Convert.ToInt32(dict[nameof(MinArea)]);
if (dict.ContainsKey(nameof(MaxArea)))
MaxArea = Convert.ToInt32(dict[nameof(MaxArea)]);
}
}

View File

@@ -35,4 +35,20 @@ public class CannyOperation:BaseOperation
Threshold1 = br.ReadInt32();
Threshold2 = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Threshold1)] = Threshold1;
dict[nameof(Threshold2)] = Threshold2;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Threshold1)))
Threshold1 = Convert.ToInt32(dict[nameof(Threshold1)]);
if (dict.ContainsKey(nameof(Threshold2)))
Threshold2 = Convert.ToInt32(dict[nameof(Threshold2)]);
}
}

View File

@@ -52,4 +52,20 @@ public class DetectionPaddingOperation:BaseOperation
HorizontalPadding = br.ReadInt32();
VerticalPadding = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(HorizontalPadding)] = HorizontalPadding;
dict[nameof(VerticalPadding)] = VerticalPadding;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(HorizontalPadding)))
HorizontalPadding = Convert.ToInt32(dict[nameof(HorizontalPadding)]);
if (dict.ContainsKey(nameof(VerticalPadding)))
VerticalPadding = Convert.ToInt32(dict[nameof(VerticalPadding)]);
}
}

View File

@@ -47,4 +47,17 @@ public class GaussianBlurOperation : BaseOperation
KernelSize.Script = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(KernelSize)] = KernelSize.Script;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(KernelSize)))
KernelSize.Script = dict[nameof(KernelSize)].ToString();
}
}

View File

@@ -51,4 +51,29 @@ public class PorabollisticHoughOperation:BaseOperation
MinLineLength = br.ReadInt32();
MaxLineGap = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(DistanceResolution)] = DistanceResolution;
dict[nameof(AngleResolution)] = AngleResolution;
dict[nameof(Threshold)] = Threshold;
dict[nameof(MinLineLength)] = MinLineLength;
dict[nameof(MaxLineGap)] = MaxLineGap;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(DistanceResolution)))
DistanceResolution = Convert.ToDouble(dict[nameof(DistanceResolution)]);
if (dict.ContainsKey(nameof(AngleResolution)))
AngleResolution = Convert.ToDouble(dict[nameof(AngleResolution)]);
if (dict.ContainsKey(nameof(Threshold)))
Threshold = Convert.ToInt32(dict[nameof(Threshold)]);
if (dict.ContainsKey(nameof(MinLineLength)))
MinLineLength = Convert.ToInt32(dict[nameof(MinLineLength)]);
if (dict.ContainsKey(nameof(MaxLineGap)))
MaxLineGap = Convert.ToInt32(dict[nameof(MaxLineGap)]);
}
}

View File

@@ -44,4 +44,20 @@ public class ThresholdMinMaxOperation : BaseOperation
ThresholdMin.Script = br.ReadString();
ThresholdMax.Script = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ThresholdMin)] = ThresholdMin.Script;
dict[nameof(ThresholdMax)] = ThresholdMax.Script;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ThresholdMin)))
ThresholdMin.Script = dict[nameof(ThresholdMin)].ToString();
if (dict.ContainsKey(nameof(ThresholdMax)))
ThresholdMax.Script = dict[nameof(ThresholdMax)].ToString();
}
}

View File

@@ -41,4 +41,17 @@ public class ThresholdOperation:BaseOperation
ThresholdMin.Script = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ThresholdMin)] = ThresholdMin.Script;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ThresholdMin)))
ThresholdMin.Script = dict[nameof(ThresholdMin)].ToString();
}
}

View File

@@ -132,6 +132,23 @@ public class ColorProfileOperation:BaseOperation
_initialized = false;
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ColorProfilePath)] = ColorProfilePath.Path;
dict[nameof(Category)] = Category.Value;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ColorProfilePath)))
ColorProfilePath.Path = EnsureRelativePath(dict[nameof(ColorProfilePath)].ToString());
if (dict.ContainsKey(nameof(Category)))
Category.Value = dict[nameof(Category)].ToString();
_initialized = false;
}
string EnsureRelativePath(string path)
{
if (path.StartsWith(".."))

View File

@@ -94,4 +94,35 @@ public class GaborFilterOperation : BaseOperation
ThetaToAngle = br.ReadDouble();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(KSize)] = KSize;
dict[nameof(Sigma)] = Sigma;
dict[nameof(NumFilters)] = NumFilters;
dict[nameof(Lambda)] = Lambda;
dict[nameof(Gamma)] = Gamma;
dict[nameof(ThetaFromAngle)] = ThetaFromAngle;
dict[nameof(ThetaToAngle)] = ThetaToAngle;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(KSize)))
KSize = Convert.ToInt32(dict[nameof(KSize)]);
if (dict.ContainsKey(nameof(Sigma)))
Sigma = Convert.ToDouble(dict[nameof(Sigma)]);
if (dict.ContainsKey(nameof(NumFilters)))
NumFilters = Convert.ToInt32(dict[nameof(NumFilters)]);
if (dict.ContainsKey(nameof(Lambda)))
Lambda = Convert.ToDouble(dict[nameof(Lambda)]);
if (dict.ContainsKey(nameof(Gamma)))
Gamma = Convert.ToDouble(dict[nameof(Gamma)]);
if (dict.ContainsKey(nameof(ThetaFromAngle)))
ThetaFromAngle = Convert.ToDouble(dict[nameof(ThetaFromAngle)]);
if (dict.ContainsKey(nameof(ThetaToAngle)))
ThetaToAngle = Convert.ToDouble(dict[nameof(ThetaToAngle)]);
}
}

View File

@@ -57,4 +57,27 @@ public class LUTFilterOperation : BaseOperation
base.Load(br);
LutData.Deserialize(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
// Save LutData as serialized representation
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
LutData.Serialize(bw);
dict[nameof(LutData)] = Convert.ToBase64String(ms.ToArray());
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(LutData)))
{
var base64Data = dict[nameof(LutData)].ToString();
var bytes = Convert.FromBase64String(base64Data);
using var ms = new MemoryStream(bytes);
using var br = new BinaryReader(ms);
LutData.Deserialize(br);
}
}
}

View File

@@ -38,4 +38,17 @@ public class NoiseFilterOperation:BaseOperation
base.Load(br);
NoiseSize = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(NoiseSize)] = NoiseSize;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(NoiseSize)))
NoiseSize = Convert.ToInt32(dict[nameof(NoiseSize)]);
}
}

View File

@@ -51,5 +51,33 @@ public class RangeFilterOperation: BaseOperation
C3Max = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(C1Min)] = C1Min;
dict[nameof(C1Max)] = C1Max;
dict[nameof(C2Min)] = C2Min;
dict[nameof(C2Max)] = C2Max;
dict[nameof(C3Min)] = C3Min;
dict[nameof(C3Max)] = C3Max;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(C1Min)))
C1Min = Convert.ToInt32(dict[nameof(C1Min)]);
if (dict.ContainsKey(nameof(C1Max)))
C1Max = Convert.ToInt32(dict[nameof(C1Max)]);
if (dict.ContainsKey(nameof(C2Min)))
C2Min = Convert.ToInt32(dict[nameof(C2Min)]);
if (dict.ContainsKey(nameof(C2Max)))
C2Max = Convert.ToInt32(dict[nameof(C2Max)]);
if (dict.ContainsKey(nameof(C3Min)))
C3Min = Convert.ToInt32(dict[nameof(C3Min)]);
if (dict.ContainsKey(nameof(C3Max)))
C3Max = Convert.ToInt32(dict[nameof(C3Max)]);
}
}

View File

@@ -68,4 +68,22 @@ public class GetImageOperation:BaseOperation,IHaveImage
Id = new Guid(br.ReadString());
Label = br.ReadString();
}
/// <summary>
/// Save the operation to the dictionary
/// </summary>
/// <param name="dict"></param>
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
}
/// <summary>
/// Load the operation from the dictionary
/// </summary>
/// <param name="dict"></param>
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
}
}

View File

@@ -35,4 +35,17 @@ public class GetChannelOperation: BaseOperation
base.Load(br);
Channel = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Channel)] = Channel;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Channel)))
Channel = Convert.ToInt32(dict[nameof(Channel)]);
}
}

View File

@@ -41,4 +41,20 @@ public class ImageSizeProportionOperation:BaseOperation
Width = br.ReadDouble();
Height = br.ReadDouble();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Width)] = Width;
dict[nameof(Height)] = Height;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Width)))
Width = Convert.ToDouble(dict[nameof(Width)]);
if (dict.ContainsKey(nameof(Height)))
Height = Convert.ToDouble(dict[nameof(Height)]);
}
}

View File

@@ -39,4 +39,17 @@ public class ImageFromMemoryOperation:BaseOperation
base.Load(br);
SlotName = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(SlotName)] = SlotName;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(SlotName)))
SlotName = dict[nameof(SlotName)].ToString();
}
}

View File

@@ -33,4 +33,17 @@ public class ImageToMemoryOperation:BaseOperation
base.Load(br);
SlotName = br.ReadString();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(SlotName)] = SlotName;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(SlotName)))
SlotName = dict[nameof(SlotName)].ToString();
}
}

View File

@@ -40,4 +40,17 @@ public class ClosingOperation:BaseOperation
base.Load(br);
Closing = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Closing)] = Closing;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Closing)))
Closing = Convert.ToInt32(dict[nameof(Closing)]);
}
}

View File

@@ -80,4 +80,43 @@ public class CutOperation:BaseOperation
SearchArea.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ReferencePoint)] = ReferencePoint.ToString();
// Save SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_IsGood"] = SearchArea.IsGood;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_SizeX"] = SearchArea.Size.X;
dict["SearchArea_SizeY"] = SearchArea.Size.Y;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ReferencePoint)))
ReferencePoint = new Guid(dict[nameof(ReferencePoint)].ToString());
// Load SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable") || dict.ContainsKey("SearchArea_IsGood") || dict.ContainsKey("SearchArea_LocationX"))
{
SearchArea = new RectangleElement();
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_IsGood"))
SearchArea.IsGood = Convert.ToBoolean(dict["SearchArea_IsGood"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_SizeX") && dict.ContainsKey("SearchArea_SizeY"))
SearchArea.Size = new Vector2(Convert.ToSingle(dict["SearchArea_SizeX"]), Convert.ToSingle(dict["SearchArea_SizeY"]));
}
}
}

View File

@@ -41,4 +41,17 @@ public class DilationOperation:BaseOperation
base.Load(br);
Dilation = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Dilation)] = Dilation;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Dilation)))
Dilation = Convert.ToInt32(dict[nameof(Dilation)]);
}
}

View File

@@ -40,4 +40,17 @@ public class HatsOperation : BaseOperation
base.Load(br);
HatSize = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(HatSize)] = HatSize;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(HatSize)))
HatSize = Convert.ToInt32(dict[nameof(HatSize)]);
}
}

View File

@@ -40,4 +40,17 @@ public class OpeningOperation : BaseOperation
base.Load(br);
Opening = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Opening)] = Opening;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Opening)))
Opening = Convert.ToInt32(dict[nameof(Opening)]);
}
}

View File

@@ -45,4 +45,14 @@ public class SubtractOperation : BaseOperation
{
base.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
}
}

View File

@@ -51,4 +51,14 @@ public class TakeBiggestOperation : BaseOperation
{
base.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
}
}

View File

@@ -48,4 +48,14 @@ public class UnionOperation : BaseOperation
{
base.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
}
}

View File

@@ -155,4 +155,54 @@ public class FindBlobOperation : BaseOperation, IHaveOrigin, IHaveSearchArea
SearchArea.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(MinArea)] = MinArea;
dict[nameof(MaxArea)] = MaxArea;
dict[nameof(MinRadius)] = MinRadius;
dict[nameof(MaxRadius)] = MaxRadius;
dict[nameof(BadIfFound)] = BadIfFound;
dict[nameof(ReferenceId)] = ReferenceId.ToString();
// Save SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_IsGood"] = SearchArea.IsGood;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_SizeX"] = SearchArea.Size.X;
dict["SearchArea_SizeY"] = SearchArea.Size.Y;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(MinArea)))
MinArea = Convert.ToInt32(dict[nameof(MinArea)]);
if (dict.ContainsKey(nameof(MaxArea)))
MaxArea = Convert.ToInt32(dict[nameof(MaxArea)]);
if (dict.ContainsKey(nameof(MinRadius)))
MinRadius = Convert.ToInt32(dict[nameof(MinRadius)]);
if (dict.ContainsKey(nameof(MaxRadius)))
MaxRadius = Convert.ToInt32(dict[nameof(MaxRadius)]);
if (dict.ContainsKey(nameof(BadIfFound)))
BadIfFound = Convert.ToBoolean(dict[nameof(BadIfFound)]);
if (dict.ContainsKey(nameof(ReferenceId)))
ReferenceId = new Guid(dict[nameof(ReferenceId)].ToString());
// Load SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_IsGood"))
SearchArea.IsGood = Convert.ToBoolean(dict["SearchArea_IsGood"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_SizeX") && dict.ContainsKey("SearchArea_SizeY"))
SearchArea.Size = new Vector2(Convert.ToSingle(dict["SearchArea_SizeX"]), Convert.ToSingle(dict["SearchArea_SizeY"]));
}
}

View File

@@ -230,4 +230,78 @@ public class FindManyBlobsExtOperation : BaseOperation
ReferenceId = new Guid(br.ReadString());
SearchArea.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(MinArea)] = MinArea;
dict[nameof(MaxArea)] = MaxArea;
dict[nameof(MinRadius)] = MinRadius;
dict[nameof(MaxRadius)] = MaxRadius;
dict[nameof(CheckInternalRadius)] = CheckInternalRadius;
dict[nameof(MinInternalRadius)] = MinInternalRadius;
dict[nameof(MaxInternalRadius)] = MaxInternalRadius;
dict[nameof(CheckWidth)] = CheckWidth;
dict[nameof(MinWidth)] = MinWidth;
dict[nameof(MaxWidth)] = MaxWidth;
dict[nameof(CheckHeight)] = CheckHeight;
dict[nameof(MinHeight)] = MinHeight;
dict[nameof(MaxHeight)] = MaxHeight;
dict[nameof(ReferenceId)] = ReferenceId.ToString();
// Save SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_IsGood"] = SearchArea.IsGood;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_SizeX"] = SearchArea.Size.X;
dict["SearchArea_SizeY"] = SearchArea.Size.Y;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(MinArea)))
MinArea = Convert.ToInt32(dict[nameof(MinArea)]);
if (dict.ContainsKey(nameof(MaxArea)))
MaxArea = Convert.ToInt32(dict[nameof(MaxArea)]);
if (dict.ContainsKey(nameof(MinRadius)))
MinRadius = Convert.ToInt32(dict[nameof(MinRadius)]);
if (dict.ContainsKey(nameof(MaxRadius)))
MaxRadius = Convert.ToInt32(dict[nameof(MaxRadius)]);
if (dict.ContainsKey(nameof(CheckInternalRadius)))
CheckInternalRadius = Convert.ToBoolean(dict[nameof(CheckInternalRadius)]);
if (dict.ContainsKey(nameof(MinInternalRadius)))
MinInternalRadius = Convert.ToInt32(dict[nameof(MinInternalRadius)]);
if (dict.ContainsKey(nameof(MaxInternalRadius)))
MaxInternalRadius = Convert.ToInt32(dict[nameof(MaxInternalRadius)]);
if (dict.ContainsKey(nameof(CheckWidth)))
CheckWidth = Convert.ToBoolean(dict[nameof(CheckWidth)]);
if (dict.ContainsKey(nameof(MinWidth)))
MinWidth = Convert.ToInt32(dict[nameof(MinWidth)]);
if (dict.ContainsKey(nameof(MaxWidth)))
MaxWidth = Convert.ToInt32(dict[nameof(MaxWidth)]);
if (dict.ContainsKey(nameof(CheckHeight)))
CheckHeight = Convert.ToBoolean(dict[nameof(CheckHeight)]);
if (dict.ContainsKey(nameof(MinHeight)))
MinHeight = Convert.ToInt32(dict[nameof(MinHeight)]);
if (dict.ContainsKey(nameof(MaxHeight)))
MaxHeight = Convert.ToInt32(dict[nameof(MaxHeight)]);
if (dict.ContainsKey(nameof(ReferenceId)))
ReferenceId = new Guid(dict[nameof(ReferenceId)].ToString());
// Load SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_IsGood"))
SearchArea.IsGood = Convert.ToBoolean(dict["SearchArea_IsGood"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_SizeX") && dict.ContainsKey("SearchArea_SizeY"))
SearchArea.Size = new Vector2(Convert.ToSingle(dict["SearchArea_SizeX"]), Convert.ToSingle(dict["SearchArea_SizeY"]));
}
}

View File

@@ -172,4 +172,51 @@ public class FindManyBlobsOperation:BaseOperation
SearchArea.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(MinArea)] = MinArea;
dict[nameof(MaxArea)] = MaxArea;
dict[nameof(MinRadius)] = MinRadius;
dict[nameof(MaxRadius)] = MaxRadius;
dict[nameof(ReferenceId)] = ReferenceId.ToString();
// Save SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_IsGood"] = SearchArea.IsGood;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_SizeX"] = SearchArea.Size.X;
dict["SearchArea_SizeY"] = SearchArea.Size.Y;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(MinArea)))
MinArea = Convert.ToInt32(dict[nameof(MinArea)]);
if (dict.ContainsKey(nameof(MaxArea)))
MaxArea = Convert.ToInt32(dict[nameof(MaxArea)]);
if (dict.ContainsKey(nameof(MinRadius)))
MinRadius = Convert.ToInt32(dict[nameof(MinRadius)]);
if (dict.ContainsKey(nameof(MaxRadius)))
MaxRadius = Convert.ToInt32(dict[nameof(MaxRadius)]);
if (dict.ContainsKey(nameof(ReferenceId)))
ReferenceId = new Guid(dict[nameof(ReferenceId)].ToString());
// Load SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_IsGood"))
SearchArea.IsGood = Convert.ToBoolean(dict["SearchArea_IsGood"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_SizeX") && dict.ContainsKey("SearchArea_SizeY"))
SearchArea.Size = new Vector2(Convert.ToSingle(dict["SearchArea_SizeX"]), Convert.ToSingle(dict["SearchArea_SizeY"]));
}
}

View File

@@ -109,4 +109,54 @@ public class FindRectangleOperation:BaseOperation
BadIfFound = br.ReadBoolean();
SearchArea.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(MinWidth)] = MinWidth;
dict[nameof(MaxWidth)] = MaxWidth;
dict[nameof(MinHeight)] = MinHeight;
dict[nameof(MaxHeight)] = MaxHeight;
dict[nameof(BadIfFound)] = BadIfFound;
dict[nameof(ReferenceId)] = ReferenceId.ToString();
// Save SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_IsGood"] = SearchArea.IsGood;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_SizeX"] = SearchArea.Size.X;
dict["SearchArea_SizeY"] = SearchArea.Size.Y;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(MinWidth)))
MinWidth = Convert.ToInt32(dict[nameof(MinWidth)]);
if (dict.ContainsKey(nameof(MaxWidth)))
MaxWidth = Convert.ToInt32(dict[nameof(MaxWidth)]);
if (dict.ContainsKey(nameof(MinHeight)))
MinHeight = Convert.ToInt32(dict[nameof(MinHeight)]);
if (dict.ContainsKey(nameof(MaxHeight)))
MaxHeight = Convert.ToInt32(dict[nameof(MaxHeight)]);
if (dict.ContainsKey(nameof(BadIfFound)))
BadIfFound = Convert.ToBoolean(dict[nameof(BadIfFound)]);
if (dict.ContainsKey(nameof(ReferenceId)))
ReferenceId = new Guid(dict[nameof(ReferenceId)].ToString());
// Load SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_IsGood"))
SearchArea.IsGood = Convert.ToBoolean(dict["SearchArea_IsGood"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_SizeX") && dict.ContainsKey("SearchArea_SizeY"))
SearchArea.Size = new Vector2(Convert.ToSingle(dict["SearchArea_SizeX"]), Convert.ToSingle(dict["SearchArea_SizeY"]));
}
}

View File

@@ -103,4 +103,23 @@ public class TwoBlobsAlign:BaseOperation,IHaveOrigin
DesiredAngle = br.ReadInt32();
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(Reference1)] = Reference1.ToString();
dict[nameof(Reference2)] = Reference2.ToString();
dict[nameof(DesiredAngle)] = DesiredAngle;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(Reference1)))
Reference1 = new Guid(dict[nameof(Reference1)].ToString());
if (dict.ContainsKey(nameof(Reference2)))
Reference2 = new Guid(dict[nameof(Reference2)].ToString());
if (dict.ContainsKey(nameof(DesiredAngle)))
DesiredAngle = Convert.ToInt32(dict[nameof(DesiredAngle)]);
}
}

View File

@@ -100,5 +100,21 @@ public class EdgeIntersectionOperation : BaseOperation, IHaveOrigin
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ReferenceEdge1)] = ReferenceEdge1.ToString();
dict[nameof(ReferenceEdge2)] = ReferenceEdge2.ToString();
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ReferenceEdge1)))
ReferenceEdge1 = new Guid(dict[nameof(ReferenceEdge1)].ToString());
if (dict.ContainsKey(nameof(ReferenceEdge2)))
ReferenceEdge2 = new Guid(dict[nameof(ReferenceEdge2)].ToString());
}
public OriginElement Origin { get; set; } = OriginElement.Default;
}

View File

@@ -124,6 +124,48 @@ public class FindEdgeOperation : BaseOperation, IHaveEdge, IHaveOrigin
SearchArea.Load(br);
}
public override void Save(Dictionary<string, object> dict)
{
base.Save(dict);
dict[nameof(ReferenceId)] = ReferenceId.ToString();
// Save RotatedRectangleElement SearchArea properties manually
dict["SearchArea_Editable"] = SearchArea.Editable;
dict["SearchArea_IsGood"] = SearchArea.IsGood;
dict["SearchArea_LocationX"] = SearchArea.Location.X;
dict["SearchArea_LocationY"] = SearchArea.Location.Y;
dict["SearchArea_OffsetX"] = SearchArea.Offset.X;
dict["SearchArea_OffsetY"] = SearchArea.Offset.Y;
dict["SearchArea_HeightX"] = SearchArea.Height.X;
dict["SearchArea_HeightY"] = SearchArea.Height.Y;
dict["SearchArea_HalfWidth"] = SearchArea.HalfWidth;
}
public override void Load(Dictionary<string, object> dict)
{
base.Load(dict);
if (dict.ContainsKey(nameof(ReferenceId)))
ReferenceId = new Guid(dict[nameof(ReferenceId)].ToString());
// Load RotatedRectangleElement SearchArea properties manually
if (dict.ContainsKey("SearchArea_Editable") || dict.ContainsKey("SearchArea_LocationX") || dict.ContainsKey("SearchArea_HeightX"))
{
SearchArea = new RotatedRectangleElement();
if (dict.ContainsKey("SearchArea_Editable"))
SearchArea.Editable = Convert.ToBoolean(dict["SearchArea_Editable"]);
if (dict.ContainsKey("SearchArea_IsGood"))
SearchArea.IsGood = Convert.ToBoolean(dict["SearchArea_IsGood"]);
if (dict.ContainsKey("SearchArea_LocationX") && dict.ContainsKey("SearchArea_LocationY"))
SearchArea.Location = new Vector2(Convert.ToSingle(dict["SearchArea_LocationX"]), Convert.ToSingle(dict["SearchArea_LocationY"]));
if (dict.ContainsKey("SearchArea_OffsetX") && dict.ContainsKey("SearchArea_OffsetY"))
SearchArea.Offset = new Vector2(Convert.ToSingle(dict["SearchArea_OffsetX"]), Convert.ToSingle(dict["SearchArea_OffsetY"]));
if (dict.ContainsKey("SearchArea_HeightX") && dict.ContainsKey("SearchArea_HeightY"))
SearchArea.Height = new Vector2(Convert.ToSingle(dict["SearchArea_HeightX"]), Convert.ToSingle(dict["SearchArea_HeightY"]));
if (dict.ContainsKey("SearchArea_HalfWidth"))
SearchArea.HalfWidth = Convert.ToSingle(dict["SearchArea_HalfWidth"]);
}
}
public RotatedRectangleElement SearchArea { get; set; }
public OriginElement Origin { get; set; }

View File

@@ -5,6 +5,7 @@ using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Drawing;
using System.Reflection.PortableExecutable;
using System.Text.Json;
using System.Xml;
using VisionBuilder.UI.Common.RecipeProcessing;
using Size = OpenCvSharp.Size;
@@ -32,6 +33,11 @@ public class WorkflowList
long total = 0;
foreach (BaseOperation operation in Operations)
{
if (!operation.Enabled)
{
operation.Result=true;
continue;
}
if (Context.CancellationToken.IsCancellationRequested) break;
operation.Interpret(Context);
}
@@ -62,6 +68,7 @@ public class WorkflowList
UpdateConfigVariables();
foreach (BaseOperation operation in Operations)
{
if(!operation.Enabled)continue;
if(operation == op) break;
operation.Interpret(Context);
}
@@ -275,7 +282,194 @@ public class WorkflowList
}
}
private Dictionary<string, object> ConvertJsonElementsToNatives(Dictionary<string, object> dict)
{
var result = new Dictionary<string, object>();
foreach (var kvp in dict)
{
result[kvp.Key] = ConvertJsonElementToNative(kvp.Value);
}
return result;
}
private object ConvertJsonElementToNative(object value)
{
if (value is JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.TryGetInt32(out var intVal) ? intVal :
element.TryGetDouble(out var doubleVal) ? doubleVal :
element.GetDecimal(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
JsonValueKind.Object => ConvertJsonElementsToNatives(
JsonSerializer.Deserialize<Dictionary<string, object>>(element.GetRawText())),
JsonValueKind.Array => element.EnumerateArray()
.Select(x => ConvertJsonElementToNative(x)).ToArray(),
_ => value
};
}
else if (value is Dictionary<string, object> nestedDict)
{
return ConvertJsonElementsToNatives(nestedDict);
}
return value;
}
public void SaveJSON(string fileName)
{
var workflowData = new Dictionary<string, object>();
// Save Configuration
var configData = new Dictionary<string, object>
{
["RuntimeCameraType"] = Configuration.RuntimeCameraType.ToString(),
["DevelopmentCameraType"] = Configuration.DevelopmentCameraType.ToString(),
["Outputs"] = Configuration.Outputs.ToString(),
["EmulationPath"] = Configuration.EmulationPath,
["PythonPath"] = Configuration.PythonPath,
["ResultPin"] = Configuration.ResultPin,
["SerialPort"] = Configuration.SerialPort,
["Delay"] = Configuration.Delay
};
workflowData["Configuration"] = configData;
// Save Operations
var operationsData = new List<Dictionary<string, object>>();
foreach (BaseOperation operation in Operations)
{
var operationData = new Dictionary<string, object>
{
["Type"] = operation.GetType().AssemblyQualifiedName
};
var operationDict = new Dictionary<string, object>();
operation.Save(operationDict);
operationData["Data"] = operationDict;
operationsData.Add(operationData);
}
workflowData["Operations"] = operationsData;
// Save Recipe Image
if (RecipeImage == null)
{
var mat = new Mat(128, 128, MatType.CV_8UC3, Scalar.Gray);
var image = mat.ToBytes();
workflowData["RecipeImage"] = Convert.ToBase64String(image);
}
else
{
var mat = RecipeImage;
var image = mat.Resize(new Size(128, 128)).ToBytes();
workflowData["RecipeImage"] = Convert.ToBase64String(image);
}
var options = new JsonSerializerOptions
{
WriteIndented = true
};
string jsonString = JsonSerializer.Serialize(workflowData, options);
File.WriteAllText(fileName, jsonString);
}
public void LoadJSON(string fileName, OperationDiscoveryService operationDiscoveryService)
{
string jsonString = File.ReadAllText(fileName);
var workflowData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
if (workflowData == null) return;
// Load Configuration
if (workflowData.ContainsKey("Configuration"))
{
var configElement = (JsonElement)workflowData["Configuration"];
var configData = JsonSerializer.Deserialize<Dictionary<string, object>>(configElement.GetRawText());
if (configData != null)
{
var convertedConfigData = ConvertJsonElementsToNatives(configData);
if (convertedConfigData.ContainsKey("RuntimeCameraType"))
Configuration.RuntimeCameraType = Enum.Parse<ECameraType>(convertedConfigData["RuntimeCameraType"].ToString());
if (convertedConfigData.ContainsKey("DevelopmentCameraType"))
Configuration.DevelopmentCameraType = Enum.Parse<ECameraType>(convertedConfigData["DevelopmentCameraType"].ToString());
if (convertedConfigData.ContainsKey("Outputs"))
Configuration.Outputs = Enum.Parse<EOutputs>(convertedConfigData["Outputs"].ToString());
if (convertedConfigData.ContainsKey("EmulationPath"))
Configuration.EmulationPath = convertedConfigData["EmulationPath"].ToString();
if (convertedConfigData.ContainsKey("PythonPath"))
Configuration.PythonPath = convertedConfigData["PythonPath"].ToString();
if (convertedConfigData.ContainsKey("ResultPin"))
Configuration.ResultPin = (int)convertedConfigData["ResultPin"];
if (convertedConfigData.ContainsKey("SerialPort"))
Configuration.SerialPort = convertedConfigData["SerialPort"].ToString();
if (convertedConfigData.ContainsKey("Delay"))
Configuration.Delay = (int)convertedConfigData["Delay"];
}
}
if (!File.Exists(Configuration.PythonPath))
PythonMissing();
// Load Operations
Operations.Clear();
if (workflowData.ContainsKey("Operations"))
{
var operationsElement = (JsonElement)workflowData["Operations"];
var operationsArray = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(operationsElement.GetRawText());
if (operationsArray != null)
{
foreach (var operationData in operationsArray)
{
if (operationData.ContainsKey("Type") && operationData.ContainsKey("Data"))
{
string operationType = operationData["Type"].ToString();
var type = Type.GetType(operationType);
if (type != null)
{
BaseOperation operation = operationDiscoveryService.CreateInstance(type);
var dataElement = (JsonElement)operationData["Data"];
var operationDict = JsonSerializer.Deserialize<Dictionary<string, object>>(dataElement.GetRawText());
if (operationDict != null)
{
var convertedDict = ConvertJsonElementsToNatives(operationDict);
operation.Load(convertedDict);
}
Operations.Add(operation);
}
}
}
}
}
// Load Recipe Image
if (workflowData.ContainsKey("RecipeImage"))
{
string base64Image = workflowData["RecipeImage"].ToString();
if (!string.IsNullOrEmpty(base64Image))
{
byte[] imageData = Convert.FromBase64String(base64Image);
RecipeImage = Mat.FromImageData(imageData);
}
}
}
public static WorkflowList LoadJSONFromFile(string fileName)
{
var workflowList = new WorkflowList();
workflowList.LoadJSON(fileName, new OperationDiscoveryService(workflowList));
return workflowList;
}
}

View File

@@ -43,7 +43,7 @@ namespace Hawkeye.VisionBuilder.Features.ImageStatistics
_goodNode.Nodes.Add(new TreeNode(nodeText) { Tag = filePath });
_allFiles.Add(filePath);
UpdateBranchText(_goodNode, "Good");
treeView.ExpandAll();
//treeView.ExpandAll();
}
public void AddBadImage(string filePath, string[]? defects = null)
@@ -66,7 +66,7 @@ namespace Hawkeye.VisionBuilder.Features.ImageStatistics
_badNode.Nodes.Add(new TreeNode(nodeText) { Tag = filePath });
_allFiles.Add(filePath);
UpdateBranchText(_badNode, "Bad");
treeView.ExpandAll();
//treeView.ExpandAll();
}
private void UpdateBranchText(TreeNode node, string name)

View File

@@ -35,7 +35,7 @@
btnTest = new Button();
button1 = new Button();
Indicator = new DataGridViewTextBoxColumn();
Link = new DataGridViewTextBoxColumn();
Enabled = new DataGridViewCheckBoxColumn();
UserName = new DataGridViewTextBoxColumn();
Result = new DataGridViewTextBoxColumn();
TypeName = new DataGridViewTextBoxColumn();
@@ -50,14 +50,13 @@
dataGridView1.AllowUserToAddRows = false;
dataGridView1.AllowUserToDeleteRows = false;
dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridView1.Columns.AddRange(new DataGridViewColumn[] { Indicator, Link, UserName, Result, TypeName, Time });
dataGridView1.Columns.AddRange(new DataGridViewColumn[] { Indicator, Enabled, UserName, Result, TypeName, Time });
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.Location = new Point(0, 40);
dataGridView1.MultiSelect = false;
dataGridView1.Name = "dataGridView1";
dataGridView1.ReadOnly = true;
dataGridView1.RowHeadersVisible = false;
dataGridView1.RowTemplate.Height = 25;
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridView1.Size = new Size(540, 562);
dataGridView1.TabIndex = 0;
@@ -126,12 +125,12 @@
Indicator.ReadOnly = true;
Indicator.Width = 32;
//
// Link
// Enabled
//
Link.HeaderText = "";
Link.Name = "Link";
Link.ReadOnly = true;
Link.Width = 32;
Enabled.HeaderText = "";
Enabled.Name = "Enabled";
Enabled.ReadOnly = true;
Enabled.Width = 32;
//
// UserName
//
@@ -182,7 +181,7 @@
private Label lblProcessingTime;
private Label label1;
private DataGridViewTextBoxColumn Indicator;
private DataGridViewTextBoxColumn Link;
private DataGridViewCheckBoxColumn Enabled;
private DataGridViewTextBoxColumn UserName;
private DataGridViewTextBoxColumn Result;
private DataGridViewTextBoxColumn TypeName;

View File

@@ -33,6 +33,7 @@ namespace Hawkeye.VisionBuilder.Features.VisionSteps
dataGridView1.DataSource = _bindingSource;
dataGridView1.Columns[0].DataPropertyName = nameof(BaseOperationDecorator.Result);
dataGridView1.Columns[1].DataPropertyName = nameof(BaseOperationDecorator.IsEnabled);
dataGridView1.Columns[2].DataPropertyName = nameof(BaseOperationDecorator.Label);
dataGridView1.Columns[2].ReadOnly = false;
dataGridView1.ReadOnly = false;
@@ -88,29 +89,6 @@ namespace Hawkeye.VisionBuilder.Features.VisionSteps
private void DataGridView1_Paint(object? sender, PaintEventArgs e)
{
var g = e.Graphics;
var c1 = dataGridView1.GetCellDisplayRectangle(1, 0, true);
var c2 = dataGridView1.GetCellDisplayRectangle(1, 1, true);
Pen start = new Pen(Color.Blue, 2);
Pen body = new Pen(Color.Blue, 2);
Pen end = new Pen(Color.Blue, 2);
end.EndCap = LineCap.Custom;
AdjustableArrowCap bigArrow = new AdjustableArrowCap(5, 5);
end.CustomEndCap = bigArrow;
g.DrawLine(start, c1.Right, (c1.Bottom - c1.Top) / 2 + c1.Top, (c1.Right - c1.Left) / 2 + c1.Left,
(c1.Bottom - c1.Top) / 2 + c1.Top);
g.DrawLine(body, (c1.Right - c1.Left) / 2 + c1.Left, (c1.Bottom - c1.Top) / 2 + c1.Top,
(c2.Right - c2.Left) / 2 + c1.Left, (c2.Bottom - c2.Top) / 2 + c2.Top);
g.DrawLine(end, (c2.Right - c2.Left) / 2 + c2.Left, (c2.Bottom - c2.Top) / 2 + c2.Top, c2.Right,
(c2.Bottom - c2.Top) / 2 + c2.Top);
}
@@ -119,24 +97,32 @@ namespace Hawkeye.VisionBuilder.Features.VisionSteps
private void DataGridView1_CellPainting(object? sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex > -1 && e.Value != null)
if (e.RowIndex > -1)
{
var cx = e.CellBounds.Width / 2 + e.CellBounds.Left;
var cy = e.CellBounds.Height / 2 + e.CellBounds.Top;
var radius = e.CellBounds.Height / 2 - 3;
var c1 = dataGridView1.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true);
var row = dataGridView1.Rows[e.RowIndex];
var rowValue = row.DataBoundItem as BaseOperationDecorator;
e.PaintBackground(c1, true);
if ((bool)e.Value)
if (e.ColumnIndex == 0 && e.Value != null)
{
e.Graphics.FillEllipse(Brushes.Green, cx - radius, cy - radius, radius * 2, radius * 2);
}
else
{
e.Graphics.FillEllipse(Brushes.Red, cx - radius, cy - radius, radius * 2, radius * 2);
}
var cx = e.CellBounds.Width / 2 + e.CellBounds.Left;
var cy = e.CellBounds.Height / 2 + e.CellBounds.Top;
var radius = e.CellBounds.Height / 2 - 3;
e.Handled = true;
if ((bool) e.Value)
{
e.Graphics.FillEllipse(Brushes.Green, cx - radius, cy - radius, radius * 2, radius * 2);
}
else
{
e.Graphics.FillEllipse(Brushes.Red, cx - radius, cy - radius, radius * 2, radius * 2);
}
e.Handled = true;
}
}
}

View File

@@ -1,4 +1,64 @@
<root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
@@ -60,7 +120,7 @@
<metadata name="Indicator.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Link.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<metadata name="Enabled.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="UserName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">

View File

@@ -182,13 +182,26 @@ namespace Hawkeye.VisionBuilder
private void loadRecipeToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "*.hrcp|*.hrcp";
dialog.Filter = "Suported types|*.jhrcp;*.hrcp";
PrepareDialog(dialog);
if (dialog.ShowDialog() == DialogResult.OK)
{
var path = dialog.FileName;
LoadRecipe(path);
var ext = Path.GetExtension(path);
if (ext == ".jhrcp")
{
LoadRecipeJSON(path);
}
else if (ext == ".hrcp")
{
LoadRecipeBinary(path);
}
else
{
MessageBox.Show("Unsupported file format. Please use .hrcp or .jhrcp files.");
return;
}
}
}
@@ -210,7 +223,7 @@ namespace Hawkeye.VisionBuilder
private string _loadedPath = null;
public void LoadRecipe(string path)
public void LoadRecipeBinary(string path)
{
_loadedPath = path;
var bytes = File.ReadAllBytes(path);
@@ -218,13 +231,23 @@ namespace Hawkeye.VisionBuilder
BinaryReader reader = new BinaryReader(stream);
_workflowList.PythonMissing += _workflowList_PythonMissing;
_workflowList.Load(reader, _discoveryService);
ReinitCameraPanel(_workflowList);
OrganizeWindows(null, null);
_visionStepsPanel.RefreshWorkflow();
}
public void LoadRecipeJSON(string path)
{
_loadedPath = path;
_workflowList.LoadJSON(path,_discoveryService);
ReinitCameraPanel(_workflowList);
OrganizeWindows(null, null);
_visionStepsPanel.RefreshWorkflow();
}
private void _workflowList_PythonMissing()
{
SetupConfig();
@@ -234,20 +257,33 @@ namespace Hawkeye.VisionBuilder
private void saveRecipeToolStripMenuItem_Click(object sender, EventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "*.hrcp|*.hrcp";
dialog.Filter = "*.jhrcp|*.jhrcp|*.hrcp|*.hrcp";
PrepareDialog(dialog);
if (dialog.ShowDialog() == DialogResult.OK)
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
_workflowList.RecipeImage = _workflowList.Context.LastCameraImage.ImageData;
_workflowList.Save(bw);
ms.Close();
File.WriteAllBytes(dialog.FileName, ms.ToArray());
var ext = Path.GetExtension(dialog.FileName);
if (ext == ".jhrcp")
{
_workflowList.SaveJSON(dialog.FileName);
}
else
{
SaveBinary(dialog.FileName);
}
}
}
private void SaveBinary(string path)
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
_workflowList.RecipeImage = _workflowList.Context.LastCameraImage.ImageData;
_workflowList.Save(bw);
ms.Close();
File.WriteAllBytes(path, ms.ToArray());
}
private void toolBtnConfiguration_Click(object sender, EventArgs e)
{
SetupConfig();

View File

@@ -1,4 +1,5 @@
using Hawkeye.VisionBuilder.UI.Sources.Emulation;
using Hawkeye.VisionBuilder.UI.Sources.Hawkeye;
using Inspectron.Camera.UEye;
using Ninject;
using Ninject.Extensions.ChildKernel;
@@ -26,6 +27,7 @@ public static class ModuleExtensions
kernel.Bind<IImageSource, IVisionBuilderModule>().To<EmulationCameraImageSource>().InSingletonScope();
break;
case CameraSettings.EImageSource.Hawkeye:
kernel.Bind<IImageSource, IVisionBuilderModule>().To<HawkeyeCameraImageSource>().InSingletonScope();
break;
case CameraSettings.EImageSource.IDS:
kernel.Bind<IImageSource, IVisionBuilderModule>().To<IDSImageSource>().InSingletonScope();

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\Hawkeye.VisionBuilder.UI.Sources.Emulation\Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj" />
<ProjectReference Include="..\Hawkeye.VisionBuilder.UI.Sources.Hawkeye\Hawkeye.VisionBuilder.UI.Sources.Hawkeye.csproj" />
<ProjectReference Include="..\Hawkeye.VisionBuilder.UI.Sources.IDS\Hawkeye.VisionBuilder.UI.Sources.IDS.csproj" />
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
</ItemGroup>

View File

@@ -75,6 +75,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "B24SiemensEmulator", "Plugi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPlugin", "Plugins\TestPlugin\TestPlugin.csproj", "{F7D39916-489A-3583-09A0-175AE82D08B7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hawkeye.VisionBuilder.UI.Sources.Hawkeye", "Hawkeye.VisionBuilder.UI.Sources.Hawkeye\Hawkeye.VisionBuilder.UI.Sources.Hawkeye.csproj", "{C55BE2DA-C60B-491C-8668-9517C7F6FF2F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspectron.HawkEye", "framework\Inspectron.HawkEye\Inspectron.HawkEye.csproj", "{44D1BA17-FB52-40A2-9D99-E49DA56C10C2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Starters", "Starters", "{F3414823-B70E-435E-B4EA-80ABF4371449}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -197,12 +203,22 @@ Global
{F7D39916-489A-3583-09A0-175AE82D08B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7D39916-489A-3583-09A0-175AE82D08B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7D39916-489A-3583-09A0-175AE82D08B7}.Release|Any CPU.Build.0 = Release|Any CPU
{C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C55BE2DA-C60B-491C-8668-9517C7F6FF2F}.Release|Any CPU.Build.0 = Release|Any CPU
{44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44D1BA17-FB52-40A2-9D99-E49DA56C10C2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B62D435C-3572-4B5B-A381-992345D343B0} = {F3414823-B70E-435E-B4EA-80ABF4371449}
{D5FD2E9D-DA4F-1343-47E0-FBC473A149BC} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
{7697A266-A721-4D74-9C5C-0D4F2F6BBF68} = {F2406FBB-DFD3-4CBE-9644-A9FFC2FCBB71}
{E286CE4C-B68A-94B6-F477-0DBF42358009} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
{E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
{8B7FAFEE-4066-483C-9C9C-10D2D596206D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
@@ -224,6 +240,8 @@ Global
{DB20082B-60E8-D623-3F3A-04612A929CD6} = {583A77DF-A293-4F3E-AB96-7310BC495822}
{52D6A9AE-D0F1-4C52-B688-E0219169E179} = {583A77DF-A293-4F3E-AB96-7310BC495822}
{F7D39916-489A-3583-09A0-175AE82D08B7} = {F2406FBB-DFD3-4CBE-9644-A9FFC2FCBB71}
{C55BE2DA-C60B-491C-8668-9517C7F6FF2F} = {D05689E3-04C6-4E3B-ACA7-3F4507CED4CC}
{44D1BA17-FB52-40A2-9D99-E49DA56C10C2} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}

View File

@@ -0,0 +1,159 @@
using System;
using System.IO;
using Mono.Unix.Native;
namespace Inspectron.Devices.Raspberry
{
public unsafe class I2CLinux
{
string device;
int fd = -1;
public I2CLinux(int index)
{
device = "/dev/i2c-" + index;
Open();
//Close();
}
public void Open()
{
fd = Syscall.open(device, OpenFlags.O_RDWR);
if (fd < 0)
throw new IOException(device);
}
void IoCtl(byte devAddr)
{
int ret = LunixNatives.ioctl(fd, LunixNatives.I2C_SLAVE, devAddr);
if (ret < 0)
throw new IOException(device + ": ioctl");
}
public byte readBytes(byte devAddr, byte regAddr, byte length, byte[] data, int offset, ushort timeout = 0)
{
if (length > 127)
throw new IOException(device + ": length > 127");
//Open();
IoCtl(devAddr);
//fixed(byte* p = &regAddr)
{
int ret = (int)Syscall.write(fd, &regAddr, 1);
if (ret != 1)
throw new IOException(device + ": write");
}
int count;
fixed (byte* p = &data[offset])
{
count = (int)Syscall.read(fd, p, (ulong)length);
if (count < 0)
throw new IOException(device + ": read");
else if (count != length)
throw new IOException(device + ": read short: length = " + length + " > " + count);
}
//Close();
return (byte)count;
}
public byte readBytes(byte devAddr, byte regAddr, byte length, byte[] data, ushort timeout = 0)
{
return readBytes(devAddr, regAddr, length, data, 0, timeout);
}
/** Write multiple bytes to an 8-bit device register.
* @param devAddr I2C slave device address
* @param regAddr First register address to write to
* @param length Number of bytes to write
* @param data Buffer to copy new data from
* @return Status of operation (true = success)
*/
public void writeBytes(byte devAddr, byte regAddr, byte length, byte[] data)
{
if (length > 127)
throw new IOException(device + ": length > 127");
//Open();
IoCtl(devAddr);
byte[] buffer = new byte[128];
buffer[0] = regAddr;
Array.Copy(data, 0, buffer, 1, length);
int count;
fixed (byte* p = buffer)
{
count = (int)Syscall.write(fd, p, (ulong)(length + 1));
}
if (count < 0)
{
throw new IOException(device + ": write = " + count);
}
else if (count != length + 1)
{
throw new IOException(device + ": write short = " + count);
}
//Close();
}
/** Write multiple words to a 16-bit device register.
* @param devAddr I2C slave device address
* @param regAddr First register address to write to
* @param length Number of words to write
* @param data Buffer to copy new data from
* @return Status of operation (true = success)
*/
public void writeWords(byte devAddr, byte regAddr, byte length, ushort[] data)
{
int count = 0;
byte[] buf = new byte[128];
int i;
// Should do potential byteswap and call writeBytes() really, but that
// messes with the callers buffer
if (length > 63)
{
throw new IOException(device + ": length > 63");
}
//Open();
IoCtl(devAddr);
buf[0] = regAddr;
for (i = 0; i < (int)length; i++)
{
buf[i * 2 + 1] = (byte)(data[i] >> 8);
buf[i * 2 + 2] = (byte)data[i];
}
fixed (byte* p = buf)
{
count = (int)Syscall.write(fd, p, (ulong)(length * 2 + 1));
}
if (count < 0)
{
throw new IOException(device + ": write");
}
else if (count != length * 2 + 1)
{
throw new IOException(device + ": write short");
}
//Close();
}
public void Close()
{
int ret = Syscall.close(fd);
if (ret != 0)
throw new IOException(device);
}
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Inspectron.Devices.Raspberry;
using Inspectron.HawkEye.Protocol;
using Inspectron.HawkEye.Protocol.Interfaces;
namespace Inspectron.HawkEye.Camera
{
public class InspectronCamera:ICameraControl,IImageSource,ILightControl
{
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr init(Int32 captBuf);
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
static extern int trigger(IntPtr cpt, byte[] addr, int lines, int captBuf,ref int cancelFlag);
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
static extern void set_parameters(IntPtr cpt, ref ImageSettings imageSettings);
[DllImport("libVCLibProxy.so", CallingConvention = CallingConvention.Cdecl)]
public static extern int test();
byte[] _buffer = new byte[2048 * 250 * 4];
public InspectronCamera()
{
_i2c = new I2CLinux(0);
_i2c.Open();
}
private bool _isRunningContiniuos = false;
private IntPtr _cp= IntPtr.Zero;
private ImageSettings _imageSettings=new ImageSettings()
{
CaptureBuffer = 25,
Gain = 200,
Lines = 1000,
Shutter = 200,
SensorWidth = 1440
};
private I2CLinux _i2c;
private int _lastBuffer = -1;
public void SetParameters(CameraSettings cameraSettings)
{
_cameraSettings = cameraSettings;
ImageSettings imageSettings = cameraSettings.ImageSettings;
if (_lastBuffer ==-1)
{
_lastBuffer = imageSettings.CaptureBuffer;
_cp = init(_lastBuffer);
}
_imageSettings = imageSettings;
_buffer=new byte[imageSettings.SensorWidth*(imageSettings.Lines)];
var cpImageSettings = imageSettings;
cpImageSettings.UseExternalTrigger =imageSettings.UseExternalTrigger;
if(_lastBuffer==imageSettings.CaptureBuffer)
set_parameters(_cp, ref cpImageSettings);
else
Console.WriteLine("Warning! Buffer size changed. Needs restart");
_i2c.writeBytes(4, 2, 4, BitConverter.GetBytes(imageSettings.Divider));
Thread.Sleep(100);
_i2c.writeBytes(4,3,1, new byte[] { (byte)imageSettings.UseExternalTrigger });
Thread.Sleep(100);
}
private int _cancelFlag = 0;
private CameraSettings _cameraSettings;
public byte[] GetImage()
{
_cancelFlag = 0;
var sw = Stopwatch.StartNew();
_i2c.writeBytes(4, 4, 1,new byte[]{ (byte)(_cameraSettings.LaserTrigger?1:0)} );
//Thread.Sleep(20);
_i2c.writeBytes(4, 5, 4, BitConverter.GetBytes(_cameraSettings.LaserTriggerDelay));
//Thread.Sleep(20);
trigger(_cp, _buffer, _imageSettings.Lines, _imageSettings.CaptureBuffer,ref _cancelFlag);
sw.Stop();
Console.WriteLine("Trigger time: "+sw.ElapsedMilliseconds);
if(_cancelFlag==1)return new byte[0];
return _buffer;
}
public void CancelTrigger()
{
_cancelFlag = 1;
}
public void SetLight(int pwm1, int pwm2)
{
Console.WriteLine($"setting lights to {pwm1}/{pwm2}");
pwm1 = (int)(pwm1 / 100.0 * 255);
pwm2 = (int)(pwm2 / 100.0 * 255);
_i2c.writeBytes(4, 1, 2, new byte[] { (byte)pwm1,(byte)pwm2 });
Thread.Sleep(10);
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
namespace Inspectron.Devices.Raspberry
{
public static class LunixNatives
{
public const int O_RDWR = 2;
[DllImport("libc.so.6")]
extern public static int open(string file, int mode);
[DllImport("libc.so.6")]
extern public static int close(int fd);
[DllImport("libc.so.6")]
extern public static int ioctl(int fd, int request, byte x);
public const int I2C_SLAVE = 0x0703;
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace Inspectron.HawkEye
{
public class DefragmentedPacket
{
private readonly uint _packetSize;
private byte[] _receivedParts = null;
//ConcurrentDictionary<uint,byte[]> _packetParts = new ConcurrentDictionary<uint, byte[]>();
private byte[][] _packetParts;
public DefragmentedPacket(uint packetSize)
{
_packetSize = packetSize;
_packetParts=new byte[30000][];
}
private int _uniquePackets=0;
public void Defragment(byte[] data)
{
var packetStart = -1;
for (int i = 0; i < data.Length; i++)
{
if (BitConverter.ToUInt32(data, i) == 114455)
{
packetStart = i;
break;
};
}
if (packetStart == -1) return;
MemoryStream ms = new MemoryStream(data,packetStart, data.Length - packetStart);
BinaryReader br = new BinaryReader(ms);
br.ReadUInt32();//packetStart
var packetType = br.ReadUInt32();
var sequenceId = br.ReadUInt32();
var packetNumber = br.ReadUInt32();
var totalPackets = br.ReadUInt32();
if(_receivedParts==null)_receivedParts=new byte[totalPackets];
if (_receivedParts[packetNumber] == 1) return;
_receivedParts[packetNumber] = 1;
Interlocked.Increment(ref _uniquePackets);
var dataLen = br.ReadInt32();
var dataBytes = br.ReadBytes(dataLen);
_packetParts[packetNumber] = dataBytes;
}
public byte[] Reconstruct()
{
var parts = _receivedParts.Length;
byte[] res = new byte[parts*_packetSize];
Stopwatch sw = Stopwatch.StartNew();
int resSize = 0;
for (int i = 0; i < parts; i++)
{
//if (_packetParts.ContainsKey((uint) i))
if (_packetParts[i]!=null)
{
var packetData = _packetParts[(uint) i];
Array.Copy(packetData, 0,res, resSize, packetData.Length);
resSize += packetData.Length;
}
else
{
resSize += (int)_packetSize-20/*headerSize*/;
}
}
sw.Stop();
Array.Resize(ref res,resSize);
return res;
}
public bool IsComplete
{
get
{
if (_receivedParts == null) return false;
return _uniquePackets == _receivedParts.Length;
//return _receivedParts.All(x => x == 1);
}
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace Inspectron.HawkEye
{
public class FragmentedPacket
{
private readonly uint _packetSize;
private readonly EPacketType _packetType;
private readonly uint _sequnceId;
public FragmentedPacket(uint packetSize,EPacketType packetType,uint sequnceId)
{
_packetSize = packetSize;
_packetType = packetType;
_sequnceId = sequnceId;
}
public IEnumerable<byte[]> Fragment(byte[] packetData)
{
uint dataPtr = 0;
int packetNumber = 0;
uint headerSize = 20;
uint payloadSize = (_packetSize - headerSize);
var totalPackets = (uint)Math.Ceiling(((double)packetData.Length / payloadSize));
do
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
bw.Write((uint) 114455); //packetStart //4
bw.Write((uint) _packetType); //8
bw.Write((uint) _sequnceId); //12
bw.Write((uint) packetNumber); //16
//total packets?
bw.Write(totalPackets); //20
byte[] data = new byte[payloadSize];
uint dataSize = Math.Min((uint)(packetData.Length-dataPtr), payloadSize);
Array.Copy(packetData, dataPtr, data, 0, dataSize);
dataPtr += dataSize;
bw.Write(dataSize);
bw.Write(data);
packetNumber += 1;
yield return ms.ToArray();
} while (packetNumber < totalPackets);
}
public IEnumerable<byte[]> FragmentTo(byte[] packetData,UDPSocket socket)
{
uint dataPtr = 0;
int packetNumber = 0;
uint headerSize = 20;
uint payloadSize = (_packetSize - headerSize);
var totalPackets = (uint)Math.Ceiling(((double)packetData.Length / payloadSize));
do
{
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
bw.Write((uint)114455); //packetStart //4
bw.Write((uint)_packetType); //8
bw.Write((uint)_sequnceId); //12
bw.Write((uint)packetNumber); //16
//total packets?
bw.Write(totalPackets); //20
byte[] data = new byte[payloadSize];
uint dataSize = Math.Min((uint)(packetData.Length - dataPtr), payloadSize);
Array.Copy(packetData, dataPtr, data, 0, dataSize);
dataPtr += dataSize;
bw.Write(dataSize);
bw.Write(data);
packetNumber += 1;
yield return ms.ToArray();
} while (packetNumber < totalPackets);
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.3" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
namespace Inspectron.HawkEye
{
public enum EPacketType
{
Test,
ImageData,
ImageRequest
}
}

View File

@@ -0,0 +1,7 @@
namespace Inspectron.HawkEye
{
public class ImageRequestPacket
{
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Inspectron.HawkEye
{
public class Packet
{
public EPacketType PacketType { get; set; }
public byte[] Payload { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace Inspectron.HawkEye
{
public class PacketImage
{
public uint TriggerId { get; set; }
public uint PacketId { get; set; }
public uint TotalPackets { get; set; }
public uint StartIndex { get; set; }
public byte[] Data { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace Inspectron.HawkEye.Protocol
{
public class CameraSettings
{
public ImageSettings ImageSettings { get; set; }
public int LightPwm1 { get; set; }
public int LightPwm2 { get; set; }
public string Name { get; set; }
public int OffsetX { get; set; }
public int ImageWidth { get; set; }
public int RescaleWidth { get; set; }
public int MinorCutoff { get; set; }
public bool BayerFilter { get; set; }
public bool LaserTrigger { get; set; }
public int LaserTriggerDelay { get; set; }
public bool FlipLines { get; set; }
public bool MirrorX { get; set; }
public bool TriggerLights { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Net;
using System.Net.NetworkInformation;
namespace Inspectron.HawkEye.Protocol.Discovery
{
public class CameraInfo
{
public string Mac { get; set; }
public IPAddress Address { get; set; }
public IPAddress AdapterAddress { get; set; }
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Inspectron.HawkEye.Protocol.Discovery
{
public class DiscoveryClient
{
public ReadOnlyCollection<CameraInfo> Discovered => new ReadOnlyCollection<CameraInfo>(_discovered);
private readonly int _port;
private readonly List<CameraInfo> _discovered = new List<CameraInfo>();
private readonly object _discoveryLock = new object();
public DiscoveryClient(int port)
{
_port = port;
}
public event Action<CameraInfo> CameraFound = delegate { };
public void Discover()
{
var allInterfaces = NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up);
Parallel.ForEach(allInterfaces, DiscoverOnInterface);
//foreach (NetworkInterface i in allInterfaces)
//{
// DiscoverOnInterface(i);
//}
}
private void DiscoverOnInterface(NetworkInterface iface)
{
var address = iface.GetIPProperties().UnicastAddresses
.First(x => x.Address.AddressFamily == AddressFamily.InterNetwork).Address;
UdpClient client;
lock (_discoveryLock)
{
client = new UdpClient(new IPEndPoint(address, 0));
var requestData = Encoding.ASCII.GetBytes("discovery");
client.Client.ReceiveTimeout = 2000;
var s = client.Client;
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
client.EnableBroadcast = true;
client.Send(requestData, requestData.Length, new IPEndPoint(IPAddress.Broadcast, _port));
client.Send(requestData, requestData.Length, new IPEndPoint(IPAddress.Broadcast, _port));
client.Send(requestData, requestData.Length, new IPEndPoint(IPAddress.Broadcast, _port));
}
var serverEp = new IPEndPoint(IPAddress.Any, 0);
byte[] serverResponseData;
try
{
serverResponseData = client.Receive(ref serverEp);
var serverResponse = Encoding.ASCII.GetString(serverResponseData);
Console.WriteLine("Recived {0} from {1}", serverResponse, serverEp.Address);
var found = new CameraInfo {Mac = serverResponse, Address = serverEp.Address,AdapterAddress = address};
lock (_discoveryLock)
{
if (_discovered.Any(x => x.Mac == found.Mac)) return;
}
CameraFound(found);
_discovered.Add(found);
}
catch
{
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Inspectron.HawkEye.Protocol.Discovery
{
public class DiscoveryServer
{
private UdpClient _server;
private byte[] _name;
public DiscoveryServer(int port,string name=null)
{
_server = new UdpClient(port);
if(name==null)
{ _name = Encoding.UTF8.GetBytes( NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.Select(nic => nic.GetPhysicalAddress().ToString())
.FirstOrDefault());
}
else
{
_name = Encoding.UTF8.GetBytes(name);
}
}
public void Start()
{
Thread th = new Thread(DiscoveryLoop);
th.Start();
}
private void DiscoveryLoop()
{
while (true)
{
var clientEp = new IPEndPoint(IPAddress.Any, 0);
var clientRequestData = _server.Receive(ref clientEp);
var clientRequest = Encoding.ASCII.GetString(clientRequestData);
Console.WriteLine("Recived {0} from {1}, sending response", clientRequest, clientEp.Address.ToString());
_server.Send(_name, _name.Length, clientEp);
_server.Send(_name, _name.Length, clientEp);
_server.Send(_name, _name.Length, clientEp);
}
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Inspectron.HawkEye.Protocol
{
public enum ECommand
{
Connect,
Trigger, StartContinuous, StopContinuous,
Settings,SaveSettings,
OK,
SaveCalibration,
GetCalibration,
NotOK
}
}

View File

@@ -0,0 +1,9 @@
namespace Inspectron.HawkEye.Protocol
{
public enum EData
{
Image,
OK
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Net;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageClient : IDisposable
{
private static readonly object connectionLock = new object();
private readonly IPEndPoint _endpoint;
private readonly IPAddress _adapter;
private readonly UDPBSocket _imageSocket = new UDPBSocket();
private readonly UDPBSocket _commandSocket = new UDPBSocket();
private bool _isConnected = true;
public ImageClient(IPEndPoint endpoint,IPAddress adapter)
{
_endpoint = endpoint;
_adapter = adapter;
}
public void Dispose()
{
_isConnected = false;
_commandSocket.Dispose();
}
public void Connect()
{
lock (connectionLock)
{
_commandSocket.Connect(_endpoint,_adapter);
var port = UDPBSocket.FindFreePort(_adapter);
var bytesPort = BitConverter.GetBytes(port);
_commandSocket.SendData(new[]
{(byte) ECommand.Connect, bytesPort[0], bytesPort[1], bytesPort[2], bytesPort[3]});
var settingsData0 = _commandSocket.Receive();
var b = new byte[settingsData0.Length - 1];
Array.Copy(settingsData0, 1, b, 0, b.Length);
var settingsString = Encoding.UTF8.GetString(b);
SettingsReceived(JsonConvert.DeserializeObject<CameraSettings>(settingsString));
_imageSocket.Listen(_adapter,port);
var th = new Thread(ReceiveLoop);
th.Start();
}
}
public event Action<byte[]> ImageReceived = delegate { };
public event Action<CameraSettings> SettingsReceived = delegate { };
public void Trigger()
{
_commandSocket.SendData(new[] {(byte) ECommand.Trigger});
}
public void ApplySettings(CameraSettings cameraSettings)
{
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cameraSettings));
var databytes = new byte[1000];
databytes[0] = (byte) ECommand.Settings;
Array.Copy(data, 0, databytes, 1, data.Length);
_commandSocket.SendData(databytes);
}
public void StartContinuous()
{
_commandSocket.SendData(new[] {(byte) ECommand.StartContinuous});
}
public void StopContinuous()
{
_commandSocket.SendData(new[] {(byte) ECommand.StopContinuous});
}
public void SaveSettingsOnCamera()
{
_commandSocket.SendData(new[] {(byte) ECommand.SaveSettings});
}
private void ReceiveLoop()
{
while (_isConnected)
{
var data = _imageSocket.Receive();
Process(data);
}
}
private void Process(byte[] data)
{
switch ((EData) data[0])
{
case EData.Image:
var b = new byte[data.Length - 1];
Array.Copy(data, 1, b, 0, b.Length);
ImageReceived(data);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageClientTCP : IDisposable
{
private static readonly object connectionLock = new object();
private readonly IPEndPoint _endpoint;
private readonly IPAddress _adapter;
private TcpListener _imageSocket;
private TcpClient _commandSocket;
private bool _isConnected = true;
public ImageClientTCP(IPEndPoint endpoint,IPAddress adapter)
{
_endpoint = endpoint;
_adapter = adapter;
}
public void Dispose()
{
_isConnected = false;
_commandSocket.Dispose();
}
byte[] _commandBuffer = new byte[1500];
private TcpClient _lastClient;
public bool SupportsCalibration { get; set; }
public void Connect()
{
lock (connectionLock)
{
_commandSocket=new TcpClient(new IPEndPoint(_adapter, 0));
Console.WriteLine($"Bind on {_adapter?.ToString()}");
_commandSocket.Connect(_endpoint);
var port = UDPBSocket.FindFreePort(_adapter);
Console.WriteLine("Connected");
_imageSocket = new TcpListener(_adapter, port);
_imageSocket.Start();
Console.WriteLine("TCP started");
var bytesPort = BitConverter.GetBytes(port);
_commandSocket.Client.SendData(new[]
{(byte) ECommand.Connect, bytesPort[0], bytesPort[1], bytesPort[2], bytesPort[3]});
var receivedLen = _commandSocket.Client.Receive(_commandBuffer);
Console.WriteLine("received answer length:"+receivedLen);
var b = new byte[receivedLen - 1];
Array.Copy(_commandBuffer, 1, b, 0, b.Length);
var settingsString = Encoding.UTF8.GetString(b);
SettingsReceived(JsonConvert.DeserializeObject<CameraSettings>(settingsString));
if (SupportsCalibration)
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.GetCalibration});
receivedLen = _commandSocket.Client.Receive(_commandBuffer);
if (_commandBuffer[0] == (byte) ECommand.OK)
{
Console.WriteLine($"calibration received {receivedLen} bytes");
var c = new byte[receivedLen - 1];
Array.Copy(_commandBuffer, 1, c, 0, c.Length);
var calibrationString = Encoding.UTF8.GetString(c);
CalibrationReceived(JsonConvert.DeserializeObject<List<Dictionary<double,double>>>(calibrationString));
}
else
{
Console.WriteLine("no calibration received");
}
}
var th = new Thread(ReceiveLoop);
th.Start();
}
}
public event Action<byte[]> ImageReceived = delegate { };
public event Action<CameraSettings> SettingsReceived = delegate { };
public event Action<List<Dictionary<double,double>>> CalibrationReceived = delegate { };
public void Trigger()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.Trigger});
}
public void ApplySettings(CameraSettings cameraSettings)
{
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cameraSettings));
var databytes = new byte[1000];
databytes[0] = (byte) ECommand.Settings;
Array.Copy(data, 0, databytes, 1, data.Length);
_commandSocket.Client.SendData(databytes);
byte[] ok = new byte[1];
_commandSocket.Client.Receive(ok);
}
public void StartContinuous()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.StartContinuous});
}
public void StopContinuous()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.StopContinuous});
}
public void SaveSettingsOnCamera()
{
_commandSocket.Client.SendData(new[] {(byte) ECommand.SaveSettings});
}
private byte[] _imageBuffer = new byte[10*1024*1024];
private void ReceiveLoop()
{
while (_isConnected)
{
_lastClient = _imageSocket.AcceptTcpClient();
while (true)
{
int received=0;
try
{
_lastClient.Client.Receive(_imageBuffer, 0, 1,
SocketFlags.None);
Process(_imageBuffer);
}
catch
{
break;
}
}
}
}
public void SaveCalibration(List<Dictionary<double,double>> calibration)
{
var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(calibration));
var databytes = new byte[1500];
databytes[0] = (byte)ECommand.SaveCalibration;
Array.Copy(data, 0, databytes, 1, data.Length);
_commandSocket.Client.SendData(databytes);
byte[] ok = new byte[1];
_commandSocket.Client.Receive(ok);
}
private void Process(byte[] data)
{
switch ((EData) data[0])
{
case EData.Image:
_lastClient.Client.Receive(_imageBuffer, 1, 4,
SocketFlags.None);
var imageSize = BitConverter.ToInt32(_imageBuffer,1);
var received = 0;
do
{
received += _lastClient.Client.Receive(_imageBuffer, 5+ received, imageSize- received,
SocketFlags.None);
} while (received < imageSize);
var b = new byte[imageSize];
Array.Copy(data, 5, b, 0, b.Length);
ImageReceived(b);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -0,0 +1,164 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Inspectron.HawkEye.Protocol.Interfaces;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageServer
{
private readonly IImageSource _imageSource;
private readonly ICameraControl _cameraControl;
private readonly ILightControl _lightControl;
private readonly UDPBSocket _commandSocket = new UDPBSocket();
private readonly UDPBSocket _imageSocket = new UDPBSocket();
private bool _autoTrigger;
private bool _applySettings;
private CameraSettings _imageSettingsToApply=new CameraSettings(){ImageSettings = new ImageSettings()};
public ImageServer(IImageSource imageSource, ICameraControl cameraControl, ILightControl lightControl)
{
_imageSource = imageSource;
_cameraControl = cameraControl;
_lightControl = lightControl;
_imageSocket.LossSimulation = 0;
}
public void Start()
{
_commandSocket.Listen(IPAddress.Any, 27001);
var th = new Thread(ReceiveLoop);
th.Start();
if (File.Exists("settings.json"))
{
var settings = JsonConvert.DeserializeObject<CameraSettings>(File.ReadAllText("settings.json"));
_imageSettingsToApply = settings;
ApplyParameters(settings);
}
}
public void ApplyParameters(CameraSettings cameraSettings)
{
_cameraControl.SetParameters(cameraSettings);
_lightControl.SetLight(cameraSettings.LightPwm1, cameraSettings.LightPwm2);
}
private void SaveSettingsLocally()
{
File.WriteAllText("settings.json", JsonConvert.SerializeObject(_imageSettingsToApply));
}
private void ReceiveLoop()
{
while (true)
{
var data = _commandSocket.Receive();
try
{
ProcessCommand(data);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
private void TriggerLoop()
{
while (_autoTrigger)
{
if (_applySettings)
{
ApplyParameters(_imageSettingsToApply);
_applySettings = false;
}
try
{
SendImage();
}
catch
{
return;
}
}
}
private void ProcessCommand(byte[] data)
{
Console.WriteLine(((ECommand) data[0]).ToString());
switch ((ECommand) data[0])
{
case ECommand.Connect:
{
var port = new byte[4];
Array.Copy(data, 1, port, 0, 4);
_imageSocket.Connect(new IPEndPoint((_commandSocket.LastConnection as IPEndPoint).Address, BitConverter.ToInt32(port,0)),null);
var databytes = new byte[1000];
var settings = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_imageSettingsToApply));
Array.Copy(settings, 0, databytes, 1, settings.Length);
databytes[0] = (byte) ECommand.Settings;
_commandSocket.SendData(databytes);
}
break;
case ECommand.Trigger:
{
Task.Run(() => { SendImage(); });
}
break;
case ECommand.StartContinuous:
{
_autoTrigger = true;
var th = new Thread(TriggerLoop);
th.Start();
}
break;
case ECommand.StopContinuous:
{
_autoTrigger = false;
}
break;
case ECommand.Settings:
{
var databytes = new byte[999];
Array.Copy(data, 1, databytes, 0, 999);
var settings = JsonConvert.DeserializeObject<CameraSettings>(Encoding.UTF8.GetString(databytes));
_imageSettingsToApply = settings;
if (_autoTrigger)
_applySettings = true;
else
ApplyParameters(_imageSettingsToApply);
}
break;
case ECommand.SaveSettings:
{
SaveSettingsLocally();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void SendImage()
{
var image = _imageSource.GetImage();
var b = new byte[image.Length + 1];
b[0] = (byte) EData.Image;
Array.Copy(image, 0, b, 1, image.Length);
_imageSocket.SendData(b);
}
}
}

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Inspectron.HawkEye.Protocol.Interfaces;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;
namespace Inspectron.HawkEye.Protocol
{
public class ImageServerTCP
{
private readonly IImageSource _imageSource;
private readonly ICameraControl _cameraControl;
private readonly ILightControl _lightControl;
private TcpListener _commandSocket;
private TcpClient _imageSocket;
private bool _autoTrigger;
private bool _applySettings;
private CameraSettings _imageSettingsToApply=new CameraSettings(){ImageSettings = new ImageSettings()};
private TcpClient _lastClient;
public ImageServerTCP(IImageSource imageSource, ICameraControl cameraControl, ILightControl lightControl)
{
_imageSource = imageSource;
_cameraControl = cameraControl;
_lightControl = lightControl;
}
public void Start()
{
_commandSocket = new TcpListener(IPAddress.Any, 27001);
_commandSocket.Start();
Console.WriteLine("listen tcp");
var th = new Thread(ReceiveLoop);
th.Start();
if (File.Exists("settings.json"))
{
var settings = JsonConvert.DeserializeObject<CameraSettings>(File.ReadAllText("settings.json"));
_imageSettingsToApply = settings;
ApplyParameters(settings);
}
}
public void ApplyParameters(CameraSettings cameraSettings, bool setLight = false)
{
Console.WriteLine("Setting parameters:"+JsonConvert.SerializeObject(cameraSettings,Formatting.Indented));
_cameraControl.SetParameters(cameraSettings);
if(setLight&&!cameraSettings.TriggerLights) _lightControl.SetLight(cameraSettings.LightPwm1, cameraSettings.LightPwm2);
else _lightControl.SetLight(0, 0);
}
private void SaveSettingsLocally()
{
File.WriteAllText("settings.json", JsonConvert.SerializeObject(_imageSettingsToApply,Formatting.Indented));
}
byte[] _commandBuffer = new byte[1500];
private Task _lastTriggerTask=Task.CompletedTask;
private void ReceiveLoop()
{
while (true)
{
_lastClient=_commandSocket.AcceptTcpClient();
do
{
try
{
_lastClient.GetStream().Read(_commandBuffer, 0, 1500);
}
catch
{
break;
}
ProcessCommand(_commandBuffer);
} while (_lastClient.Connected);
CancelTrigger();
_lightControl.SetLight(0, 0);
}
}
private void TriggerLoop()
{
while (_autoTrigger)
{
if (_applySettings)
{
ApplyParameters(_imageSettingsToApply,true);
_applySettings = false;
}
try
{
SendImage();
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
return;
}
}
}
private void ProcessCommand(byte[] data)
{
Console.WriteLine(((ECommand) data[0]).ToString());
switch ((ECommand) data[0])
{
case ECommand.Connect:
{
var port = new byte[4];
Array.Copy(data, 1, port, 0, 4);
_imageSocket=new TcpClient();
_imageSocket.Connect(new IPEndPoint((_lastClient.Client.RemoteEndPoint as IPEndPoint).Address, BitConverter.ToInt32(port,0)));
var databytes = new byte[1000];
var settings = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_imageSettingsToApply));
Array.Copy(settings, 0, databytes, 1, settings.Length);
databytes[0] = (byte) ECommand.Settings;
_lastClient.Client.SendData(databytes);
Console.WriteLine("data sent");
_lightControl.SetLight(_imageSettingsToApply.LightPwm1, _imageSettingsToApply.LightPwm2);
_autoTrigger = false;
}
break;
case ECommand.GetCalibration:
{
var databytes = new byte[1500];
if (File.Exists("calibration.calib"))
{
databytes[0] = (byte)ECommand.OK;
Array.Copy(File.ReadAllBytes("calibration.calib"),0,databytes,1,1500-1);
_lastClient.Client.SendData(databytes);
}
else
{
databytes[0] = (byte)ECommand.NotOK;
_lastClient.Client.SendData(databytes);
}
}
break;
case ECommand.Trigger:
{
CancelTrigger();
_lastTriggerTask=Task.Run(() =>
{
try
{
SendImage();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
});
}
break;
case ECommand.StartContinuous:
{
_autoTrigger = true;
var th = new Thread(TriggerLoop);
th.Start();
}
break;
case ECommand.StopContinuous:
{
_autoTrigger = false;
}
break;
case ECommand.Settings:
{
CancelTrigger();
var databytes = new byte[999];
Array.Copy(data, 1, databytes, 0, 999);
var settings = JsonConvert.DeserializeObject<CameraSettings>(Encoding.UTF8.GetString(databytes));
_imageSettingsToApply = settings;
if (_autoTrigger)
_applySettings = true;
else
ApplyParameters(_imageSettingsToApply,true);
_lastClient.Client.Send(new []{(byte)EData.OK});
}
break;
case ECommand.SaveCalibration:
{
var databytes = new byte[1500 - 1];
Array.Copy(data, 1, databytes, 0, 1500 - 1);
File.WriteAllBytes("calibration.calib",databytes);
_lastClient.Client.Send(new[] { (byte)EData.OK });
}
break;
case ECommand.SaveSettings:
{
SaveSettingsLocally();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void CancelTrigger()
{
_imageSource.CancelTrigger();
_lastTriggerTask.Wait();
}
private void SendImage()
{
if (_imageSettingsToApply.TriggerLights)
{
_lightControl.SetLight(_imageSettingsToApply.LightPwm1, _imageSettingsToApply.LightPwm2);
}
var image = _imageSource.GetImage();
if (image.Length == 0) return;
if (_imageSettingsToApply.TriggerLights)
{
_lightControl.SetLight(0, 0);
}
var b = EncodeImage(image);
_imageSocket.Client.SendData(b);
}
private static byte[] EncodeImage(byte[] image)
{
var b = new byte[image.Length + 5];
b[0] = (byte) EData.Image;
var byteSize = BitConverter.GetBytes(image.Length);
Array.Copy(byteSize, 0, b, 1, 4);
Array.Copy(image, 0, b, 5, image.Length);
return b;
}
private static byte[] EncodeChanneledImage(byte[] image,byte channels)
{
var b = new byte[image.Length + 6];
b[0] = (byte)EData.Image;
b[1] = channels;
var byteSize = BitConverter.GetBytes(image.Length);
Array.Copy(byteSize, 0, b, 2, 4);
Array.Copy(image, 0, b, 6, image.Length);
return b;
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace Inspectron.HawkEye.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct ImageSettings
{
public int Shutter { get; set; }
public int Gain { get; set; }
public int SensorWidth { get; set; }
public int Lines { get; set; }
public int CaptureBuffer { get; set; }
public int UseExternalTrigger { get; set; }
public int Divider { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.HawkEye.Protocol.Interfaces
{
public interface ICameraControl
{
void SetParameters(CameraSettings imageSettings);
}
}

View File

@@ -0,0 +1,8 @@
namespace Inspectron.HawkEye.Protocol.Interfaces
{
public interface IImageSource
{
byte[] GetImage();
void CancelTrigger();
}
}

View File

@@ -0,0 +1,7 @@
namespace Inspectron.HawkEye.Protocol.Interfaces
{
public interface ILightControl
{
void SetLight(int pwm1, int pwm2);
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Net;
using System.Net.Sockets;
using Inspectron.HawkEye.RTSP;
namespace Inspectron.HawkEye.Protocol
{
public static class SocketExtensions
{
public static void Listen(this Socket self,IPAddress adapterAddress,int port)
{
self.Bind(new IPEndPoint(adapterAddress,port));
}
public static void SendData(this Socket self, byte[] data)
{
Console.WriteLine("send data");
self.Send(data);
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the AAC-hbd (High Bitrate) Payload
// It has methods to process the RTP Payload
// (c) 2018 Roger Hardiman, RJH Technical Consultancy Ltd
/*
RFC 3640
3.3.6. High Bit-rate AAC
This mode is signaled by mode=AAC-hbr.This mode supports the
transportation of variable size AAC frames.In one RTP packet,
either one or more complete AAC frames are carried, or a single
fragment of an AAC frame is carried.In this mode, the AAC frames
are allowed to be interleaved and hence receivers MUST support de-
interleaving.The maximum size of an AAC frame in this mode is 8191
octets.
In this mode, the RTP payload consists of the AU Header Section,
followed by either one AAC frame, several concatenated AAC frames or
one fragmented AAC frame.The Auxiliary Section MUST be empty. For
each AAC frame contained in the payload, there MUST be an AU-header
in the AU Header Section to provide:
a) the size of each AAC frame in the payload and
b) index information for computing the sequence(and hence timing) of
each AAC frame.
To code the maximum size of an AAC frame requires 13 bits.
Therefore, in this configuration 13 bits are allocated to the AU-
size, and 3 bits to the AU-Index(-delta) field.Thus, each AU-header
has a size of 2 octets.Each AU-Index field MUST be coded with the
value 0. In the AU Header Section, the concatenated AU-headers MUST
be preceded by the 16-bit AU-headers-length field, as specified in
section 3.2.1.
In addition to the required MIME format parameters, the following
parameters MUST be present: sizeLength, indexLength, and
indexDeltaLength.AAC frames always have a fixed duration per Access
Unit; when interleaving in this mode, this specific duration MUST be
signaled by the MIME format parameter constantDuration.In addition,
the parameter maxDisplacement MUST be present when interleaving.
For example:
m= audio 49230 RTP/AVP 96
a= rtpmap:96 mpeg4-generic/48000/6
a= fmtp:96 streamtype= 5; profile-level-id= 16; mode= AAC-hbr;config= 11B0; sizeLength= 13; indexLength= 3;indexDeltaLength= 3; constantDuration= 1024
The hexadecimal value of the "config" parameter is the AudioSpecificConfig(), as defined in ISO/IEC 14496-3.
AudioSpecificConfig() specifies a 5.1 channel AAC stream with a sampling rate of 48 kHz.For the description of MIME parameters, see
section 4.1.
*/
public class AACPayload
{
public uint ObjectType = 0;
public uint FrequencyIndex = 0;
public uint ChannelConfiguration = 0;
// Constructor
public AACPayload(String config_string)
{
/***
5 bits: object type
if (object type == 31)
6 bits + 32: object type
4 bits: frequency index
if (frequency index == 15)
24 bits: frequency
4 bits: channel configuration
var bits: AOT Specific Config
***/
// config is a string in hex eg 1490 or 0x1210
// Read each ASCII character and add to a bit array
BitStream bs = new BitStream();
bs.AddHexString(config_string);
// Read 5 bits
ObjectType = bs.Read(5);
// Read 4 bits
FrequencyIndex = bs.Read(4);
// Read 4 bits
ChannelConfiguration = bs.Read(4);
}
public List<byte[]> Process_AAC_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// RTP Payload for MPEG4-GENERIC can consist of multple blocks.
// Each block has 3 parts
// Part 1 - Acesss Unit Header Length + Header
// Part 2 - Access Unit Auxiliary Data Length + Data (not used in AAC High Bitrate)
// Part 3 - Access Unit Audio Data
// The rest of the RTP packet is the AMR data
List<byte[]> audio_data = new List<byte[]>();
int ptr = 0;
while (true) {
if (ptr + 4 > rtp_payload.Length) break; // 2 bytes for AU Header Length, 2 bytes of AU Header payload
// Get Size of the AU Header
int au_headers_length_bits = (((rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0))); // 16 bits
int au_headers_length = (int)Math.Ceiling((double)au_headers_length_bits / 8.0);
ptr += 2;
// Examine the AU Header. Get the size of the AAC data
int aac_frame_size = (((rtp_payload[ptr] << 8) + (rtp_payload[ptr+1] << 0)) >> 3); // 13 bits
int aac_index_delta = rtp_payload[ptr+1] & 0x03; // 3 bits
ptr += au_headers_length;
// extract the AAC block
if (ptr + aac_frame_size > rtp_payload.Length) break; // not enough data to copy
byte[] aac_data = new byte[aac_frame_size];
System.Array.Copy(rtp_payload, ptr, aac_data, 0, aac_frame_size);
audio_data.Add(aac_data);
ptr += aac_frame_size;
}
return audio_data;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the AMR Payload
// It has methods to process the RTP Payload
public class AMRPayload
{
// Constructor
public AMRPayload()
{
}
public List<byte[]> Process_AMR_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Octet-Aligned Mode (RFC 4867 Section 4.4.1)
// First byte is the Payload Header
if (rtp_payload.Length < 1) return null;
byte payloadHeader = rtp_payload[0];
// The rest of the RTP packet is the AMR data
List<byte[]> audio_data = new List<byte[]>();
byte[] amr_data = new byte[rtp_payload.Length - 1];
System.Array.Copy(rtp_payload,1,amr_data,0,rtp_payload.Length-1);
audio_data.Add(amr_data);
return audio_data;
}
}
}

View File

@@ -0,0 +1,183 @@
using System;
using System.Security.Cryptography;
using System.Text;
using Inspectron.HawkEye.RTSP.Messages;
namespace Inspectron.HawkEye.RTSP
{
// WWW-Authentication and Authorization Headers
public class Authentication
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
public enum Type {Basic, Digest};
private String username = null;
private String password = null;
private String realm = null;
private String nonce = null;
private Type authentication_type = Type.Digest;
private readonly MD5 md5 = System.Security.Cryptography.MD5.Create();
private const char quote = '\"';
// Constructor
public Authentication(String username, String password, String realm, Type authentication_type) {
this.username = username;
this.password = password;
this.realm = realm;
this.authentication_type = authentication_type;
this.nonce = new Random().Next(100000000,999999999).ToString(); // random 9 digit number
}
public String GetHeader() {
if (authentication_type == Type.Basic) {
return "Basic realm=" + quote + realm + quote;
}
if (authentication_type == Type.Digest) {
return "Digest realm=" + quote + realm + quote + ", nonce=" + quote + nonce + quote;
}
return null;
}
public bool IsValid(RtspMessage received_message) {
string authorization = received_message.Headers["Authorization"];
// Check Username and Password
if (authentication_type == Type.Basic && authorization.StartsWith("Basic ")) {
string base64_str = authorization.Substring(6); // remove 'Basic '
byte[] data = Convert.FromBase64String(base64_str);
string decoded = Encoding.UTF8.GetString(data);
int split_position = decoded.IndexOf(':');
string decoded_username = decoded.Substring(0, split_position);
string decoded_password = decoded.Substring(split_position + 1);
if ((decoded_username == username) && (decoded_password == password)) {
_logger.Debug("Basic Authorization passed");
return true;
} else {
_logger.Debug("Basic Authorization failed");
return false;
}
}
// Check Username, URI, Nonce and the MD5 hashed Response
if (authentication_type == Type.Digest && authorization.StartsWith("Digest ")) {
string value_str = authorization.Substring(7); // remove 'Digest '
string[] values = value_str.Split(',');
string auth_header_username = null;
string auth_header_realm = null;
string auth_header_nonce = null;
string auth_header_uri = null;
string auth_header_response = null;
string message_method = null;
string message_uri = null;
try {
message_method = received_message.Command.Split(' ')[0];
message_uri = received_message.Command.Split(' ')[1];
} catch {}
foreach (string value in values) {
string[] tuple = value.Trim().Split(new char[] {'='},2); // split on first '='
if (tuple.Length == 2 && tuple[0].Equals("username")) {
auth_header_username = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("realm")) {
auth_header_realm = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("nonce")) {
auth_header_nonce = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("uri")) {
auth_header_uri = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
else if (tuple.Length == 2 && tuple[0].Equals("response")) {
auth_header_response = tuple[1].Trim(new char[] {' ','\"'}); // trim space and quotes
}
}
// Create the MD5 Hash using all parameters passed in the Auth Header with the
// addition of the 'Password'
String hashA1 = CalculateMD5Hash(md5, auth_header_username+":"+auth_header_realm+":"+this.password);
String hashA2 = CalculateMD5Hash(md5, message_method + ":" + auth_header_uri);
String expected_response = CalculateMD5Hash(md5, hashA1 + ":" + auth_header_nonce + ":" + hashA2);
// Check if everything matches
// ToDo - extract paths from the URIs (ignoring SETUP's trackID)
if ((auth_header_username == this.username)
&& (auth_header_realm == this.realm)
&& (auth_header_nonce == this.nonce)
&& (auth_header_response == expected_response)
){
_logger.Debug("Digest Authorization passed");
return true;
} else {
_logger.Debug("Digest Authorization failed");
return false;
}
}
return false;
}
// Generate Basic or Digest Authorization
public string GenerateAuthorization(string username, string password,
string auth_type, string realm, string nonce, string url, string command) {
if (username == null || username.Length == 0) return null;
if (password == null || password.Length == 0) return null;
if (realm == null || realm.Length == 0) return null;
if (auth_type.Equals("Digest") && (nonce == null || nonce.Length == 0)) return null;
if (auth_type.Equals("Basic")) {
byte[] credentials = System.Text.Encoding.UTF8.GetBytes(username+":"+password);
String credentials_base64 = Convert.ToBase64String(credentials);
String basic_authorization = "Basic " + credentials_base64;
return basic_authorization;
}
else if (auth_type.Equals("Digest")) {
MD5 md5 = System.Security.Cryptography.MD5.Create();
String hashA1 = CalculateMD5Hash(md5, username+":"+realm+":"+password);
String hashA2 = CalculateMD5Hash(md5, command + ":" + url);
String response = CalculateMD5Hash(md5, hashA1 + ":" + nonce + ":" + hashA2);
const String quote = "\"";
String digest_authorization = "Digest username=" + quote + username + quote +", "
+ "realm=" + quote + realm + quote + ", "
+ "nonce=" + quote + nonce + quote + ", "
+ "uri=" + quote + url + quote + ", "
+ "response=" + quote + response + quote;
return digest_authorization;
}
else {
return null;
}
}
// MD5 (lower case)
private string CalculateMD5Hash(MD5 md5_session, string input)
{
byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
byte[] hash = md5_session.ComputeHash(inputBytes);
StringBuilder output = new StringBuilder();
for (int i = 0; i < hash.Length; i++) {
output.Append(hash[i].ToString("x2"));
}
return output.ToString();
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
// (c) 2018 Roger Hardiman, RJH Technical Consultancy Ltd
// Simple class to Read and Write bits in a bit stream.
// Data is written to the end of the bit stream and the bit stream can be returned as a Byte Array
// Data can be read from the head of the bit stream
// Example
// bitstream.AddValue(0xA,4); // Write 4 bit value
// bitstream.AddValue(0xB,4);
// bitstream.AddValue(0xC,4);
// bitstream.AddValue(0xD,4);
// bitstream.ToArray() -> {0xAB, 0xCD} // Return Byte Array
// bitstream.Read(8) -> 0xAB // Read 8 bit value
namespace Inspectron.HawkEye.RTSP
{
// Very simple bitstream
public class BitStream {
private List <byte> data = new List<byte>(); // List only stores 0 or 1 (one 'bit' per List item)
// Constructor
public BitStream() {
}
public void AddValue(int value, int num_bits) {
// Add each bit to the List
for (int i = num_bits-1; i >= 0; i--) {
data.Add((byte)((value>>i) & 0x01));
}
}
public void AddHexString(String hex_string) {
char[] hex_chars = hex_string.ToUpper().ToCharArray();
foreach (char c in hex_chars) {
if ((c.Equals('0'))) this.AddValue(0,4);
else if ((c.Equals('1'))) this.AddValue(1, 4);
else if ((c.Equals('2'))) this.AddValue(2, 4);
else if ((c.Equals('3'))) this.AddValue(3, 4);
else if ((c.Equals('4'))) this.AddValue(4, 4);
else if ((c.Equals('5'))) this.AddValue(5, 4);
else if ((c.Equals('6'))) this.AddValue(6, 4);
else if ((c.Equals('7'))) this.AddValue(7, 4);
else if ((c.Equals('8'))) this.AddValue(8, 4);
else if ((c.Equals('9'))) this.AddValue(9, 4);
else if ((c.Equals('A'))) this.AddValue(10, 4);
else if ((c.Equals('B'))) this.AddValue(11, 4);
else if ((c.Equals('C'))) this.AddValue(12, 4);
else if ((c.Equals('D'))) this.AddValue(13, 4);
else if ((c.Equals('E'))) this.AddValue(14, 4);
else if ((c.Equals('F'))) this.AddValue(15, 4);
}
}
public uint Read(int num_bits) {
// Read and remove items from the front of the list of bits
if (data.Count < num_bits) return 0;
uint result = 0;
for (int i = 0; i < num_bits; i++) {
result = result << 1;
result = result + data[0];
data.RemoveAt(0);
}
return result;
}
public byte[] ToArray() {
int num_bytes = (int)Math.Ceiling((double)data.Count/8.0);
byte[] array = new byte[num_bytes];
int ptr = 0;
int shift = 7;
for (int i = 0; i < data.Count; i++) {
array[ptr] += (byte)(data[i] << shift);
if (shift == 0) {
shift = 7;
ptr++;
}
else {
shift--;
}
}
return array;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
using System.Collections.Generic;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the G711 Payload
// It has methods to process the RTP Payload
public class G711Payload
{
// Constructor
public G711Payload()
{
}
public List<byte[]> Process_G711_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
List<byte[]> audio_data = new List<byte[]>();
audio_data.Add(rtp_payload);
return audio_data;
}
/* Untested - used with G711.1 and PCMA-WB and PCMU-WB Codec Names */
public List<byte[]> Process_G711_1_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Look at the Header. This tells us the G711 mode being used
// Mode Index (MI) is
// 1 - R1 40 octets containg Layer 0 data
// 2 - R2a 50 octets containing Layer 0 plus Layer 1 data
// 3 - R2b 50 octets containing Layer 0 plus Layer 2 data
// 4 - R3 60 octets containing Layer 0 plus Layer 1 plus Layer 2 data
byte mode_index = (byte)(rtp_payload[0] & 0x07);
int size_of_one_frame = 0; // will be in bytes
switch (mode_index) {
case 1: size_of_one_frame = 40; break;
case 2: size_of_one_frame = 50; break;
case 3: size_of_one_frame = 50; break;
case 4: size_of_one_frame = 60; break;
default: return null; // invalid Mode Index
}
int number_frames = (rtp_payload.Length - 1) / size_of_one_frame;
// Return just the basic u-Law or A-Law audio (the Layer 0 audio)
List<byte[]> audio_data = new List<byte[]>();
// Extract each audio frame and place in the audio_data List
int frame_start = 1; // starts just after the MI header
while (frame_start + size_of_one_frame < rtp_payload.Length) {
byte[] layer_0_audio = new byte[40];
System.Array.Copy(rtp_payload,frame_start,layer_0_audio,0,40); // 40 octets in Layer 0 data
audio_data.Add(layer_0_audio);
frame_start += size_of_one_frame;
}
return audio_data;
}
}
}

View File

@@ -0,0 +1,184 @@
using System.Collections.Generic;
using System.IO;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the H264 Payload
// It has methods to parse parameters in the SDP
// It has methods to process the RTP Payload
public class H264Payload
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
int norm, fu_a, fu_b, stap_a, stap_b, mtap16, mtap24 = 0; // used for diagnostics stats
List<byte[]> temporary_rtp_payloads = new List<byte[]>(); // used to assemble the RTP packets that form one RTP Frame
// Eg all the RTP Packets from M=0 through to M=1
MemoryStream fragmented_nal = new MemoryStream(); // used to concatenate fragmented H264 NALs where NALs are split over RTP packets
// Constructor
public H264Payload()
{
}
public List<byte[]> Process_H264_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Add to the list of payloads for the current Frame of video
temporary_rtp_payloads.Add(rtp_payload); // Todo Could optimise this and go direct to Process Frame if just 1 packet in frame
if (rtp_marker == 1)
{
// End Marker is set. Process the list of RTP Packets (forming 1 RTP frame) and save the NALs to a file
List<byte[]> nal_units = Process_H264_RTP_Frame(temporary_rtp_payloads);
temporary_rtp_payloads.Clear();
return nal_units;
}
return null; // we don't have a frame yet. Keep accumulating RTP packets
}
// Process a RTP Frame. A RTP Frame can consist of several RTP Packets which have the same Timestamp
// Returns a list of NAL Units (with no 00 00 00 01 header and with no Size header)
private List<byte[]> Process_H264_RTP_Frame(List<byte[]> rtp_payloads)
{
_logger.Debug("RTP Data comprised of " + rtp_payloads.Count + " rtp packets");
List<byte[]> nal_units = new List<byte[]>(); // Stores the NAL units for a Video Frame. May be more than one NAL unit in a video frame.
for (int payload_index = 0; payload_index < rtp_payloads.Count; payload_index++)
{
// Examine the first rtp_payload and the first byte (the NAL header)
int nal_header_f_bit = (rtp_payloads[payload_index][0] >> 7) & 0x01;
int nal_header_nri = (rtp_payloads[payload_index][0] >> 5) & 0x03;
int nal_header_type = (rtp_payloads[payload_index][0] >> 0) & 0x1F;
// If the Nal Header Type is in the range 1..23 this is a normal NAL (not fragmented)
// So write the NAL to the file
if (nal_header_type >= 1 && nal_header_type <= 23)
{
_logger.Debug("Normal NAL");
norm++;
nal_units.Add(rtp_payloads[payload_index]);
}
// There are 4 types of Aggregation Packet (split over RTP payloads)
else if (nal_header_type == 24)
{
_logger.Debug("Agg STAP-A");
stap_a++;
// RTP packet contains multiple NALs, each with a 16 bit header
// Read 16 byte size
// Read NAL
try
{
int ptr = 1; // start after the nal_header_type which was '24'
// if we have at least 2 more bytes (the 16 bit size) then consume more data
while (ptr + 2 < (rtp_payloads[payload_index].Length - 1))
{
int size = (rtp_payloads[payload_index][ptr] << 8) + (rtp_payloads[payload_index][ptr + 1] << 0);
ptr = ptr + 2;
byte[] nal = new byte[size];
System.Array.Copy(rtp_payloads[payload_index], ptr, nal, 0, size); // copy the NAL
nal_units.Add(nal); // Add to list of NALs for this RTP frame. Start Codes like 00 00 00 01 get added later
ptr = ptr + size;
}
}
catch
{
_logger.Debug("H264 Aggregate Packet processing error");
}
}
else if (nal_header_type == 25)
{
_logger.Debug("Agg STAP-B not supported");
stap_b++;
}
else if (nal_header_type == 26)
{
_logger.Debug("Agg MTAP16 not supported");
mtap16++;
}
else if (nal_header_type == 27)
{
_logger.Debug("Agg MTAP24 not supported");
mtap24++;
}
else if (nal_header_type == 28)
{
_logger.Debug("Frag FU-A");
fu_a++;
// Parse Fragmentation Unit Header
int fu_header_s = (rtp_payloads[payload_index][1] >> 7) & 0x01; // start marker
int fu_header_e = (rtp_payloads[payload_index][1] >> 6) & 0x01; // end marker
int fu_header_r = (rtp_payloads[payload_index][1] >> 5) & 0x01; // reserved. should be 0
int fu_header_type = (rtp_payloads[payload_index][1] >> 0) & 0x1F; // Original NAL unit header
_logger.Debug("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e);
// Check Start and End flags
if (fu_header_s == 1 && fu_header_e == 0)
{
// Start of Fragment.
// Initiise the fragmented_nal byte array
// Build the NAL header with the original F and NRI flags but use the the Type field from the fu_header_type
byte reconstructed_nal_type = (byte)((nal_header_f_bit << 7) + (nal_header_nri << 5) + fu_header_type);
// Empty the stream
fragmented_nal.SetLength(0);
// Add reconstructed_nal_type byte to the memory stream
fragmented_nal.WriteByte(reconstructed_nal_type);
// copy the rest of the RTP payload to the memory stream
fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2);
}
if (fu_header_s == 0 && fu_header_e == 0)
{
// Middle part of Fragment
// Append this payload to the fragmented_nal
// Data starts after the NAL Unit Type byte and the FU Header byte
fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2);
}
if (fu_header_s == 0 && fu_header_e == 1)
{
// End part of Fragment
// Append this payload to the fragmented_nal
// Data starts after the NAL Unit Type byte and the FU Header byte
fragmented_nal.Write(rtp_payloads[payload_index], 2, rtp_payloads[payload_index].Length - 2);
// Add the NAL to the array of NAL units
nal_units.Add(fragmented_nal.ToArray());
}
}
else if (nal_header_type == 29)
{
_logger.Debug("Frag FU-B not supported");
fu_b++;
}
else
{
_logger.Debug("Unknown NAL header " + nal_header_type + " not supported");
}
}
// Output some statistics
_logger.Debug("Norm=" + norm + " ST-A=" + stap_a + " ST-B=" + stap_b + " M16=" + mtap16 + " M24=" + mtap24 + " FU-A=" + fu_a + " FU-B=" + fu_b);
// Output all the NALs that form one RTP Frame (one frame of video)
return nal_units;
}
}
}

View File

@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Inspectron.HawkEye.RTSP
{
// This class handles the H265 Payload
// It has methods to parse parameters in the SDP
// It has methods to process the RTP Payload
// By Roger Hardiman, RJH Technical Consultancy Ltd
public class H265Payload
{
// H265 / HEVC structure.
// An 'Access Unit' is the set of NAL Units that form one Picture
// NAL Units have a 2 byte header comprising of
// F Bit, Type, Layer ID and TID
int single, agg, frag = 0; // used for diagnostics stats
bool has_donl = false;
List<byte[]> temporary_rtp_payloads = new List<byte[]>(); // used to assemble the RTP packets that form one RTP Frame
// Eg all the RTP Packets from M=0 through to M=1
MemoryStream fragmented_nal = new MemoryStream(); // used to concatenate fragmented H264 NALs where NALs are split over RTP packets
// Constructor
public H265Payload(bool has_donl)
{
this.has_donl = has_donl;
}
public List<byte[]> Process_H265_RTP_Packet(byte[] rtp_payload, int rtp_marker) {
// Add payload to the List of payloads for the current Frame of Video
// ie all the payloads with M=0 up to the final payload where M=1
temporary_rtp_payloads.Add(rtp_payload); // Todo Could optimise this and go direct to Process Frame if just 1 packet in frame
if (rtp_marker == 1)
{
// End Marker is set. Process the list of RTP Packets (forming 1 RTP frame) and save the NALs to a file
List<byte[]> nal_units = Process_H265_RTP_Frame(temporary_rtp_payloads);
temporary_rtp_payloads.Clear();
return nal_units;
}
return null; // we don't have a frame yet. Keep accumulating RTP packets
}
// Process a RTP Frame. A RTP Frame can consist of several RTP Packets which have the same Timestamp
// Returns a list of NAL Units (with no 00 00 00 01 header and with no Size header)
private List<byte[]> Process_H265_RTP_Frame(List<byte[]> rtp_payloads)
{
Console.WriteLine("RTP Data comprised of " + rtp_payloads.Count + " rtp packets");
List<byte[]> nal_units = new List<byte[]>(); // Stores the NAL units for a Video Frame. May be more than one NAL unit in a video frame.
for (int payload_index = 0; payload_index < rtp_payloads.Count; payload_index++)
{
// Examine the first two bytes of the RTP data, the Payload Header
// F (Forbidden Bit),
// Type of NAL Unit (or VCL NAL Unit if Type is < 32),
// LayerId
// TID (TemporalID = TID - 1)
/*+---------------+---------------+
*|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*|F| Type | LayerId | TID |
*+-------------+-----------------+
*/
int payload_header = (rtp_payloads[payload_index][0] << 8) | (rtp_payloads[payload_index][1]);
int payload_header_f_bit = (payload_header >> 15) & 0x01;
int payload_header_type = (payload_header >> 9) & 0x3F;
int payload_header_layer_id = (payload_header >> 3) & 0x3F;
int payload_header_tid = payload_header & 0x7;
// There are three ways to Packetize NAL units into RTP Packets
// Single NAL Unit Packet
// Aggregation Packet (payload_header_type = 48)
// Fragmentation Unit (payload_header_type = 49)
// Single NAL Unit Packet
// 32=VPS
// 33=SPS
// 34=PPS
if (payload_header_type != 48 && payload_header_type != 49)
{
Console.WriteLine("Single NAL");
single++;
//TODO - Handle DONL
nal_units.Add(rtp_payloads[payload_index]);
}
// Aggregation Packet
else if (payload_header_type == 48)
{
Console.WriteLine("Aggregation Packet");
agg++;
// RTP packet contains multiple NALs, each with a 16 bit header
// Read 16 byte size
// Read NAL
// Use a Try/Catch to protect from bad RTP data where block sizes exceed the
// available data
try
{
int ptr = 2; // start after 16 bit Payload Header
// loop until the ptr has moved beyond the length of the data
while (ptr < (rtp_payloads[payload_index].Length - 1))
{
if (has_donl) ptr = ptr + 2; // step over the DONL data
int size = (rtp_payloads[payload_index][ptr] << 8) + (rtp_payloads[payload_index][ptr + 1] << 0);
ptr = ptr + 2;
byte[] nal = new byte[size];
System.Array.Copy(rtp_payloads[payload_index], ptr, nal, 0, size); // copy the NAL
nal_units.Add(nal); // Add to list of NALs for this RTP frame. Start Codes like 00 00 00 01 get added later
ptr = ptr + size;
}
}
catch
{
Console.WriteLine("H265 Aggregate Packet processing error");
}
}
// Fragmentation Unit
else if (payload_header_type == 49)
{
Console.WriteLine("Fragmentation Unit");
frag++;
// Parse Fragmentation Unit Header
int fu_header_s = (rtp_payloads[payload_index][2] >> 7) & 0x01; // start marker
int fu_header_e = (rtp_payloads[payload_index][2] >> 6) & 0x01; // end marker
int fu_header_type = (rtp_payloads[payload_index][2] >> 0) & 0x3F; // fu type
Console.WriteLine("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e);
// Check Start and End flags
if (fu_header_s == 1 && fu_header_e == 0)
{
// Start of Fragment.
// Initiise the fragmented_nal byte array
// Empty the stream
fragmented_nal.SetLength(0);
// Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type
int nal_header = (payload_header & 0x81FF); // strip out existing 'type'
nal_header = nal_header | (fu_header_type << 9);
fragmented_nal.WriteByte((byte)((nal_header >> 8) & 0xFF));
fragmented_nal.WriteByte((byte)((nal_header >> 0) & 0xFF));
if (has_donl)
{
// start copying after the DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5);
}
else
{
// there is no DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3);
}
}
if (fu_header_s == 0 && fu_header_e == 0)
{
// Middle part of Fragment
// Append this payload to the fragmented_nal
if (has_donl) {
// start copying after the DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5);
} else {
// there is no DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3);
}
}
if (fu_header_s == 0 && fu_header_e == 1)
{
// End part of Fragment
// Append this payload to the fragmented_nal
if (has_donl)
{
// start copying after the DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 5, rtp_payloads[payload_index].Length - 5);
}
else
{
// there is no DONL data
fragmented_nal.Write(rtp_payloads[payload_index], 3, rtp_payloads[payload_index].Length - 3);
}
// Add the NAL to the array of NAL units
nal_units.Add(fragmented_nal.ToArray());
}
}
else {
Console.WriteLine("Unknown Payload Header Type = " + payload_header_type);
}
}
// Output some statistics
Console.WriteLine("Single=" + single + " Agg=" + agg + " Frag=" + frag);
// Output all the NALs that form one RTP Frame (one frame of video)
return nal_units;
}
}
}

View File

@@ -0,0 +1,41 @@
namespace Inspectron.HawkEye.RTSP
{
/// <summary>
/// Interface for Transport of Rtsp (TCP, TCP+SSL,..)
/// </summary>
public interface IRtspTransport
{
/// <summary>
/// Gets the stream of the transport.
/// </summary>
/// <returns>A stream</returns>
System.IO.Stream GetStream();
/// <summary>
/// Gets the remote address.
/// </summary>
/// <value>The remote address.</value>
string RemoteAddress
{
get;
}
/// <summary>
/// Closes this instance.
/// </summary>
void Close();
/// <summary>
/// Gets a value indicating whether this <see cref="IRtspTransport"/> is connected.
/// </summary>
/// <value><c>true</c> if connected; otherwise, <c>false</c>.</value>
bool Connected { get; }
/// <summary>
/// Reconnect this instance.
/// <remarks>Must do nothing if already connected.</remarks>
/// </summary>
/// <exception cref="System.Net.Sockets.SocketException">Error during socket </exception>
void Reconnect();
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Describe a couple of port used to transfer video and command.
/// </summary>
public class PortCouple
{
/// <summary>
/// Gets or sets the first port number.
/// </summary>
/// <value>The first port.</value>
public int First { get; set; }
/// <summary>
/// Gets or sets the second port number.
/// </summary>
/// <remarks>If not present the value is 0</remarks>
/// <value>The second port.</value>
public int Second { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
public PortCouple()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
/// <param name="first">The first port.</param>
public PortCouple(int first)
{
First = first;
Second = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="PortCouple"/> class.
/// </summary>
/// <param name="first">The first port.</param>
/// <param name="second">The second port.</param>
public PortCouple(int first, int second)
{
First = first;
Second = second;
}
/// <summary>
/// Gets a value indicating whether this instance has second port.
/// </summary>
/// <value>
/// <c>true</c> if this instance has second port; otherwise, <c>false</c>.
/// </value>
public bool IsSecondPortPresent
{
get { return Second != 0; }
}
/// <summary>
/// Parses the int values of port.
/// </summary>
/// <param name="stringValue">A string value.</param>
/// <returns>The port couple</returns>
public static PortCouple Parse(string stringValue)
{
if (stringValue == null)
throw new ArgumentNullException("stringValue");
Contract.Requires(!string.IsNullOrEmpty(stringValue));
string[] values = stringValue.Split('-');
int tempValue;
int.TryParse(values[0], out tempValue);
PortCouple result = new PortCouple(tempValue);
tempValue = 0;
if (values.Length > 1)
int.TryParse(values[1], out tempValue);
result.Second = tempValue;
return result;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
if (IsSecondPortPresent)
return First.ToString(CultureInfo.InvariantCulture) + "-" + Second.ToString(CultureInfo.InvariantCulture);
else
return First.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Class wich represent each message echanged on Rtsp socket.
/// </summary>
public abstract class RtspChunk : ICloneable
{
/// <summary>
/// Logs the message to debug.
/// </summary>
public void LogMessage()
{
LogMessage(NLog.LogLevel.Debug);
}
/// <summary>
/// Logs the message.
/// </summary>
/// <param name="alevel">The log level.</param>
public abstract void LogMessage(NLog.LogLevel aLevel);
/// <summary>
/// Gets or sets the data associate with the message.
/// </summary>
/// <value>Array of byte transmit with the message.</value>
public byte[] Data
{ get; set; }
/// <summary>
/// Gets or sets the source port wich receive the message.
/// </summary>
/// <value>The source port.</value>
public RtspListener SourcePort { get; set; }
#region ICloneable Membres
/// <summary>
/// Crée un nouvel objet qui est une copie de l'instance en cours.
/// </summary>
/// <returns>
/// Nouvel objet qui est une copie de cette instance.
/// </returns>
public abstract object Clone();
#endregion
}
}

View File

@@ -0,0 +1,45 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Message wich represent data. ($ limited message)
/// </summary>
public class RtspData : RtspChunk
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// Logs the message to debug.
/// </summary>
public override void LogMessage(NLog.LogLevel aLevel)
{
// Default value to debug
if (aLevel == null)
aLevel = NLog.LogLevel.Debug;
// if the level is not logged directly return
if (!_logger.IsEnabled(aLevel))
return;
_logger.Log(aLevel, "Data message");
if (Data == null)
_logger.Log(aLevel, "Data : null");
else
_logger.Log(aLevel, "Data length :-{0}-", Data.Length);
}
public int Channel { get; set; }
/// <summary>
/// Clones this instance.
/// <remarks>Listner is not cloned</remarks>
/// </summary>
/// <returns>a clone of this instance</returns>
public override object Clone()
{
RtspData result = new RtspData();
result.Channel = this.Channel;
if (this.Data != null)
result.Data = this.Data.Clone() as byte[];
result.SourcePort = this.SourcePort;
return result;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// Class containing helper constant for general use headers.
/// </summary>
public static class RtspHeaderNames
{
public const string ContentBase = "Content-Base";
public const string ContentEncoding = "Content-Encoding";
public const string ContentType = "Content-Type";
public const string Public = "Public";
public const string Session = "Session";
public const string Transport = "Transport";
public const string WWWAuthenticate = "WWW-Authenticate";
public const string Authorization = "Authorization";
}
}

View File

@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace Inspectron.HawkEye.RTSP.Messages
{
public class RtspMessage : RtspChunk
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// The regex to validate the Rtsp message.
/// </summary>
private static readonly Regex _rtspVersionTest = new Regex(@"^RTSP/\d\.\d", RegexOptions.Compiled);
/// <summary>
/// Create the good type of Rtsp Message from the header.
/// </summary>
/// <param name="aRequestLine">A request line.</param>
/// <returns>An Rtsp message</returns>
public static RtspMessage GetRtspMessage(string aRequestLine)
{
// We can't determine the message
if (string.IsNullOrEmpty(aRequestLine))
return new RtspMessage();
string[] requestParts = aRequestLine.Split(new char[] { ' ' }, 3);
RtspMessage returnValue;
if (requestParts.Length == 3)
{
// A request is : Method SP Request-URI SP RTSP-Version
// A response is : RTSP-Version SP Status-Code SP Reason-Phrase
// RTSP-Version = "RTSP" "/" 1*DIGIT "." 1*DIGIT
if (_rtspVersionTest.IsMatch(requestParts[2]))
returnValue = RtspRequest.GetRtspRequest(requestParts);
else if (_rtspVersionTest.IsMatch(requestParts[0]))
returnValue = new RtspResponse();
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Got a strange message {0}", aRequestLine);
returnValue = new RtspMessage();
}
}
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Got a strange message {0}", aRequestLine);
returnValue = new RtspMessage();
}
returnValue.Command = aRequestLine;
return returnValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspMessage"/> class.
/// </summary>
public RtspMessage()
{
Data = new byte[0];
Creation = DateTime.Now;
}
private Dictionary<string, string> _headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
internal protected string[] commandArray;
/// <summary>
/// Gets or sets the creation time.
/// </summary>
/// <value>The creation time.</value>
public DateTime Creation { get; private set; }
/// <summary>
/// Gets or sets the command of the message (first line).
/// </summary>
/// <value>The command.</value>
public string Command
{
get
{
if (commandArray == null)
return string.Empty;
return string.Join(" ", commandArray);
}
set
{
if (value == null)
commandArray = new string[] { String.Empty };
else
commandArray = value.Split(new char[] {' '}, 3);
}
}
/// <summary>
/// Gets the Method of the message (eg OPTIONS, DESCRIBE, SETUP, PLAY).
/// </summary>
/// <value>The Method</value>
public string Method
{
get
{
if (commandArray == null)
return string.Empty;
return commandArray[0];
}
}
/// <summary>
/// Gets the headers of the message.
/// </summary>
/// <value>The headers.</value>
public Dictionary<string, string> Headers
{
get
{
return _headers;
}
}
/// <summary>
/// Adds one header from a string.
/// </summary>
/// <param name="line">The string containing header of format Header: Value.</param>
/// <exception cref="ArgumentNullException"><paramref name="line"/> is null</exception>
public void AddHeader(string line)
{
if (line == (string)null)
throw new ArgumentNullException("line");
//spliter
string[] elements = line.Split(new char[] { ':' }, 2);
if (elements.Length == 2)
{
_headers[elements[0].Trim()] = elements[1].TrimStart();
}
else
{
_logger.Warn(CultureInfo.InvariantCulture, "Invalid Header received : -{0}-", line);
}
}
/// <summary>
/// Gets or sets the Ccommande Seqquence number.
/// <remarks>If the header is not define or not a valid number it return 0</remarks>
/// </summary>
/// <value>The sequence number.</value>
public int CSeq
{
get
{
string returnStringValue;
int returnValue;
if (!(_headers.TryGetValue("CSeq", out returnStringValue) &&
int.TryParse(returnStringValue, out returnValue)))
returnValue = 0;
return returnValue;
}
set
{
_headers["CSeq"] = value.ToString(CultureInfo.InvariantCulture);
}
}
/// <summary>
/// Gets the session ID.
/// </summary>
/// <value>The session ID.</value>
public virtual string Session
{
get
{
if (!_headers.ContainsKey("Session"))
return null;
return _headers["Session"];
}
set
{
_headers["Session"] = value;
}
}
/// <summary>
/// Initialises the length of the data byte array from content lenth header.
/// </summary>
public void InitialiseDataFromContentLength()
{
int dataLength;
if (!(_headers.ContainsKey("Content-Length")
&& int.TryParse(_headers["Content-Length"], out dataLength)))
{
dataLength = 0;
}
this.Data = new byte[dataLength];
}
/// <summary>
/// Adjusts the content length header.
/// </summary>
public void AdjustContentLength()
{
if (Data.Length > 0)
{
_headers["Content-Length"] = Data.Length.ToString(CultureInfo.InvariantCulture);
}
else
{
_headers.Remove("Content-Length");
}
}
/// <summary>
/// Sends to the message to a stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is empty</exception>
/// <exception cref="ArgumentException"><paramref name="stream"/> can't be written.</exception>
public void SendTo(Stream stream)
{
// <pex>
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanWrite)
throw
new ArgumentException("Stream CanWrite == false, can't send message to it", "stream");
// </pex>
Contract.EndContractBlock();
Encoding encoder = ASCIIEncoding.UTF8;
StringBuilder outputString = new StringBuilder();
AdjustContentLength();
// output header
outputString.Append(Command);
outputString.Append("\r\n");
foreach (KeyValuePair<string, string> item in _headers)
{
outputString.AppendFormat("{0}: {1}\r\n", item.Key, item.Value);
}
outputString.Append("\r\n");
byte[] buffer = encoder.GetBytes(outputString.ToString());
lock(stream) {
stream.Write(buffer, 0, buffer.Length);
// Output data
if (Data.Length > 0)
stream.Write(Data, 0, Data.Length);
}
stream.Flush();
}
/// <summary>
/// Logs the message.
/// </summary>
/// <param name="aLevel">A log level.</param>
public override void LogMessage(NLog.LogLevel aLevel)
{
// Default value to debug
if (aLevel == null)
aLevel = NLog.LogLevel.Debug;
// if the level is not logged directly return
if (!_logger.IsEnabled(aLevel))
return;
_logger.Log(aLevel, "Commande : {0}", Command);
foreach (KeyValuePair<string, string> item in _headers)
{
_logger.Log(aLevel, "Header : {0}: {1}", item.Key, item.Value);
}
if (Data.Length > 0)
{
_logger.Log(aLevel, "Data :-{0}-", ASCIIEncoding.ASCII.GetString(Data));
}
}
/// <summary>
/// Crée un nouvel objet qui est une copie de l'instance en cours.
/// </summary>
/// <returns>
/// Nouvel objet qui est une copie de cette instance.
/// </returns>
public override object Clone()
{
RtspMessage returnValue = GetRtspMessage(this.Command);
foreach (var item in this.Headers)
{
if (item.Value == null)
returnValue.Headers.Add(item.Key.Clone() as string, null);
else
returnValue.Headers.Add(item.Key.Clone() as string, item.Value.Clone() as string);
}
returnValue.Data = this.Data.Clone() as byte[];
returnValue.SourcePort = this.SourcePort;
return returnValue;
}
}
}

View File

@@ -0,0 +1,191 @@
using System;
using System.Diagnostics;
namespace Inspectron.HawkEye.RTSP.Messages
{
/// <summary>
/// An Rtsp Request
/// </summary>
public class RtspRequest : RtspMessage
{
/// <summary>
/// Request type.
/// </summary>
public enum RequestType
{
UNKNOWN,
DESCRIBE,
ANNOUNCE,
GET_PARAMETER,
OPTIONS,
PAUSE,
PLAY,
RECORD,
REDIRECT,
SETUP,
SET_PARAMETER,
TEARDOWN,
}
/// <summary>
/// Parses the request command.
/// </summary>
/// <param name="aStringRequest">A string request command.</param>
/// <returns>The typed request.</returns>
internal static RequestType ParseRequest(string aStringRequest)
{
RequestType returnValue;
if (!Enum.TryParse<RequestType>(aStringRequest, true, out returnValue))
returnValue = RequestType.UNKNOWN;
return returnValue;
}
/// <summary>
/// Gets the Rtsp request.
/// </summary>
/// <param name="aRequestParts">A request parts.</param>
/// <returns>the parsed request</returns>
internal static RtspMessage GetRtspRequest(string[] aRequestParts)
{
// <pex>
Debug.Assert(aRequestParts != (string[])null, "aRequestParts");
Debug.Assert(aRequestParts.Length != 0, "aRequestParts.Length == 0");
// </pex>
// we already know this is a Request
RtspRequest returnValue;
switch (ParseRequest(aRequestParts[0]))
{
case RequestType.OPTIONS:
returnValue = new RtspRequestOptions();
break;
case RequestType.DESCRIBE:
returnValue = new RtspRequestDescribe();
break;
case RequestType.SETUP:
returnValue = new RtspRequestSetup();
break;
case RequestType.PLAY:
returnValue = new RtspRequestPlay();
break;
case RequestType.PAUSE:
returnValue = new RtspRequestPause();
break;
case RequestType.TEARDOWN:
returnValue = new RtspRequestTeardown();
break;
case RequestType.GET_PARAMETER:
returnValue = new RtspRequestGetParameter();
break;
case RequestType.ANNOUNCE:
returnValue = new RtspRequestAnnounce();
break;
case RequestType.RECORD:
returnValue = new RtspRequestRecord();
break;
/*
case RequestType.REDIRECT:
break;
case RequestType.SET_PARAMETER:
break;
*/
case RequestType.UNKNOWN:
default:
returnValue = new RtspRequest();
break;
}
return returnValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="RtspRequest"/> class.
/// </summary>
public RtspRequest()
{
Command = "OPTIONS * RTSP/1.0";
}
/// <summary>
/// Gets the request.
/// </summary>
/// <value>The request in string format.</value>
public string Request
{
get
{
return commandArray[0];
}
}
/// <summary>
/// Gets the request.
/// <remarks>The return value is typed with <see cref="Rtsp.RequestType"/> if the value is not
/// reconise the value is sent. The string value can be get by <see cref="Request"/></remarks>
/// </summary>
/// <value>The request.</value>
public RequestType RequestTyped
{
get
{
return ParseRequest(commandArray[0]);
}
set
{
if (Enum.IsDefined(typeof(RequestType), value))
commandArray[0] = value.ToString();
else
commandArray[0] = RequestType.UNKNOWN.ToString();
}
}
private Uri _RtspUri;
/// <summary>
/// Gets or sets the Rtsp asked URI.
/// </summary>
/// <value>The Rtsp asked URI.</value>
/// <remarks>The request with uri * is return with null URI</remarks>
public Uri RtspUri
{
get
{
if (commandArray.Length < 2 || commandArray[1]=="*")
return null;
if (_RtspUri == null)
Uri.TryCreate(commandArray[1], UriKind.Absolute, out _RtspUri);
return _RtspUri;
}
set
{
_RtspUri = value;
if (commandArray.Length < 2)
{
Array.Resize(ref commandArray, 3);
}
commandArray[1] = (value != null ? value.ToString().TrimEnd('/') : "*");
}
}
/// <summary>
/// Gets the assiociate OK response with the request.
/// </summary>
/// <returns>an Rtsp response correcponding to request.</returns>
public virtual RtspResponse CreateResponse()
{
RtspResponse returnValue = new RtspResponse();
returnValue.ReturnCode = 200;
returnValue.CSeq = this.CSeq;
if (this.Headers.ContainsKey(RtspHeaderNames.Session))
{
returnValue.Headers[RtspHeaderNames.Session] = this.Headers[RtspHeaderNames.Session];
}
return returnValue;
}
public Object ContextData { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More