c# script support
python server support
This commit is contained in:
1092
Hawkeye.VisionBuilder.Workflow/API/PythonAPI.cs
Normal file
1092
Hawkeye.VisionBuilder.Workflow/API/PythonAPI.cs
Normal file
File diff suppressed because it is too large
Load Diff
103
Hawkeye.VisionBuilder.Workflow/API/generator.nswag
Normal file
103
Hawkeye.VisionBuilder.Workflow/API/generator.nswag
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"runtime": "Net80",
|
||||||
|
"defaultVariables": null,
|
||||||
|
"documentGenerator": {
|
||||||
|
"fromDocument": {
|
||||||
|
"json": "",
|
||||||
|
"url": "http://localhost:8000/openapi.json",
|
||||||
|
"output": null,
|
||||||
|
"newLineBehavior": "Auto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"codeGenerators": {
|
||||||
|
"openApiToCSharpClient": {
|
||||||
|
"clientBaseClass": null,
|
||||||
|
"configurationClass": null,
|
||||||
|
"generateClientClasses": true,
|
||||||
|
"suppressClientClassesOutput": false,
|
||||||
|
"generateClientInterfaces": false,
|
||||||
|
"suppressClientInterfacesOutput": false,
|
||||||
|
"clientBaseInterface": null,
|
||||||
|
"injectHttpClient": true,
|
||||||
|
"disposeHttpClient": true,
|
||||||
|
"protectedMethods": [],
|
||||||
|
"generateExceptionClasses": true,
|
||||||
|
"exceptionClass": "ApiException",
|
||||||
|
"wrapDtoExceptions": true,
|
||||||
|
"useHttpClientCreationMethod": false,
|
||||||
|
"httpClientType": "System.Net.Http.HttpClient",
|
||||||
|
"useHttpRequestMessageCreationMethod": false,
|
||||||
|
"useBaseUrl": true,
|
||||||
|
"generateBaseUrlProperty": true,
|
||||||
|
"generateSyncMethods": false,
|
||||||
|
"generatePrepareRequestAndProcessResponseAsAsyncMethods": false,
|
||||||
|
"exposeJsonSerializerSettings": false,
|
||||||
|
"clientClassAccessModifier": "public",
|
||||||
|
"typeAccessModifier": "public",
|
||||||
|
"propertySetterAccessModifier": "",
|
||||||
|
"generateNativeRecords": false,
|
||||||
|
"useRequiredKeyword": false,
|
||||||
|
"generateContractsOutput": false,
|
||||||
|
"contractsNamespace": null,
|
||||||
|
"contractsOutputFilePath": null,
|
||||||
|
"parameterDateTimeFormat": "s",
|
||||||
|
"parameterDateFormat": "yyyy-MM-dd",
|
||||||
|
"generateUpdateJsonSerializerSettingsMethod": true,
|
||||||
|
"useRequestAndResponseSerializationSettings": false,
|
||||||
|
"serializeTypeInformation": false,
|
||||||
|
"queryNullValue": "",
|
||||||
|
"className": "PythonModelAPI",
|
||||||
|
"operationGenerationMode": "SingleClientFromPathSegments",
|
||||||
|
"additionalNamespaceUsages": [],
|
||||||
|
"additionalContractNamespaceUsages": [],
|
||||||
|
"generateOptionalParameters": false,
|
||||||
|
"generateJsonMethods": false,
|
||||||
|
"enforceFlagEnums": false,
|
||||||
|
"parameterArrayType": "System.Collections.Generic.IEnumerable",
|
||||||
|
"parameterDictionaryType": "System.Collections.Generic.IDictionary",
|
||||||
|
"responseArrayType": "System.Collections.Generic.ICollection",
|
||||||
|
"responseDictionaryType": "System.Collections.Generic.IDictionary",
|
||||||
|
"wrapResponses": false,
|
||||||
|
"wrapResponseMethods": [],
|
||||||
|
"generateResponseClasses": true,
|
||||||
|
"responseClass": "SwaggerResponse",
|
||||||
|
"namespace": "Hawkeye.VisionBuilder.Workflow",
|
||||||
|
"requiredPropertiesMustBeDefined": true,
|
||||||
|
"dateType": "System.DateTimeOffset",
|
||||||
|
"jsonConverters": null,
|
||||||
|
"anyType": "object",
|
||||||
|
"dateTimeType": "System.DateTimeOffset",
|
||||||
|
"timeType": "System.TimeSpan",
|
||||||
|
"timeSpanType": "System.TimeSpan",
|
||||||
|
"arrayType": "System.Collections.Generic.ICollection",
|
||||||
|
"arrayInstanceType": "System.Collections.ObjectModel.Collection",
|
||||||
|
"dictionaryType": "System.Collections.Generic.IDictionary",
|
||||||
|
"dictionaryInstanceType": "System.Collections.Generic.Dictionary",
|
||||||
|
"arrayBaseType": "System.Collections.ObjectModel.Collection",
|
||||||
|
"dictionaryBaseType": "System.Collections.Generic.Dictionary",
|
||||||
|
"classStyle": "Poco",
|
||||||
|
"jsonLibrary": "NewtonsoftJson",
|
||||||
|
"jsonPolymorphicSerializationStyle": "NJsonSchema",
|
||||||
|
"generateDefaultValues": true,
|
||||||
|
"generateDataAnnotations": true,
|
||||||
|
"excludedTypeNames": [],
|
||||||
|
"excludedParameterNames": [],
|
||||||
|
"handleReferences": false,
|
||||||
|
"generateImmutableArrayProperties": false,
|
||||||
|
"generateImmutableDictionaryProperties": false,
|
||||||
|
"jsonSerializerSettingsTransformationMethod": null,
|
||||||
|
"inlineNamedArrays": false,
|
||||||
|
"inlineNamedDictionaries": false,
|
||||||
|
"inlineNamedTuples": true,
|
||||||
|
"inlineNamedAny": false,
|
||||||
|
"generateDtoTypes": true,
|
||||||
|
"generateOptionalPropertiesAsNullable": false,
|
||||||
|
"generateNullableReferenceTypes": false,
|
||||||
|
"templateDirectory": null,
|
||||||
|
"serviceHost": null,
|
||||||
|
"serviceSchemes": null,
|
||||||
|
"output": "PythonAPI.cs",
|
||||||
|
"newLineBehavior": "Auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using static Community.CsharpSqlite.Sqlite3;
|
||||||
|
|
||||||
|
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||||
|
|
||||||
|
public class CSharpDataTransferHTTP
|
||||||
|
{
|
||||||
|
private readonly PythonModelAPI _api;
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private readonly CancellationTokenSource _pingCts = new();
|
||||||
|
private readonly Thread _pingThread;
|
||||||
|
|
||||||
|
public CSharpDataTransferHTTP()
|
||||||
|
{
|
||||||
|
var handler = new SocketsHttpHandler
|
||||||
|
{
|
||||||
|
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(10),
|
||||||
|
MaxConnectionsPerServer = 100
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
_client = new HttpClient(handler)
|
||||||
|
{
|
||||||
|
// i've added this
|
||||||
|
DefaultRequestHeaders =
|
||||||
|
{
|
||||||
|
ExpectContinue = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_api = new PythonModelAPI("http://localhost:8000",_client);
|
||||||
|
_pingThread = new Thread(PingLoop) { IsBackground = true };
|
||||||
|
_pingThread.Start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PingLoop()
|
||||||
|
{
|
||||||
|
while (!_pingCts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_client.GetAsync("http://localhost:8000/").Wait();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors, optionally log
|
||||||
|
}
|
||||||
|
Thread.Sleep(3000); // Ping every 3 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadModel(string path, string modelName)
|
||||||
|
{
|
||||||
|
_api.LoadModelAsync(new LoadModelIn()
|
||||||
|
{
|
||||||
|
Name = modelName,
|
||||||
|
Path = Path.GetFileName(path)
|
||||||
|
}).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ActivateModel(string modelName)
|
||||||
|
{
|
||||||
|
_api.ActivateModelAsync(new ActivateModelIn(){Name = modelName}).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] GetAcceptSize()
|
||||||
|
{
|
||||||
|
var response = _api.GetAcceptSizeAsync().Result;
|
||||||
|
|
||||||
|
return response.Accept_size.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] GetOutputSize()
|
||||||
|
{
|
||||||
|
var response = _api.GetOutputSizeAsync().Result;
|
||||||
|
|
||||||
|
return response.Accept_size.ToArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] PredictRaw(byte[] byteArray)
|
||||||
|
{
|
||||||
|
var response = _api.PredictRawAsync(new FileParameter(new MemoryStream(byteArray))).Result;
|
||||||
|
var stream = response.Stream;
|
||||||
|
// read the stream to byte array
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
stream.CopyTo(memoryStream);
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var responseBytes = memoryStream.ToArray();
|
||||||
|
return responseBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Predict(byte[] byteArray)
|
||||||
|
{
|
||||||
|
var response = _api.PredictAsync(new FileParameter(new MemoryStream(byteArray))).Result;
|
||||||
|
var stream = response.Stream;
|
||||||
|
// read the stream to byte array
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
stream.CopyTo(memoryStream);
|
||||||
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var responseBytes = memoryStream.ToArray();
|
||||||
|
return responseBytes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,5 +2,26 @@
|
|||||||
|
|
||||||
public class PythonModelProxyHTTP
|
public class PythonModelProxyHTTP
|
||||||
{
|
{
|
||||||
|
private static bool _isInitialized = false;
|
||||||
|
private static UvicornScriptRunner _pythonScriptRunner;
|
||||||
|
private static CSharpDataTransferHTTP _cSharpDataTransferHTTP;
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
|
var workDirAbsPath = Path.GetFullPath(@"..\Data\Models");
|
||||||
|
_pythonScriptRunner = new UvicornScriptRunner(workDirAbsPath);
|
||||||
|
_cSharpDataTransferHTTP = new CSharpDataTransferHTTP();
|
||||||
|
|
||||||
|
_pythonScriptRunner.Start();
|
||||||
|
Thread.Sleep(1000); // Wait for the server to start
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CSharpDataTransferHTTP GetInterface()
|
||||||
|
{
|
||||||
|
if (!_isInitialized) Initialize();
|
||||||
|
|
||||||
|
return _cSharpDataTransferHTTP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||||
|
|
||||||
|
public class UvicornScriptRunner
|
||||||
|
{
|
||||||
|
private Process process;
|
||||||
|
private readonly string _workingDirectory;
|
||||||
|
|
||||||
|
public UvicornScriptRunner(string workingDirectory)
|
||||||
|
{
|
||||||
|
_workingDirectory = workingDirectory;
|
||||||
|
}
|
||||||
|
public static string ConvertToWslPath(string windowsPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(windowsPath))
|
||||||
|
throw new ArgumentException("Path cannot be null or empty.", nameof(windowsPath));
|
||||||
|
|
||||||
|
// Replace backslashes with forward slashes
|
||||||
|
string wslPath = windowsPath.Replace('\\', '/');
|
||||||
|
|
||||||
|
// Extract the drive letter and convert it to WSL format
|
||||||
|
if (wslPath.Length > 1 && wslPath[1] == ':')
|
||||||
|
{
|
||||||
|
char driveLetter = char.ToLower(wslPath[0]);
|
||||||
|
wslPath = $"/mnt/{driveLetter}{wslPath.Substring(2)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return wslPath;
|
||||||
|
}
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//startInfo.CreateNoWindow = true;
|
||||||
|
// create and start the process
|
||||||
|
process = new Process();
|
||||||
|
var convertedWd = ConvertToWslPath(_workingDirectory);
|
||||||
|
var arg =
|
||||||
|
$@"--distribution ubuntu --user root --cd ""{convertedWd}"" -- uvicorn PythonModelAPI:app";
|
||||||
|
process.StartInfo = new ProcessStartInfo("wsl", arg)
|
||||||
|
{
|
||||||
|
UseShellExecute = false
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
// on windows only
|
||||||
|
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||||
|
ChildProcessTracker.AddProcess(process);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (process != null && !process.HasExited)
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
process.Dispose();
|
||||||
|
process = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,13 +9,29 @@
|
|||||||
<Configurations>Debug;Release;CPU</Configurations>
|
<Configurations>Debug;Release;CPU</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="OpenAPIs\**" />
|
||||||
|
<EmbeddedResource Remove="OpenAPIs\**" />
|
||||||
|
<None Remove="OpenAPIs\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ILGPU" Version="1.5.1" />
|
<PackageReference Include="ILGPU" Version="1.5.1" />
|
||||||
<PackageReference Include="IronPython" Version="3.4.0" />
|
<PackageReference Include="IronPython" Version="3.4.0" />
|
||||||
<PackageReference Include="MessagePack" Version="2.5.108" />
|
<PackageReference Include="MessagePack" Version="2.5.108" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.ApiDescription.Client" Version="7.0.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
|
||||||
<PackageReference Include="NetMQ" Version="4.0.1.11" />
|
<PackageReference Include="NetMQ" Version="4.0.1.11" />
|
||||||
|
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
<PackageReference Include="Ninject" Version="3.3.6" />
|
||||||
|
<PackageReference Include="NSwag.ApiDescription.Client" Version="13.18.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="OpenCvSharp4" Version="4.6.0.20220608" />
|
<PackageReference Include="OpenCvSharp4" Version="4.6.0.20220608" />
|
||||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.6.0.20220608" />
|
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.6.0.20220608" />
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ public abstract class ColorAIOperation:BaseOperation
|
|||||||
public ColorAIOperation(string model_name)
|
public ColorAIOperation(string model_name)
|
||||||
{
|
{
|
||||||
_modelName = model_name;
|
_modelName = model_name;
|
||||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
_cSharpDataTransferMQRPC = PythonModelProxyHTTP.GetInterface();
|
||||||
CanHaveProcessingError = false;
|
CanHaveProcessingError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
private readonly CSharpDataTransferHTTP _cSharpDataTransferMQRPC;
|
||||||
private int _imageWidth;
|
private int _imageWidth;
|
||||||
private int _imageHeight;
|
private int _imageHeight;
|
||||||
|
|
||||||
@@ -52,15 +52,16 @@ public abstract class ColorAIOperation:BaseOperation
|
|||||||
this.SetError("Model file not found");
|
this.SetError("Model file not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
|
||||||
|
_cSharpDataTransferMQRPC.LoadModel(ModelFilePath.Path, _modelName);
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_cSharpDataTransferMQRPC.ActivateModel(_modelName);
|
||||||
|
|
||||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
var sizeEncoded = _cSharpDataTransferMQRPC.GetAcceptSize();
|
||||||
|
|
||||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
|
||||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||||
_imageWidth = size[1];
|
_imageWidth = size[1];
|
||||||
_imageHeight = size[2];
|
_imageHeight = size[2];
|
||||||
@@ -82,22 +83,30 @@ public abstract class ColorAIOperation:BaseOperation
|
|||||||
allowedFlags[i] = 1;
|
allowedFlags[i] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall(IsRaw?"predict_raw": "predict", byteArray));
|
|
||||||
|
|
||||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
byte[] result;
|
||||||
|
if (IsRaw)
|
||||||
|
{
|
||||||
|
result = _cSharpDataTransferMQRPC.PredictRaw(byteArray);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = _cSharpDataTransferMQRPC.Predict(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
byte[] resultColor;
|
byte[] resultColor;
|
||||||
|
|
||||||
if (IsRaw)
|
if (IsRaw)
|
||||||
{
|
{
|
||||||
resultColor = result.Chunk(25).Select(x =>
|
resultColor = result.Chunk(25).Select(x =>
|
||||||
{
|
{
|
||||||
return x.Select((x, i) => allowedFlags[i] == 1 ? (byte) (x) : (byte) 0).Max();
|
return x.Select((x, i) => allowedFlags[i] == 1 ? (byte) (x) : (byte) 0).Max();
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
//resultColor = result.Select(x => (byte)(x)).ToArray();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resultColor = result.Select(x => allowedFlags[x]==1? (byte)(x * 255) : (byte)0).ToArray();
|
resultColor = result.Select(x => allowedFlags[x]==1? (byte)(x * 255.0) : (byte)0).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ public class RawModelAIOperation: BaseOperation
|
|||||||
public RawModelAIOperation()
|
public RawModelAIOperation()
|
||||||
{
|
{
|
||||||
|
|
||||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
_cSharpDataTransferMQRPC = PythonModelProxyHTTP.GetInterface();
|
||||||
CanHaveProcessingError = false;
|
CanHaveProcessingError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
private CSharpDataTransferHTTP _cSharpDataTransferMQRPC;
|
||||||
private int _imageWidth;
|
private int _imageWidth;
|
||||||
private int _imageHeight;
|
private int _imageHeight;
|
||||||
|
|
||||||
@@ -66,34 +66,22 @@ public class RawModelAIOperation: BaseOperation
|
|||||||
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
|
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
|
||||||
ModelFilePath.Path = absPath;
|
ModelFilePath.Path = absPath;
|
||||||
}
|
}
|
||||||
|
_cSharpDataTransferMQRPC.LoadModel(ModelFilePath.Path, _modelName);
|
||||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_cSharpDataTransferMQRPC.ActivateModel(_modelName);
|
||||||
|
|
||||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
|
||||||
|
|
||||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
var sizeEncoded = _cSharpDataTransferMQRPC.GetAcceptSize();
|
||||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||||
_imageWidth = size[1];
|
_imageWidth = size[0];
|
||||||
_imageHeight = size[2];
|
_imageHeight = size[1];
|
||||||
|
|
||||||
var outputSize = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_output_size"));
|
var outputSize = _cSharpDataTransferMQRPC.GetOutputSize();
|
||||||
var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray();
|
var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray();
|
||||||
|
|
||||||
int classes;
|
|
||||||
if (outputSizeArray.Length<3)
|
|
||||||
{
|
|
||||||
classes=1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
classes = outputSizeArray[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var currentImage = context.ActiveImage;
|
var currentImage = context.ActiveImage;
|
||||||
|
|
||||||
@@ -105,20 +93,11 @@ public class RawModelAIOperation: BaseOperation
|
|||||||
|
|
||||||
var allowed = FilterClasses.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray();
|
var allowed = FilterClasses.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray();
|
||||||
|
|
||||||
var allowedFlags = new byte[128];
|
var result = _cSharpDataTransferMQRPC.PredictRaw(byteArray);
|
||||||
foreach (var i in allowed)
|
|
||||||
{
|
|
||||||
allowedFlags[i] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("predict_raw", byteArray));
|
|
||||||
|
|
||||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
|
||||||
|
|
||||||
byte[] resultColor;
|
byte[] resultColor;
|
||||||
resultColor = result.Select(x => (byte)(x) ).ToArray();
|
resultColor = result.Select(x => (byte)(x) ).ToArray();
|
||||||
|
|
||||||
|
|
||||||
// black and white mask
|
// black and white mask
|
||||||
var mask = new Mat(outputSizeArray[0], outputSizeArray[1], MatType.CV_8UC1, resultColor);
|
var mask = new Mat(outputSizeArray[0], outputSizeArray[1], MatType.CV_8UC1, resultColor);
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public class YoloDetectionOperation:BaseOperation
|
|||||||
}
|
}
|
||||||
protected override void InterpretInternal(Context context)
|
protected override void InterpretInternal(Context context)
|
||||||
{
|
{
|
||||||
|
CheckImageExists(context);
|
||||||
CheckColorful(context);
|
CheckColorful(context);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Compunet.YoloV8;
|
||||||
|
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||||
|
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||||
|
using Microsoft.Scripting.Runtime;
|
||||||
|
using OpenCvSharp;
|
||||||
|
|
||||||
|
namespace Hawkeye.VisionBuilder.Workflow.Operations.Scripts;
|
||||||
|
|
||||||
|
[Category("Script")]
|
||||||
|
public class CSharpProcessOperation:BaseOperation
|
||||||
|
{
|
||||||
|
private FilePath _scriptFilePath = new FilePath() { Format = "C# dll(*.dll)|*.dll" };
|
||||||
|
private bool _initialized;
|
||||||
|
private object _processor;
|
||||||
|
private MethodInfo? _method;
|
||||||
|
|
||||||
|
|
||||||
|
public FilePath ScriptFilePath
|
||||||
|
{
|
||||||
|
get => _scriptFilePath;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_scriptFilePath = value;
|
||||||
|
_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
if (!File.Exists(ScriptFilePath?.Path))
|
||||||
|
{
|
||||||
|
this.SetError("Script file not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var absPath = Path.GetFullPath(ScriptFilePath?.Path);
|
||||||
|
var loadContext = new PluginLoadContext(absPath);
|
||||||
|
var assembly = loadContext.LoadFromAssemblyPath(absPath);
|
||||||
|
|
||||||
|
// find class that has Process method
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
var method = type.GetMethod("ProcessImage", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
if (method != null)
|
||||||
|
{
|
||||||
|
_processor = Activator.CreateInstance(type);
|
||||||
|
_method = method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Label = Path.GetFileNameWithoutExtension(ScriptFilePath?.Path);
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InterpretInternal(Context context)
|
||||||
|
{
|
||||||
|
CheckImageExists(context);
|
||||||
|
if (!EnsureInitialized()) return;
|
||||||
|
|
||||||
|
var image = context.ActiveImage.ImageData;
|
||||||
|
var result = (Mat)_method.Invoke(_processor, new []{ image });
|
||||||
|
|
||||||
|
context.ActiveImage = new HawkeyeImage()
|
||||||
|
{
|
||||||
|
ImageData = result
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
|
||||||
|
namespace Hawkeye.VisionBuilder.Workflow.Operations.Scripts;
|
||||||
|
|
||||||
|
class PluginLoadContext : AssemblyLoadContext
|
||||||
|
{
|
||||||
|
private AssemblyDependencyResolver _resolver;
|
||||||
|
|
||||||
|
public PluginLoadContext(string pluginPath)
|
||||||
|
{
|
||||||
|
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Assembly Load(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||||
|
if (assemblyPath != null)
|
||||||
|
{
|
||||||
|
return LoadFromAssemblyPath(assemblyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||||
|
{
|
||||||
|
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||||
|
if (libraryPath != null)
|
||||||
|
{
|
||||||
|
return LoadUnmanagedDllFromPath(libraryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,22 +1,43 @@
|
|||||||
|
|
||||||
# app.py
|
# app.py
|
||||||
from fastapi import FastAPI, UploadFile, File, Body
|
from fastapi import FastAPI, Response, UploadFile, File, Body, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import io
|
import io
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
import time
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
# ==== Schemas (show up in Swagger) ====
|
||||||
|
class PredictOut(BaseModel):
|
||||||
|
result: List[int]
|
||||||
|
model_config = {"json_schema_extra": {"examples": [{"result": [0,1,2,3]}]}}
|
||||||
|
|
||||||
|
class StatusOut(BaseModel):
|
||||||
|
status: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
class AcceptSizeOut(BaseModel):
|
||||||
|
status: str
|
||||||
|
accept_size: List[int]
|
||||||
|
|
||||||
|
class LoadModelIn(BaseModel):
|
||||||
|
path: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
class ActivateModelIn(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
# ==== Service ====
|
||||||
class Service:
|
class Service:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.model = None
|
self.model = None
|
||||||
self.models={}
|
self.models = {}
|
||||||
self.active_model = "default"
|
self.active_model = "default"
|
||||||
|
|
||||||
def activate_model(self, name):
|
def activate_model(self, name):
|
||||||
|
|
||||||
self.model = self.models[name]
|
self.model = self.models[name]
|
||||||
self.active_model = name
|
self.active_model = name
|
||||||
return 0
|
return 0
|
||||||
@@ -26,15 +47,13 @@ class Service:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_accept_size(self):
|
def get_accept_size(self):
|
||||||
print(self.model.layers[0].input)
|
print(self.model.input_shape)
|
||||||
|
return list(self.model.input_shape) # (B,H,W,C)
|
||||||
return list(self.model.input_shape)
|
|
||||||
|
|
||||||
def get_output_size(self):
|
def get_output_size(self):
|
||||||
return list(self.model.layers[-1].output[0].shape[1:4])
|
return list(self.model.output_shape) # (B,H,W,C) or (B,H,W)
|
||||||
|
|
||||||
|
def predict(self, data: bytes) -> List[int]:
|
||||||
def predict(self, data: bytes):
|
|
||||||
target_size = self.get_accept_size()
|
target_size = self.get_accept_size()
|
||||||
image = np.reshape(
|
image = np.reshape(
|
||||||
np.frombuffer(data, dtype=np.uint8),
|
np.frombuffer(data, dtype=np.uint8),
|
||||||
@@ -48,21 +67,25 @@ class Service:
|
|||||||
else:
|
else:
|
||||||
classed = tf.argmax(predictions[0], axis=2)
|
classed = tf.argmax(predictions[0], axis=2)
|
||||||
|
|
||||||
return JSONResponse({"result": classed.numpy().reshape(-1).tolist()})
|
return classed.numpy().reshape(-1).tolist()
|
||||||
|
|
||||||
def predict_raw(self, data):
|
def predict_raw(self, data: bytes) -> np.ndarray:
|
||||||
target_size = self.get_accept_size()
|
target_size = self.get_accept_size()
|
||||||
image = np.reshape(np.frombuffer(data, dtype=np.uint8), (target_size[1], target_size[2], target_size[3])) / 255.
|
image = np.reshape(
|
||||||
|
np.frombuffer(data, dtype=np.uint8),
|
||||||
|
(target_size[1], target_size[2], target_size[3])
|
||||||
|
) / 255.0
|
||||||
|
|
||||||
predictions = self.model(np.array([image])).numpy()
|
predictions = self.model({"input_layer": np.array([image], dtype=np.float32)}).numpy()
|
||||||
|
|
||||||
if predictions[0].ndim==2:
|
if predictions[0].ndim == 2:
|
||||||
classed = predictions[0][:,:,np.newaxis].clip(0,255)
|
classed = predictions[0][:, :, np.newaxis].clip(0, 255)
|
||||||
return (classed).astype(np.uint8).reshape(-1).tolist()
|
return classed.astype(np.uint8).reshape(-1).tolist()
|
||||||
else:
|
else:
|
||||||
classed = predictions[0][:,:,:]
|
classed = predictions[0][:, :, :]
|
||||||
|
|
||||||
return (classed*255).astype(np.uint8).reshape(-1).tolist()
|
res = (classed * 255).astype(np.uint8).reshape(-1)
|
||||||
|
return np.asarray(res)
|
||||||
|
|
||||||
svc = Service()
|
svc = Service()
|
||||||
|
|
||||||
@@ -75,33 +98,61 @@ def _prep_raw_bytes_for_predict(buf: bytes) -> bytes:
|
|||||||
arr = np.asarray(img, dtype=np.uint8) # HxWx3 uint8
|
arr = np.asarray(img, dtype=np.uint8) # HxWx3 uint8
|
||||||
return arr.tobytes()
|
return arr.tobytes()
|
||||||
|
|
||||||
@app.post("/predict")
|
# ==== Routes ====
|
||||||
|
@app.post("/predict", response_model=bytes, summary="Predict (classified indices)")
|
||||||
async def predict(file: UploadFile = File(...)):
|
async def predict(file: UploadFile = File(...)):
|
||||||
buf = await file.read()
|
try:
|
||||||
raw = _prep_raw_bytes_for_predict(buf)
|
buf = await file.read()
|
||||||
return svc.predict(raw)
|
raw = _prep_raw_bytes_for_predict(buf)
|
||||||
|
result = svc.predict(raw)
|
||||||
|
return Response(content=result, media_type="application/octet-stream")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@app.post("/predict_raw")
|
@app.post("/predictRaw", response_model=bytes, summary="Predict (raw/uint8 flattened)")
|
||||||
async def predict_raw(file: UploadFile = File(...)):
|
async def predict_raw(file: UploadFile = File(...)):
|
||||||
buf = await file.read()
|
|
||||||
raw = _prep_raw_bytes_for_predict(buf)
|
|
||||||
return svc.predict_raw(raw)
|
|
||||||
|
|
||||||
@app.post("/load_model")
|
|
||||||
async def load_model(path: str = Body(...), name: str = Body(...)):
|
|
||||||
try:
|
try:
|
||||||
svc.load_model(path, name)
|
start_time = time.time()
|
||||||
return JSONResponse({"status": "success", "message": f"Model {name} loaded successfully."})
|
buf = await file.read()
|
||||||
|
result = svc.predict_raw(buf)
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
print(f"Prediction took {elapsed_time:.2f} seconds")
|
||||||
|
return Response(content=result.tobytes(), media_type="application/octet-stream")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse({"status": "error", "message": str(e)}, status_code=500)
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
@app.post("/activate_model")
|
@app.post("/loadModel", response_model=StatusOut, summary="Load a model from path")
|
||||||
async def activate_model(name: str = Body(...)):
|
async def load_model(payload: LoadModelIn):
|
||||||
try:
|
try:
|
||||||
svc.activate_model(name)
|
svc.load_model(payload.path, payload.name)
|
||||||
return JSONResponse({"status": "success", "message": f"Model {name} activated successfully."})
|
return StatusOut(status="success", message=f"Model {payload.name} loaded successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse({"status": "error", "message": str(e)}, status_code=500)
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.post("/activateModel", response_model=StatusOut, summary="Activate a loaded model")
|
||||||
|
async def activate_model(payload: ActivateModelIn):
|
||||||
|
try:
|
||||||
|
svc.activate_model(payload.name)
|
||||||
|
return StatusOut(status="success", message=f"Model {payload.name} activated successfully.")
|
||||||
|
except KeyError:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Model {payload.name} not found.")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/getAcceptSize", response_model=AcceptSizeOut, summary="Get input tensor shape")
|
||||||
|
async def get_accept_size():
|
||||||
|
try:
|
||||||
|
size = svc.get_accept_size()[1:] # Exclude batch size
|
||||||
|
return AcceptSizeOut(status="success", accept_size=size)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
@app.get("/getOutputSize", response_model=AcceptSizeOut, summary="Get output tensor shape")
|
||||||
|
async def get_output_size():
|
||||||
|
try:
|
||||||
|
size = svc.get_output_size()[1:]
|
||||||
|
return AcceptSizeOut(status="success", accept_size=size)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<OutputPath>.</OutputPath>
|
<OutputPath>.</OutputPath>
|
||||||
<Name>PythonModelAPI</Name>
|
<Name>PythonModelAPI</Name>
|
||||||
<RootNamespace>PythonModelAPI</RootNamespace>
|
<RootNamespace>PythonModelAPI</RootNamespace>
|
||||||
|
<PublishUrl>D:\Inspectron\Hawkeye\code\VisionBuilder5\VisionBuilder.UI\Hawkeye.VisionBuilder\bin\Debug\Data\Models</PublishUrl>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|||||||
BIN
PythonModelAPI/office4_tr.h5
Normal file
BIN
PythonModelAPI/office4_tr.h5
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
using OpenCvSharp;
|
using OpenCvSharp;
|
||||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||||
|
|
||||||
namespace VisionBuilder.UI.Common;
|
namespace VisionBuilder.UI.Common.NullClasses;
|
||||||
|
|
||||||
public class NoLearning:ILearningTool
|
public class NoLearning:ILearningTool
|
||||||
{
|
{
|
||||||
12
VisionBuilder.UI.Common/NullClasses/NoRecipeCreation.cs
Normal file
12
VisionBuilder.UI.Common/NullClasses/NoRecipeCreation.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||||
|
|
||||||
|
namespace VisionBuilder.UI.Common.NullClasses;
|
||||||
|
|
||||||
|
public class NoRecipeCreation: IRecipeCreationTool
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; } = false;
|
||||||
|
public void CreateRecipe(string recipeName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||||
|
|
||||||
|
public interface IRecipeCreationTool
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public void CreateRecipe(string recipeName);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Ninject;
|
using Ninject;
|
||||||
using Ninject.Extensions.ChildKernel;
|
using Ninject.Extensions.ChildKernel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using VisionBuilder.UI.Common.NullClasses;
|
||||||
using VisionBuilder.UI.Common.Plugins;
|
using VisionBuilder.UI.Common.Plugins;
|
||||||
using VisionBuilder.UI.Common.ViewModel;
|
using VisionBuilder.UI.Common.ViewModel;
|
||||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||||
@@ -20,6 +21,7 @@ public static class VisionBuilder
|
|||||||
mainKernel.Bind<InspectronSettings>().ToConstant(settings);
|
mainKernel.Bind<InspectronSettings>().ToConstant(settings);
|
||||||
mainKernel.Bind<UIConfiguration, ISettings>().ToConstant(new UIConfiguration());
|
mainKernel.Bind<UIConfiguration, ISettings>().ToConstant(new UIConfiguration());
|
||||||
mainKernel.Bind<ILearningTool>().To<NoLearning>().InSingletonScope();
|
mainKernel.Bind<ILearningTool>().To<NoLearning>().InSingletonScope();
|
||||||
|
mainKernel.Bind<IRecipeCreationTool>().To<NoRecipeCreation>().InSingletonScope();
|
||||||
// --- //
|
// --- //
|
||||||
|
|
||||||
mainKernel.Bind<MainWindowVM>().ToSelf().InSingletonScope();
|
mainKernel.Bind<MainWindowVM>().ToSelf().InSingletonScope();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
{
|
{
|
||||||
listView1 = new ListView();
|
listView1 = new ListView();
|
||||||
btnCancel = new MaterialSkin.Controls.MaterialRaisedButton();
|
btnCancel = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||||
|
btnCreateNewRecipe = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// listView1
|
// listView1
|
||||||
@@ -62,11 +63,30 @@
|
|||||||
btnCancel.UseVisualStyleBackColor = true;
|
btnCancel.UseVisualStyleBackColor = true;
|
||||||
btnCancel.Click += btnCancel_Click;
|
btnCancel.Click += btnCancel_Click;
|
||||||
//
|
//
|
||||||
|
// btnCreateNewRecipe
|
||||||
|
//
|
||||||
|
btnCreateNewRecipe.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||||
|
btnCreateNewRecipe.Depth = 0;
|
||||||
|
btnCreateNewRecipe.DrawBorder = true;
|
||||||
|
btnCreateNewRecipe.Font = new Font("Segoe UI Semibold", 12F, FontStyle.Bold, GraphicsUnit.Point, 204);
|
||||||
|
btnCreateNewRecipe.Icon = null;
|
||||||
|
btnCreateNewRecipe.Location = new Point(8, 624);
|
||||||
|
btnCreateNewRecipe.MouseState = MaterialSkin.MouseState.HOVER;
|
||||||
|
btnCreateNewRecipe.Name = "btnCreateNewRecipe";
|
||||||
|
btnCreateNewRecipe.Primary = false;
|
||||||
|
btnCreateNewRecipe.Size = new Size(160, 48);
|
||||||
|
btnCreateNewRecipe.TabIndex = 6;
|
||||||
|
btnCreateNewRecipe.Text = "Create New Recipe";
|
||||||
|
btnCreateNewRecipe.UseVisualStyleBackColor = true;
|
||||||
|
btnCreateNewRecipe.Visible = false;
|
||||||
|
btnCreateNewRecipe.Click += btnCreateNewRecipe_Click;
|
||||||
|
//
|
||||||
// RecipeSelectionDialog
|
// RecipeSelectionDialog
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(962, 688);
|
ClientSize = new Size(962, 688);
|
||||||
|
Controls.Add(btnCreateNewRecipe);
|
||||||
Controls.Add(btnCancel);
|
Controls.Add(btnCancel);
|
||||||
Controls.Add(listView1);
|
Controls.Add(listView1);
|
||||||
MaximizeBox = false;
|
MaximizeBox = false;
|
||||||
@@ -81,5 +101,6 @@
|
|||||||
|
|
||||||
private ListView listView1;
|
private ListView listView1;
|
||||||
private MaterialSkin.Controls.MaterialRaisedButton btnCancel;
|
private MaterialSkin.Controls.MaterialRaisedButton btnCancel;
|
||||||
|
private MaterialSkin.Controls.MaterialRaisedButton btnCreateNewRecipe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,31 @@
|
|||||||
using MaterialSkin.Controls;
|
using MaterialSkin.Controls;
|
||||||
using OpenCvSharp.XImgProc;
|
using OpenCvSharp.XImgProc;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using MaterialSkin.Core.Controls;
|
||||||
using OpenCvSharp.Extensions;
|
using OpenCvSharp.Extensions;
|
||||||
using VisionBuilder.UI.Common.ViewModel;
|
using VisionBuilder.UI.Common.ViewModel;
|
||||||
using VisionBuilder.UI.Common.ViewModel.Classes;
|
using VisionBuilder.UI.Common.ViewModel.Classes;
|
||||||
|
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||||
|
|
||||||
namespace VisionBuilder.UI.Windows.Dialogs
|
namespace VisionBuilder.UI.Windows.Dialogs
|
||||||
{
|
{
|
||||||
public partial class RecipeSelectionDialog : MaterialForm
|
public partial class RecipeSelectionDialog : MaterialForm
|
||||||
{
|
{
|
||||||
private readonly RecipeSelectionVM _recipeSelectionVm;
|
private readonly RecipeSelectionVM _recipeSelectionVm;
|
||||||
|
private readonly IRecipeCreationTool _recipeCreationTool;
|
||||||
|
|
||||||
public RecipeSelectionDialog(RecipeSelectionVM recipeSelectionVm)
|
public RecipeSelectionDialog(RecipeSelectionVM recipeSelectionVm, IRecipeCreationTool recipeCreationTool)
|
||||||
{
|
{
|
||||||
_recipeSelectionVm = recipeSelectionVm;
|
_recipeSelectionVm = recipeSelectionVm;
|
||||||
|
_recipeCreationTool = recipeCreationTool;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ReloadRecipes();
|
ReloadRecipes();
|
||||||
|
|
||||||
|
if (!_recipeCreationTool.Enabled)
|
||||||
|
{
|
||||||
|
btnCreateNewRecipe.Visible = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadRecipes()
|
public void ReloadRecipes()
|
||||||
@@ -53,7 +62,7 @@ namespace VisionBuilder.UI.Windows.Dialogs
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
listView1.Items.Add(x.RecipeName, x.RecipeName, "plug").Tag=x;
|
listView1.Items.Add(x.RecipeName, x.RecipeName, "plug").Tag = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -63,7 +72,7 @@ namespace VisionBuilder.UI.Windows.Dialogs
|
|||||||
{
|
{
|
||||||
if (listView1.SelectedIndices.Count > 0)
|
if (listView1.SelectedIndices.Count > 0)
|
||||||
{
|
{
|
||||||
_recipeSelectionVm.SelectedRecipe = (RecipeData)listView1.SelectedItems[0].Tag!;
|
_recipeSelectionVm.SelectedRecipe = (RecipeData)listView1.SelectedItems[0].Tag!;
|
||||||
}
|
}
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
@@ -73,5 +82,13 @@ namespace VisionBuilder.UI.Windows.Dialogs
|
|||||||
{
|
{
|
||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void btnCreateNewRecipe_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (MaterialInputBox.Prompt("Create new recipe", "Recipe name", out var recipeName)==DialogResult.OK)
|
||||||
|
{
|
||||||
|
_recipeCreationTool.CreateRecipe(recipeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,13 +222,13 @@ Global
|
|||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{B62D435C-3572-4B5B-A381-992345D343B0} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
|
||||||
{D5FD2E9D-DA4F-1343-47E0-FBC473A149BC} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
{D5FD2E9D-DA4F-1343-47E0-FBC473A149BC} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||||
{7697A266-A721-4D74-9C5C-0D4F2F6BBF68} = {F2406FBB-DFD3-4CBE-9644-A9FFC2FCBB71}
|
{7697A266-A721-4D74-9C5C-0D4F2F6BBF68} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
||||||
{E286CE4C-B68A-94B6-F477-0DBF42358009} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
{E286CE4C-B68A-94B6-F477-0DBF42358009} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||||
{E8868ABD-E4D0-1B7E-494E-06FB1F7D1AF5} = {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}
|
{8B7FAFEE-4066-483C-9C9C-10D2D596206D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{FC4698F6-C653-427F-9A42-22D07D466A14} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{FC4698F6-C653-427F-9A42-22D07D466A14} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
|
{8F6BA34A-D070-372B-DD72-B74905460744} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
||||||
{4F937F88-70B7-A9E4-834A-D60B104686E0} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
{4F937F88-70B7-A9E4-834A-D60B104686E0} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||||
{0DAFEB64-E123-0974-B77C-3518AFD28D87} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
{0DAFEB64-E123-0974-B77C-3518AFD28D87} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||||
{7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82} = {D05689E3-04C6-4E3B-ACA7-3F4507CED4CC}
|
{7AACFEF4-794A-42A3-9A05-9B6B4ECD5B82} = {D05689E3-04C6-4E3B-ACA7-3F4507CED4CC}
|
||||||
|
|||||||
Reference in New Issue
Block a user