diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs index 7cc6fe4..cba85dd 100644 --- a/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs +++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs @@ -8,6 +8,7 @@ public class CSharpDataTransferHTTP private readonly HttpClient _client; private readonly CancellationTokenSource _pingCts = new(); private readonly Thread _pingThread; + private readonly object _lock = new(); public CSharpDataTransferHTTP() { @@ -15,22 +16,17 @@ public class CSharpDataTransferHTTP { PooledConnectionIdleTimeout = TimeSpan.FromSeconds(10), MaxConnectionsPerServer = 100 - - - }; _client = new HttpClient(handler) { - // i've added this DefaultRequestHeaders = { ExpectContinue = false } }; - _api = new PythonModelAPI("http://localhost:8000",_client); + _api = new PythonModelAPI("http://localhost:8000", _client); _pingThread = new Thread(PingLoop) { IsBackground = true }; _pingThread.Start(); - } private void PingLoop() @@ -51,38 +47,40 @@ public class CSharpDataTransferHTTP public void LoadModel(string path, string modelName) { - _api.LoadModelAsync(new LoadModelIn() + lock (_lock) { - Name = modelName, - Path = Path.GetFileName(path) - }).Wait(); + _api.LoadModelAsync(new LoadModelIn() + { + Name = modelName, + Path = Path.GetFileName(path) + }).Wait(); + } } public void ActivateModel(string modelName) { - _api.ActivateModelAsync(new ActivateModelIn(){Name = modelName}).Wait(); + lock (_lock) + { + _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); @@ -92,16 +90,15 @@ public class CSharpDataTransferHTTP 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; - + lock (_lock) + { + var response = _api.PredictAsync(new FileParameter(new MemoryStream(byteArray))).Result; + var stream = response.Stream; + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + var responseBytes = memoryStream.ToArray(); + return responseBytes; + } } - - } \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/DockerScriptRunner.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/DockerScriptRunner.cs new file mode 100644 index 0000000..c927484 --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/DockerScriptRunner.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; +using static IronPython.Modules._ast; + +namespace Hawkeye.VisionBuilder.Workflow.DataTransfer; + +public class DockerScriptRunner +{ + private Process process; + private readonly string _workingDirectory; + + public DockerScriptRunner(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() + { + + var killContainerArg = @"-d ubuntu -u root -e sh -c ""docker rm -f cv || true"""; + var killProcess = new Process + { + StartInfo = new ProcessStartInfo("wsl", killContainerArg) + { + UseShellExecute = false + } + }; + killProcess.Start(); + killProcess.WaitForExit(); + killProcess.Dispose(); + + //startInfo.CreateNoWindow = true; + // create and start the process + process = new Process(); + var convertedWd = ConvertToWslPath(_workingDirectory); + var arg = + $@"-d ubuntu -u root -e sh -c ""cd \""{convertedWd}\"" && docker run --rm --name cv --gpus all -it -p 8000:8000 -v \""$(pwd)\"":/app -w /app cv uvicorn --host 0.0.0.0 PythonModelAPI:app """; + + Console.WriteLine("args:"+arg); + process.StartInfo = new ProcessStartInfo("wsl", arg) + { + UseShellExecute = false, + CreateNoWindow = true + }; + 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; + } + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs index 3fb2108..31bdf94 100644 --- a/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs +++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs @@ -1,21 +1,53 @@ -namespace Hawkeye.VisionBuilder.Workflow.DataTransfer; +using Serilog; + +namespace Hawkeye.VisionBuilder.Workflow.DataTransfer; public class PythonModelProxyHTTP { private static bool _isInitialized = false; - private static UvicornScriptRunner _pythonScriptRunner; + private static DockerScriptRunner _pythonScriptRunner; private static CSharpDataTransferHTTP _cSharpDataTransferHTTP; + static object _lock = new object(); + public static void Initialize() { - if (_isInitialized) return; + lock (_lock) + { + if (_isInitialized) return; + _isInitialized = true; - var workDirAbsPath = Path.GetFullPath(@"..\Data\Models"); - _pythonScriptRunner = new UvicornScriptRunner(workDirAbsPath); - _cSharpDataTransferHTTP = new CSharpDataTransferHTTP(); + var workDirAbsPath = Path.GetFullPath(@"..\Data\Models"); + _pythonScriptRunner = new DockerScriptRunner(workDirAbsPath); - _pythonScriptRunner.Start(); - Thread.Sleep(1000); // Wait for the server to start + + _pythonScriptRunner.Start(); + // Wait for the server to start by checking if port 8000 is open + while (true) + { + try + { + HttpClient http = new HttpClient(); + // check if 404 occurs on localhost:8000 + var response = http.GetAsync("http://127.0.0.1:8000/").Result; + if (response.StatusCode == System.Net.HttpStatusCode.NotFound || + response.StatusCode == System.Net.HttpStatusCode.OK) + { + break; // Port is open + } + + } + catch + { + Log.Debug("Waiting for port to open"); + // Ignore exceptions and retry + } + + Thread.Sleep(1000); // Wait before retrying + } + Thread.Sleep(2000); + _cSharpDataTransferHTTP = new CSharpDataTransferHTTP(); + } } public static CSharpDataTransferHTTP GetInterface() diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs b/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs index 27a7ecd..a0aa1da 100644 --- a/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs +++ b/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs @@ -16,7 +16,7 @@ public class RawModelAIOperation: BaseOperation public RawModelAIOperation() { - _cSharpDataTransferMQRPC = PythonModelProxyHTTP.GetInterface(); + CanHaveProcessingError = false; } @@ -46,6 +46,7 @@ public class RawModelAIOperation: BaseOperation protected override void InterpretInternal(Context context) { + _cSharpDataTransferMQRPC = PythonModelProxyHTTP.GetInterface(); if (!CheckImageExists(context)) return; if (!CheckColorful(context)) return; @@ -53,7 +54,7 @@ public class RawModelAIOperation: BaseOperation { if (!File.Exists(ModelFilePath?.Path)) { - this.SetError("Model file not found"); + this.SetError($"Model file \"{ModelFilePath?.Path}\" not found"); return; } @@ -74,31 +75,47 @@ public class RawModelAIOperation: BaseOperation _cSharpDataTransferMQRPC.ActivateModel(_modelName); - var sizeEncoded = _cSharpDataTransferMQRPC.GetAcceptSize(); - var size = sizeEncoded.Select(Convert.ToInt32).ToArray(); - _imageWidth = size[0]; - _imageHeight = size[1]; + int[] sizeEncoded=[]; + int[] outputSize = []; + sizeEncoded = _cSharpDataTransferMQRPC.GetAcceptSize(); + outputSize = _cSharpDataTransferMQRPC.GetOutputSize(); - var outputSize = _cSharpDataTransferMQRPC.GetOutputSize(); - var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray(); + + + + + + + + + + var currentImage = context.ActiveImage; var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB); + + + var size = sizeEncoded.Select(Convert.ToInt32).ToArray(); + _imageWidth = size[0]; + _imageHeight = size[1]; + var resized = rightColor.Resize(new Size(_imageHeight, _imageWidth)); + + + int dataSize = resized.Rows * resized.Cols * resized.ElemSize(); var byteArray = new byte[dataSize]; Marshal.Copy(resized.Data, byteArray, 0, dataSize); - var allowed = FilterClasses.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray(); - var result = _cSharpDataTransferMQRPC.PredictRaw(byteArray); byte[] resultColor; resultColor = result.Select(x => (byte)(x) ).ToArray(); - // black and white mask + + var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray(); var mask = new Mat(outputSizeArray[0], outputSizeArray[1], MatType.CV_8UC1, resultColor); var maskResized = mask.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));