475 lines
18 KiB
C#
475 lines
18 KiB
C#
using Hawkeye.VisionBuilder.Workflow.Configuration;
|
|
using Hawkeye.VisionBuilder.Workflow.Links;
|
|
using OpenCvSharp;
|
|
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;
|
|
|
|
namespace Hawkeye.VisionBuilder.Workflow;
|
|
|
|
public class WorkflowList
|
|
{
|
|
|
|
|
|
public IImageSource ImageSource { get; set; }
|
|
public Mat? RecipeImage { get; set; }
|
|
|
|
public WorkflowConfiguration Configuration { get; set; } = new WorkflowConfiguration();
|
|
public List<BaseOperation> Operations { get; set; } = new List<BaseOperation>();
|
|
public Context Context { get; set; } = new Context();
|
|
|
|
private int _executionCounter = 0;
|
|
|
|
public void Execute()
|
|
{
|
|
|
|
Context.GraphicsElements.Clear();
|
|
UpdateConfigVariables();
|
|
long total = 0;
|
|
foreach (BaseOperation operation in Operations)
|
|
{
|
|
if (!operation.Enabled)
|
|
{
|
|
operation.Result=true;
|
|
continue;
|
|
}
|
|
if (Context.CancellationToken.IsCancellationRequested) break;
|
|
operation.Interpret(Context);
|
|
}
|
|
_executionCounter++;
|
|
if (_executionCounter % 10 == 0)
|
|
GC.Collect();
|
|
|
|
|
|
}
|
|
|
|
public BaseOperation GetById(Guid originId)
|
|
{
|
|
var operation = Operations.FirstOrDefault(x =>
|
|
x.Id == originId);
|
|
if(operation==null)return NoOrigin.Instance;
|
|
return operation as BaseOperation;
|
|
}
|
|
|
|
public IHaveOrigin GetOriginById(Guid originId)
|
|
{
|
|
return GetById(originId) as IHaveOrigin ?? NoOrigin.Instance;
|
|
}
|
|
|
|
public void ExecuteTill(BaseOperation op)
|
|
{
|
|
|
|
Context.GraphicsElements.Clear();
|
|
UpdateConfigVariables();
|
|
foreach (BaseOperation operation in Operations)
|
|
{
|
|
if(!operation.Enabled)continue;
|
|
if(operation == op) break;
|
|
operation.Interpret(Context);
|
|
}
|
|
}
|
|
public void ExecuteOne(BaseOperation operation)
|
|
{
|
|
Context.GraphicsElements.Clear();
|
|
UpdateConfigVariables();
|
|
try
|
|
{
|
|
|
|
operation.Interpret(Context);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
operation.SetError(e.Message);
|
|
}
|
|
|
|
}
|
|
|
|
void UpdateConfigVariables()
|
|
{
|
|
BaseOperation.SetScriptValue("config", Configuration);
|
|
}
|
|
public void Save(BinaryWriter bw)
|
|
{
|
|
Configuration.Save(bw);
|
|
bw.Write(Operations.Count);
|
|
foreach (BaseOperation operation in Operations)
|
|
{
|
|
bw.Write(operation.GetType().AssemblyQualifiedName);
|
|
operation.Save(bw);
|
|
}
|
|
if (RecipeImage == null)
|
|
{
|
|
var mat = new Mat(128, 128, MatType.CV_8UC3, Scalar.Gray);
|
|
var image = mat.ToBytes();
|
|
bw.Write(image.Length);
|
|
bw.Write(image);
|
|
}
|
|
else
|
|
{
|
|
var mat = RecipeImage;
|
|
var image = mat.Resize(new Size(128, 128)).ToBytes();
|
|
bw.Write(image.Length);
|
|
bw.Write(image);
|
|
}
|
|
|
|
}
|
|
public event Action PythonMissing=delegate { };
|
|
public void Load(BinaryReader br, OperationDiscoveryService operationDiscoveryService)
|
|
{
|
|
Configuration.Load(br);
|
|
if (!File.Exists(Configuration.PythonPath))
|
|
PythonMissing();
|
|
int count = br.ReadInt32();
|
|
Operations.Clear();
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
string operationType = br.ReadString();
|
|
var type=Type.GetType(operationType);
|
|
BaseOperation operation = operationDiscoveryService.CreateInstance(type);
|
|
operation.Load(br);
|
|
Operations.Add(operation);
|
|
}
|
|
int length = br.ReadInt32();
|
|
var image = br.ReadBytes(length);
|
|
RecipeImage = Mat.FromImageData(image);
|
|
|
|
}
|
|
|
|
public static WorkflowList LoadFromFile(string fileName)
|
|
{
|
|
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
|
|
using (var br = new BinaryReader(fs))
|
|
{
|
|
var workflowList = new WorkflowList();
|
|
workflowList.Load(br, new OperationDiscoveryService(workflowList));
|
|
return workflowList;
|
|
}
|
|
}
|
|
|
|
public void SaveXML(XmlWriter writer)
|
|
{
|
|
writer.WriteStartElement("Workflow");
|
|
|
|
// Save Configuration
|
|
writer.WriteStartElement("Configuration");
|
|
writer.WriteAttributeString("RuntimeCameraType", Configuration.RuntimeCameraType.ToString());
|
|
writer.WriteAttributeString("DevelopmentCameraType", Configuration.DevelopmentCameraType.ToString());
|
|
writer.WriteAttributeString("Outputs", Configuration.Outputs.ToString());
|
|
writer.WriteAttributeString("EmulationPath", Configuration.EmulationPath);
|
|
writer.WriteAttributeString("PythonPath", Configuration.PythonPath);
|
|
writer.WriteAttributeString("ResultPin", Configuration.ResultPin.ToString());
|
|
writer.WriteAttributeString("SerialPort", Configuration.SerialPort);
|
|
writer.WriteAttributeString("Delay", Configuration.Delay.ToString());
|
|
writer.WriteEndElement(); // Configuration
|
|
|
|
// Save Operations
|
|
writer.WriteStartElement("Operations");
|
|
writer.WriteAttributeString("Count", Operations.Count.ToString());
|
|
|
|
foreach (BaseOperation operation in Operations)
|
|
{
|
|
writer.WriteStartElement("Operation");
|
|
writer.WriteAttributeString("Type", operation.GetType().AssemblyQualifiedName);
|
|
operation.SaveXML(writer);
|
|
writer.WriteEndElement(); // Operation
|
|
}
|
|
writer.WriteEndElement(); // Operations
|
|
|
|
// Save Recipe Image
|
|
writer.WriteStartElement("RecipeImage");
|
|
if (RecipeImage == null)
|
|
{
|
|
var mat = new Mat(128, 128, MatType.CV_8UC3, Scalar.Gray);
|
|
var image = mat.ToBytes();
|
|
writer.WriteAttributeString("Length", image.Length.ToString());
|
|
writer.WriteBase64(image, 0, image.Length);
|
|
}
|
|
else
|
|
{
|
|
var mat = RecipeImage;
|
|
var image = mat.Resize(new Size(128, 128)).ToBytes();
|
|
writer.WriteAttributeString("Length", image.Length.ToString());
|
|
writer.WriteBase64(image, 0, image.Length);
|
|
}
|
|
writer.WriteEndElement(); // RecipeImage
|
|
|
|
writer.WriteEndElement(); // Workflow
|
|
}
|
|
|
|
public void LoadXML(XmlReader reader, OperationDiscoveryService operationDiscoveryService)
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
if (reader.NodeType == XmlNodeType.Element)
|
|
{
|
|
switch (reader.Name)
|
|
{
|
|
case "Configuration":
|
|
LoadConfigurationXML(reader);
|
|
break;
|
|
case "Operations":
|
|
LoadOperationsXML(reader, operationDiscoveryService);
|
|
break;
|
|
case "RecipeImage":
|
|
LoadRecipeImageXML(reader);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadConfigurationXML(XmlReader reader)
|
|
{
|
|
if (reader.GetAttribute("RuntimeCameraType") != null)
|
|
Configuration.RuntimeCameraType = Enum.Parse<ECameraType>(reader.GetAttribute("RuntimeCameraType"));
|
|
if (reader.GetAttribute("DevelopmentCameraType") != null)
|
|
Configuration.DevelopmentCameraType = Enum.Parse<ECameraType>(reader.GetAttribute("DevelopmentCameraType"));
|
|
if (reader.GetAttribute("Outputs") != null)
|
|
Configuration.Outputs = Enum.Parse<EOutputs>(reader.GetAttribute("Outputs"));
|
|
if (reader.GetAttribute("EmulationPath") != null)
|
|
Configuration.EmulationPath = reader.GetAttribute("EmulationPath");
|
|
if (reader.GetAttribute("PythonPath") != null)
|
|
Configuration.PythonPath = reader.GetAttribute("PythonPath");
|
|
if (reader.GetAttribute("ResultPin") != null)
|
|
Configuration.ResultPin = int.Parse(reader.GetAttribute("ResultPin"));
|
|
if (reader.GetAttribute("SerialPort") != null)
|
|
Configuration.SerialPort = reader.GetAttribute("SerialPort");
|
|
if (reader.GetAttribute("Delay") != null)
|
|
Configuration.Delay = int.Parse(reader.GetAttribute("Delay"));
|
|
}
|
|
|
|
private void LoadOperationsXML(XmlReader reader, OperationDiscoveryService operationDiscoveryService)
|
|
{
|
|
Operations.Clear();
|
|
|
|
while (reader.Read())
|
|
{
|
|
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Operation")
|
|
{
|
|
string operationType = reader.GetAttribute("Type");
|
|
var type = Type.GetType(operationType);
|
|
if (type != null)
|
|
{
|
|
BaseOperation operation = operationDiscoveryService.CreateInstance(type);
|
|
// go to child elements of Operation
|
|
operation.LoadXML(reader);
|
|
Operations.Add(operation);
|
|
}
|
|
}
|
|
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Operations")
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadRecipeImageXML(XmlReader reader)
|
|
{
|
|
if (reader.GetAttribute("Length") != null)
|
|
{
|
|
int length = int.Parse(reader.GetAttribute("Length"));
|
|
byte[] imageData = new byte[length];
|
|
int bytesRead = reader.ReadElementContentAsBase64(imageData, 0, length);
|
|
if (bytesRead > 0)
|
|
{
|
|
RecipeImage = Mat.FromImageData(imageData);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
} |