wsl support

This commit is contained in:
meelstorm
2025-08-28 16:30:29 +02:00
parent 07002dd875
commit bf13a5ea2d
4 changed files with 169 additions and 45 deletions

View File

@@ -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()
@@ -50,6 +46,8 @@ public class CSharpDataTransferHTTP
}
public void LoadModel(string path, string modelName)
{
lock (_lock)
{
_api.LoadModelAsync(new LoadModelIn()
{
@@ -57,32 +55,32 @@ public class CSharpDataTransferHTTP
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);
@@ -91,17 +89,16 @@ public class CSharpDataTransferHTTP
}
public byte[] Predict(byte[] byteArray)
{
lock (_lock)
{
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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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()
{
lock (_lock)
{
if (_isInitialized) return;
_isInitialized = true;
var workDirAbsPath = Path.GetFullPath(@"..\Data\Models");
_pythonScriptRunner = new UvicornScriptRunner(workDirAbsPath);
_cSharpDataTransferHTTP = new CSharpDataTransferHTTP();
_pythonScriptRunner = new DockerScriptRunner(workDirAbsPath);
_pythonScriptRunner.Start();
Thread.Sleep(1000); // Wait for the server to 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()

View File

@@ -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));