diff --git a/Hawkeye.VisionBuilder.Workflow/API/PythonAPI.cs b/Hawkeye.VisionBuilder.Workflow/API/PythonAPI.cs new file mode 100644 index 0000000..40ea910 --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/API/PythonAPI.cs @@ -0,0 +1,1092 @@ +//---------------------- +// +// Generated using the NSwag toolchain v14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 612 // Disable "CS0612 '...' is obsolete" +#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null" +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" +#pragma warning disable 8600 // Disable "CS8600 Converting null literal or possible null value to non-nullable type" +#pragma warning disable 8602 // Disable "CS8602 Dereference of a possibly null reference" +#pragma warning disable 8603 // Disable "CS8603 Possible null reference return" +#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter" +#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type" +#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)." + +namespace Hawkeye.VisionBuilder.Workflow +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class PythonModelAPI + { + #pragma warning disable 8618 + private string _baseUrl; + #pragma warning restore 8618 + + private System.Net.Http.HttpClient _httpClient; + private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); + private Newtonsoft.Json.JsonSerializerSettings _instanceSettings; + + #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public PythonModelAPI(string baseUrl, System.Net.Http.HttpClient httpClient) + #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + BaseUrl = baseUrl; + _httpClient = httpClient; + Initialize(); + } + + private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + public string BaseUrl + { + get { return _baseUrl; } + set + { + _baseUrl = value; + if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/")) + _baseUrl += '/'; + } + } + + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } } + + static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + + partial void Initialize(); + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// + /// Predict (classified indices) + /// + /// Successful Response + /// A server side error occurred. + public virtual System.Threading.Tasks.Task PredictAsync(FileParameter file) + { + return PredictAsync(file, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Predict (classified indices) + /// + /// Successful Response + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task PredictAsync(FileParameter file, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var boundary_ = System.Guid.NewGuid().ToString(); + var content_ = new System.Net.Http.MultipartFormDataContent(boundary_); + content_.Headers.Remove("Content-Type"); + content_.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary_); + + if (file == null) + throw new System.ArgumentNullException("file"); + else + { + var content_file_ = new System.Net.Http.StreamContent(file.Data); + if (!string.IsNullOrEmpty(file.ContentType)) + content_file_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(file.ContentType); + content_.Add(content_file_, "file", file.FileName ?? "file"); + } + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "predict" + urlBuilder_.Append("predict"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200 || status_ == 206) + { + var responseStream_ = response_.Content == null ? System.IO.Stream.Null : await ReadAsStreamAsync(response_.Content, cancellationToken).ConfigureAwait(false); + var fileResponse_ = new FileResponse(status_, headers_, responseStream_, null, response_); + disposeClient_ = false; disposeResponse_ = false; // response and client are disposed by FileResponse + return fileResponse_; + } + else + if (status_ == 422) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Validation Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Predict (raw/uint8 flattened) + /// + /// Successful Response + /// A server side error occurred. + public virtual System.Threading.Tasks.Task PredictRawAsync(FileParameter file) + { + return PredictRawAsync(file, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Predict (raw/uint8 flattened) + /// + /// Successful Response + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task PredictRawAsync(FileParameter file, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var boundary_ = System.Guid.NewGuid().ToString(); + var content_ = new System.Net.Http.MultipartFormDataContent(boundary_); + content_.Headers.Remove("Content-Type"); + content_.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary_); + + if (file == null) + throw new System.ArgumentNullException("file"); + else + { + var content_file_ = new System.Net.Http.StreamContent(file.Data); + if (!string.IsNullOrEmpty(file.ContentType)) + content_file_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(file.ContentType); + content_.Add(content_file_, "file", file.FileName ?? "file"); + } + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "predictRaw" + urlBuilder_.Append("predictRaw"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200 || status_ == 206) + { + var responseStream_ = response_.Content == null ? System.IO.Stream.Null : await ReadAsStreamAsync(response_.Content, cancellationToken).ConfigureAwait(false); + var fileResponse_ = new FileResponse(status_, headers_, responseStream_, null, response_); + disposeClient_ = false; disposeResponse_ = false; // response and client are disposed by FileResponse + return fileResponse_; + } + else + if (status_ == 422) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Validation Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Load a model from path + /// + /// Successful Response + /// A server side error occurred. + public virtual System.Threading.Tasks.Task LoadModelAsync(LoadModelIn body) + { + return LoadModelAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Load a model from path + /// + /// Successful Response + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task LoadModelAsync(LoadModelIn body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "loadModel" + urlBuilder_.Append("loadModel"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 422) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Validation Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Activate a loaded model + /// + /// Successful Response + /// A server side error occurred. + public virtual System.Threading.Tasks.Task ActivateModelAsync(ActivateModelIn body) + { + return ActivateModelAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Activate a loaded model + /// + /// Successful Response + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task ActivateModelAsync(ActivateModelIn body, System.Threading.CancellationToken cancellationToken) + { + if (body == null) + throw new System.ArgumentNullException("body"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "activateModel" + urlBuilder_.Append("activateModel"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 422) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Validation Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get input tensor shape + /// + /// Successful Response + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetAcceptSizeAsync() + { + return GetAcceptSizeAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get input tensor shape + /// + /// Successful Response + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetAcceptSizeAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "getAcceptSize" + urlBuilder_.Append("getAcceptSize"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get output tensor shape + /// + /// Successful Response + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetOutputSizeAsync() + { + return GetOutputSizeAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get output tensor shape + /// + /// Successful Response + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetOutputSizeAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "getOutputSize" + urlBuilder_.Append("getOutputSize"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static System.Threading.Tasks.Task ReadAsStringAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) + { + #if NET5_0_OR_GREATER + return content.ReadAsStringAsync(cancellationToken); + #else + return content.ReadAsStringAsync(); + #endif + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static System.Threading.Tasks.Task ReadAsStreamAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) + { + #if NET5_0_OR_GREATER + return content.ReadAsStreamAsync(cancellationToken); + #else + return content.ReadAsStreamAsync(); + #endif + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await ReadAsStringAsync(response.Content, cancellationToken).ConfigureAwait(false); + try + { + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await ReadAsStreamAsync(response.Content, cancellationToken).ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) + { + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value is string[]) + { + return string.Join(",", (string[])value); + } + else if (value.GetType().IsArray) + { + var valueArray = (System.Array)value; + var valueTextArray = new string[valueArray.Length]; + for (var i = 0; i < valueArray.Length; i++) + { + valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo); + } + return string.Join(",", valueTextArray); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class AcceptSizeOut + { + + [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Status { get; set; } + + [Newtonsoft.Json.JsonProperty("accept_size", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Accept_size { get; set; } = new System.Collections.ObjectModel.Collection(); + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ActivateModelIn + { + + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class Body_predict_predict_post + { + + [Newtonsoft.Json.JsonProperty("file", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public byte[] File { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class Body_predict_raw_predictRaw_post + { + + [Newtonsoft.Json.JsonProperty("file", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public byte[] File { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class HTTPValidationError + { + + [Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.Generic.ICollection Detail { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class LoadModelIn + { + + [Newtonsoft.Json.JsonProperty("path", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Path { get; set; } + + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Name { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class StatusOut + { + + [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Status { get; set; } + + [Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Message { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ValidationError + { + + [Newtonsoft.Json.JsonProperty("loc", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required] + public System.Collections.Generic.ICollection Loc { get; set; } = new System.Collections.ObjectModel.Collection(); + + [Newtonsoft.Json.JsonProperty("msg", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Msg { get; set; } + + [Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.Always)] + [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] + public string Type { get; set; } + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class Loc + { + + private System.Collections.Generic.IDictionary _additionalProperties; + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary AdditionalProperties + { + get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); } + set { _additionalProperties = value; } + } + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class FileParameter + { + public FileParameter(System.IO.Stream data) + : this (data, null, null) + { + } + + public FileParameter(System.IO.Stream data, string fileName) + : this (data, fileName, null) + { + } + + public FileParameter(System.IO.Stream data, string fileName, string contentType) + { + Data = data; + FileName = fileName; + ContentType = contentType; + } + + public System.IO.Stream Data { get; private set; } + + public string FileName { get; private set; } + + public string ContentType { get; private set; } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class FileResponse : System.IDisposable + { + private System.IDisposable _client; + private System.IDisposable _response; + + public int StatusCode { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public System.IO.Stream Stream { get; private set; } + + public bool IsPartial + { + get { return StatusCode == 206; } + } + + public FileResponse(int statusCode, System.Collections.Generic.IReadOnlyDictionary> headers, System.IO.Stream stream, System.IDisposable client, System.IDisposable response) + { + StatusCode = statusCode; + Headers = headers; + Stream = stream; + _client = client; + _response = response; + } + + public void Dispose() + { + Stream.Dispose(); + if (_response != null) + _response.Dispose(); + if (_client != null) + _client.Dispose(); + } + } + + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.5.0.0 (NJsonSchema v11.4.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 108 +#pragma warning restore 114 +#pragma warning restore 472 +#pragma warning restore 612 +#pragma warning restore 649 +#pragma warning restore 1573 +#pragma warning restore 1591 +#pragma warning restore 8073 +#pragma warning restore 3016 +#pragma warning restore 8600 +#pragma warning restore 8602 +#pragma warning restore 8603 +#pragma warning restore 8604 +#pragma warning restore 8625 +#pragma warning restore 8765 \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/API/generator.nswag b/Hawkeye.VisionBuilder.Workflow/API/generator.nswag new file mode 100644 index 0000000..2bb3122 --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/API/generator.nswag @@ -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" + } + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs new file mode 100644 index 0000000..7cc6fe4 --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferHTTP.cs @@ -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; + + } + + +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs index 6d73f0d..3fb2108 100644 --- a/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs +++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/PythonModelProxyHTTP.cs @@ -2,5 +2,26 @@ 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; + } } \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/UvicornScriptRunner.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/UvicornScriptRunner.cs new file mode 100644 index 0000000..e9b0b21 --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/UvicornScriptRunner.cs @@ -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; + } + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/Hawkeye.VisionBuilder.Workflow.csproj b/Hawkeye.VisionBuilder.Workflow/Hawkeye.VisionBuilder.Workflow.csproj index d3e6325..80b6571 100644 --- a/Hawkeye.VisionBuilder.Workflow/Hawkeye.VisionBuilder.Workflow.csproj +++ b/Hawkeye.VisionBuilder.Workflow/Hawkeye.VisionBuilder.Workflow.csproj @@ -9,13 +9,29 @@ Debug;Release;CPU + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/AI/ColorAIOperation.cs b/Hawkeye.VisionBuilder.Workflow/Operations/AI/ColorAIOperation.cs index 5a17478..67914b6 100644 --- a/Hawkeye.VisionBuilder.Workflow/Operations/AI/ColorAIOperation.cs +++ b/Hawkeye.VisionBuilder.Workflow/Operations/AI/ColorAIOperation.cs @@ -14,11 +14,11 @@ public abstract class ColorAIOperation:BaseOperation public ColorAIOperation(string model_name) { _modelName = model_name; - _cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface(); + _cSharpDataTransferMQRPC = PythonModelProxyHTTP.GetInterface(); CanHaveProcessingError = false; } - private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC; + private readonly CSharpDataTransferHTTP _cSharpDataTransferMQRPC; private int _imageWidth; private int _imageHeight; @@ -52,15 +52,16 @@ public abstract class ColorAIOperation:BaseOperation this.SetError("Model file not found"); return; } - _cSharpDataTransferMQRPC.TransferData(new MethodCall("load_model", ModelFilePath.Path, _modelName)); + + _cSharpDataTransferMQRPC.LoadModel(ModelFilePath.Path, _modelName); _initialized = true; } + + _cSharpDataTransferMQRPC.ActivateModel(_modelName); + + var sizeEncoded = _cSharpDataTransferMQRPC.GetAcceptSize(); - - _cSharpDataTransferMQRPC.TransferData(new MethodCall("activate_model", _modelName)); - - var sizeEncoded = _cSharpDataTransferMQRPC.TransferData(new MethodCall("get_accept_size")); var size = sizeEncoded.Select(Convert.ToInt32).ToArray(); _imageWidth = size[1]; _imageHeight = size[2]; @@ -82,22 +83,30 @@ public abstract class ColorAIOperation:BaseOperation allowedFlags[i] = 1; } - var resultEncoded = _cSharpDataTransferMQRPC.TransferData(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; + if (IsRaw) { resultColor = result.Chunk(25).Select(x => { return x.Select((x, i) => allowedFlags[i] == 1 ? (byte) (x) : (byte) 0).Max(); }).ToArray(); - //resultColor = result.Select(x => (byte)(x)).ToArray(); } 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(); } diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs b/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs index 7067464..27a7ecd 100644 --- a/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs +++ b/Hawkeye.VisionBuilder.Workflow/Operations/AI/RawModelAIOperation.cs @@ -16,11 +16,11 @@ public class RawModelAIOperation: BaseOperation public RawModelAIOperation() { - _cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface(); + _cSharpDataTransferMQRPC = PythonModelProxyHTTP.GetInterface(); CanHaveProcessingError = false; } - private CSharpDataTransferMQRPC _cSharpDataTransferMQRPC; + private CSharpDataTransferHTTP _cSharpDataTransferMQRPC; private int _imageWidth; private int _imageHeight; @@ -66,34 +66,22 @@ public class RawModelAIOperation: BaseOperation var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path)); ModelFilePath.Path = absPath; } - - _cSharpDataTransferMQRPC.TransferData(new MethodCall("load_model", ModelFilePath.Path, _modelName)); + _cSharpDataTransferMQRPC.LoadModel(ModelFilePath.Path, _modelName); _initialized = true; } + _cSharpDataTransferMQRPC.ActivateModel(_modelName); - _cSharpDataTransferMQRPC.TransferData(new MethodCall("activate_model", _modelName)); - var sizeEncoded = _cSharpDataTransferMQRPC.TransferData(new MethodCall("get_accept_size")); + var sizeEncoded = _cSharpDataTransferMQRPC.GetAcceptSize(); var size = sizeEncoded.Select(Convert.ToInt32).ToArray(); - _imageWidth = size[1]; - _imageHeight = size[2]; + _imageWidth = size[0]; + _imageHeight = size[1]; - var outputSize = _cSharpDataTransferMQRPC.TransferData(new MethodCall("get_output_size")); + var outputSize = _cSharpDataTransferMQRPC.GetOutputSize(); var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray(); - int classes; - if (outputSizeArray.Length<3) - { - classes=1; - } - else - { - classes = outputSizeArray[2]; - } - - 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 allowedFlags = new byte[128]; - foreach (var i in allowed) - { - allowedFlags[i] = 1; - } - - var resultEncoded = _cSharpDataTransferMQRPC.TransferData(new MethodCall("predict_raw", byteArray)); - - var result = resultEncoded.Select(Convert.ToByte).ToArray(); + var result = _cSharpDataTransferMQRPC.PredictRaw(byteArray); byte[] resultColor; resultColor = result.Select(x => (byte)(x) ).ToArray(); - // black and white mask var mask = new Mat(outputSizeArray[0], outputSizeArray[1], MatType.CV_8UC1, resultColor); diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/AI/YoloDetectionOperation.cs b/Hawkeye.VisionBuilder.Workflow/Operations/AI/YoloDetectionOperation.cs index c107ed2..5c6ba9d 100644 --- a/Hawkeye.VisionBuilder.Workflow/Operations/AI/YoloDetectionOperation.cs +++ b/Hawkeye.VisionBuilder.Workflow/Operations/AI/YoloDetectionOperation.cs @@ -59,6 +59,7 @@ public class YoloDetectionOperation:BaseOperation } protected override void InterpretInternal(Context context) { + CheckImageExists(context); CheckColorful(context); try { diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/Scripts/CSharpProcessOperation.cs b/Hawkeye.VisionBuilder.Workflow/Operations/Scripts/CSharpProcessOperation.cs new file mode 100644 index 0000000..91f812c --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/Operations/Scripts/CSharpProcessOperation.cs @@ -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 + }; + + } +} \ No newline at end of file diff --git a/Hawkeye.VisionBuilder.Workflow/Operations/Scripts/PluginLoadContext.cs b/Hawkeye.VisionBuilder.Workflow/Operations/Scripts/PluginLoadContext.cs new file mode 100644 index 0000000..375a019 --- /dev/null +++ b/Hawkeye.VisionBuilder.Workflow/Operations/Scripts/PluginLoadContext.cs @@ -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; + } + +} \ No newline at end of file diff --git a/PythonModelAPI/PythonModelAPI.py b/PythonModelAPI/PythonModelAPI.py index eb6a9b5..94c8e34 100644 --- a/PythonModelAPI/PythonModelAPI.py +++ b/PythonModelAPI/PythonModelAPI.py @@ -1,22 +1,43 @@ - # 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 import numpy as np from PIL import Image import io import tensorflow as tf - +import time 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: def __init__(self): self.model = None - self.models={} + self.models = {} self.active_model = "default" def activate_model(self, name): - self.model = self.models[name] self.active_model = name return 0 @@ -26,15 +47,13 @@ class Service: return 0 def get_accept_size(self): - print(self.model.layers[0].input) - - return list(self.model.input_shape) + print(self.model.input_shape) + return list(self.model.input_shape) # (B,H,W,C) 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): + def predict(self, data: bytes) -> List[int]: target_size = self.get_accept_size() image = np.reshape( np.frombuffer(data, dtype=np.uint8), @@ -48,21 +67,25 @@ class Service: else: 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() - 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: - classed = predictions[0][:,:,np.newaxis].clip(0,255) - return (classed).astype(np.uint8).reshape(-1).tolist() + if predictions[0].ndim == 2: + classed = predictions[0][:, :, np.newaxis].clip(0, 255) + return classed.astype(np.uint8).reshape(-1).tolist() 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() @@ -75,33 +98,61 @@ def _prep_raw_bytes_for_predict(buf: bytes) -> bytes: arr = np.asarray(img, dtype=np.uint8) # HxWx3 uint8 return arr.tobytes() -@app.post("/predict") +# ==== Routes ==== +@app.post("/predict", response_model=bytes, summary="Predict (classified indices)") async def predict(file: UploadFile = File(...)): - buf = await file.read() - raw = _prep_raw_bytes_for_predict(buf) - return svc.predict(raw) + try: + buf = await file.read() + 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(...)): - 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: - svc.load_model(path, name) - return JSONResponse({"status": "success", "message": f"Model {name} loaded successfully."}) + start_time = time.time() + 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: - return JSONResponse({"status": "error", "message": str(e)}, status_code=500) + raise HTTPException(status_code=400, detail=str(e)) -@app.post("/activate_model") -async def activate_model(name: str = Body(...)): +@app.post("/loadModel", response_model=StatusOut, summary="Load a model from path") +async def load_model(payload: LoadModelIn): try: - svc.activate_model(name) - return JSONResponse({"status": "success", "message": f"Model {name} activated successfully."}) + svc.load_model(payload.path, payload.name) + return StatusOut(status="success", message=f"Model {payload.name} loaded successfully.") 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)) diff --git a/PythonModelAPI/PythonModelAPI.pyproj b/PythonModelAPI/PythonModelAPI.pyproj index a496d96..e2d8c4e 100644 --- a/PythonModelAPI/PythonModelAPI.pyproj +++ b/PythonModelAPI/PythonModelAPI.pyproj @@ -11,6 +11,7 @@ . PythonModelAPI PythonModelAPI + D:\Inspectron\Hawkeye\code\VisionBuilder5\VisionBuilder.UI\Hawkeye.VisionBuilder\bin\Debug\Data\Models true diff --git a/PythonModelAPI/office4_tr.h5 b/PythonModelAPI/office4_tr.h5 new file mode 100644 index 0000000..27f45ce Binary files /dev/null and b/PythonModelAPI/office4_tr.h5 differ diff --git a/VisionBuilder.UI.Common/NoLearning.cs b/VisionBuilder.UI.Common/NullClasses/NoLearning.cs similarity index 87% rename from VisionBuilder.UI.Common/NoLearning.cs rename to VisionBuilder.UI.Common/NullClasses/NoLearning.cs index 8f8a065..122052f 100644 --- a/VisionBuilder.UI.Common/NoLearning.cs +++ b/VisionBuilder.UI.Common/NullClasses/NoLearning.cs @@ -1,7 +1,7 @@ using OpenCvSharp; using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; -namespace VisionBuilder.UI.Common; +namespace VisionBuilder.UI.Common.NullClasses; public class NoLearning:ILearningTool { diff --git a/VisionBuilder.UI.Common/NullClasses/NoRecipeCreation.cs b/VisionBuilder.UI.Common/NullClasses/NoRecipeCreation.cs new file mode 100644 index 0000000..a01a398 --- /dev/null +++ b/VisionBuilder.UI.Common/NullClasses/NoRecipeCreation.cs @@ -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(); + } +} \ No newline at end of file diff --git a/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeCreationTool.cs b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeCreationTool.cs new file mode 100644 index 0000000..d86a06c --- /dev/null +++ b/VisionBuilder.UI.Common/ViewModel/Interfaces/UI/IRecipeCreationTool.cs @@ -0,0 +1,7 @@ +namespace VisionBuilder.UI.Common.ViewModel.Interfaces.UI; + +public interface IRecipeCreationTool +{ + public bool Enabled { get; set; } + public void CreateRecipe(string recipeName); +} \ No newline at end of file diff --git a/VisionBuilder.UI.Common/VisionBuilder.cs b/VisionBuilder.UI.Common/VisionBuilder.cs index d4734e2..db356cf 100644 --- a/VisionBuilder.UI.Common/VisionBuilder.cs +++ b/VisionBuilder.UI.Common/VisionBuilder.cs @@ -2,6 +2,7 @@ using Ninject; using Ninject.Extensions.ChildKernel; using System.ComponentModel; +using VisionBuilder.UI.Common.NullClasses; using VisionBuilder.UI.Common.Plugins; using VisionBuilder.UI.Common.ViewModel; using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; @@ -20,6 +21,7 @@ public static class VisionBuilder mainKernel.Bind().ToConstant(settings); mainKernel.Bind().ToConstant(new UIConfiguration()); mainKernel.Bind().To().InSingletonScope(); + mainKernel.Bind().To().InSingletonScope(); // --- // mainKernel.Bind().ToSelf().InSingletonScope(); diff --git a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.Designer.cs b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.Designer.cs index f077d02..610f398 100644 --- a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.Designer.cs +++ b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.Designer.cs @@ -30,6 +30,7 @@ { listView1 = new ListView(); btnCancel = new MaterialSkin.Controls.MaterialRaisedButton(); + btnCreateNewRecipe = new MaterialSkin.Controls.MaterialRaisedButton(); SuspendLayout(); // // listView1 @@ -62,11 +63,30 @@ btnCancel.UseVisualStyleBackColor = true; 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 // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(962, 688); + Controls.Add(btnCreateNewRecipe); Controls.Add(btnCancel); Controls.Add(listView1); MaximizeBox = false; @@ -81,5 +101,6 @@ private ListView listView1; private MaterialSkin.Controls.MaterialRaisedButton btnCancel; + private MaterialSkin.Controls.MaterialRaisedButton btnCreateNewRecipe; } } \ No newline at end of file diff --git a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs index d5b4dc3..056e8fe 100644 --- a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs +++ b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.cs @@ -1,22 +1,31 @@ using MaterialSkin.Controls; using OpenCvSharp.XImgProc; using System.Text.RegularExpressions; +using MaterialSkin.Core.Controls; using OpenCvSharp.Extensions; using VisionBuilder.UI.Common.ViewModel; using VisionBuilder.UI.Common.ViewModel.Classes; +using VisionBuilder.UI.Common.ViewModel.Interfaces.UI; namespace VisionBuilder.UI.Windows.Dialogs { public partial class RecipeSelectionDialog : MaterialForm { private readonly RecipeSelectionVM _recipeSelectionVm; - - public RecipeSelectionDialog(RecipeSelectionVM recipeSelectionVm) + private readonly IRecipeCreationTool _recipeCreationTool; + + public RecipeSelectionDialog(RecipeSelectionVM recipeSelectionVm, IRecipeCreationTool recipeCreationTool) { _recipeSelectionVm = recipeSelectionVm; - + _recipeCreationTool = recipeCreationTool; + InitializeComponent(); ReloadRecipes(); + + if (!_recipeCreationTool.Enabled) + { + btnCreateNewRecipe.Visible = true; + } } public void ReloadRecipes() @@ -38,11 +47,11 @@ namespace VisionBuilder.UI.Windows.Dialogs listView1.LargeImageList.Images.Add("plug", plug); - + recipes.ForEach(x => { - + if (x.Image != null) @@ -53,7 +62,7 @@ namespace VisionBuilder.UI.Windows.Dialogs } else { - listView1.Items.Add(x.RecipeName, x.RecipeName, "plug").Tag=x; + listView1.Items.Add(x.RecipeName, x.RecipeName, "plug").Tag = x; } }); @@ -63,15 +72,23 @@ namespace VisionBuilder.UI.Windows.Dialogs { if (listView1.SelectedIndices.Count > 0) { - _recipeSelectionVm.SelectedRecipe = (RecipeData)listView1.SelectedItems[0].Tag!; + _recipeSelectionVm.SelectedRecipe = (RecipeData)listView1.SelectedItems[0].Tag!; } DialogResult = DialogResult.OK; } - + private void btnCancel_Click(object sender, EventArgs e) { 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); + } + } } } diff --git a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.resx b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.resx index af32865..8b2ff64 100644 --- a/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.resx +++ b/VisionBuilder.UI.Windows/Dialogs/RecipeSelectionDialog.resx @@ -1,7 +1,7 @@