diff --git a/src/Microsoft.ML.AutoML/TaskKind.cs b/src/Microsoft.ML.AutoML/TaskKind.cs index adfb9fdd89..a63d219024 100644 --- a/src/Microsoft.ML.AutoML/TaskKind.cs +++ b/src/Microsoft.ML.AutoML/TaskKind.cs @@ -10,6 +10,7 @@ internal enum TaskKind MulticlassClassification, Regression, Recommendation, + ObjectDetection, Ranking } } diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs index af9c0f40fd..f4cdf5c47c 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs @@ -68,9 +68,10 @@ public AzureAttachConsoleAppCodeGenerator(Pipeline pipeline, ColumnInferenceResu IncludeMklComponentsPackage = false, IncludeLightGBMPackage = false, IncludeFastTreePackage = false, - IncludeImageTransformerPackage = _settings.IsImage, + IncludeImageTransformerPackage = _settings.IsImage || _settings.IsObjectDetection, IncludeImageClassificationPackage = false, IncludeOnnxPackage = true, + IncludeOnnxRuntime = _settings.IsObjectDetection, IncludeResNet18Package = false, IncludeRecommenderPackage = false, StablePackageVersion = _settings.StablePackageVersion, @@ -94,6 +95,7 @@ public AzureAttachConsoleAppCodeGenerator(Pipeline pipeline, ColumnInferenceResu Separator = _columnInferenceResult.TextLoaderOptions.Separators.FirstOrDefault(), Target = _settings.Target, SampleData = sampleResult, + IsObjectDetection = _settings.IsObjectDetection, }.TransformText(), Name = "Program.cs", }; diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachModelCodeGenerator.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachModelCodeGenerator.cs index 7d109f4ae4..1ac25c2f48 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachModelCodeGenerator.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachModelCodeGenerator.cs @@ -25,10 +25,13 @@ internal class AzureAttachModelCodeGenerator : ICSharpProjectGenerator private readonly string _nameSpaceValue; public ICSharpFile ModelInputClass { get; private set; } - public ICSharpFile ModelOutputClass { get; private set; } + public ICSharpFile AzureImageModelOutputClass { get; private set; } + public ICSharpFile AzureObjectDetectionModelOutputClass { get; private set; } public ICSharpFile ModelProject { get; private set; } public ICSharpFile ConsumeModel { get; private set; } public ICSharpFile LabelMapping { get; private set; } + public ICSharpFile ImageLabelMapping { get; private set; } + public ICSharpFile ObjectDetectionConsumeModel { get; private set; } public string Name { get; set; } public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults columnInferenceResults, CodeGeneratorSettings options, string namespaceValue) @@ -53,7 +56,7 @@ public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults c var labelType = _columnInferenceResult.TextLoaderOptions.Columns.Where(t => t.Name == _settings.LabelName).First().DataKind; Type labelTypeCsharp = Utils.GetCSharpType(labelType); - ModelOutputClass = new CSharpCodeFile() + AzureImageModelOutputClass = new CSharpCodeFile() { File = new AzureImageModelOutputClass() { @@ -64,6 +67,17 @@ public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults c Name = "ModelOutput.cs", }; + AzureObjectDetectionModelOutputClass = new CSharpCodeFile() + { + File = new AzureObjectDetectionModelOutputClass() + { + Namespace = _nameSpaceValue, + Target = _settings.Target, + Labels = _settings.ObjectLabel, + }.TransformText(), + Name = "ModelOutput.cs", + }; + ModelProject = new CSharpProjectFile() { File = new ModelProject() @@ -74,6 +88,7 @@ public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults c IncludeLightGBMPackage = false, IncludeMklComponentsPackage = false, IncludeOnnxModel = true, + IncludeOnnxRuntime = _settings.IsObjectDetection, IncludeRecommenderPackage = false, StablePackageVersion = _settings.StablePackageVersion, UnstablePackageVersion = _settings.UnstablePackageVersion, @@ -83,19 +98,6 @@ public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults c Name = $"{ _settings.OutputName }.Model.csproj", }; - LabelMapping = new CSharpCodeFile() - { - File = new LabelMapping() - { - Target = _settings.Target, - Namespace = _nameSpaceValue, - LabelMappingInputLabelType = typeof(Int64).Name, - PredictionLabelType = labelTypeCsharp.Name, - TaskType = _settings.MlTask.ToString(), - }.TransformText(), - Name = "LabelMapping.cs", - }; - ConsumeModel = new CSharpCodeFile() { File = new ConsumeModel() @@ -105,6 +107,7 @@ public AzureAttachModelCodeGenerator(Pipeline pipeline, ColumnInferenceResults c MLNetModelName = _settings.ModelName, OnnxModelName = _settings.OnnxModelName, IsAzureImage = _settings.IsAzureAttach && _settings.IsImage, + IsAzureObjectDetection = _settings.IsObjectDetection && _settings.IsAzureAttach, }.TransformText(), Name = "ConsumeModel.cs", }; @@ -118,7 +121,16 @@ public ICSharpProject ToProject() project = new CSharpProject() { ModelInputClass, - ModelOutputClass, + AzureImageModelOutputClass, + ConsumeModel, + ModelProject, + }; + } else if(_settings.IsObjectDetection) + { + project = new CSharpProject() + { + ModelInputClass, + AzureObjectDetectionModelOutputClass, ConsumeModel, ModelProject, }; @@ -128,10 +140,9 @@ public ICSharpProject ToProject() project = new CSharpProject() { ModelInputClass, - ModelOutputClass, + AzureImageModelOutputClass, ConsumeModel, ModelProject, - LabelMapping, }; } project.Name = Name; diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGeneratorSettings.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGeneratorSettings.cs index 73d8e58a1f..e0fa2fb191 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGeneratorSettings.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGeneratorSettings.cs @@ -17,6 +17,7 @@ public CodeGeneratorSettings() Target = GenerateTarget.Cli; OnnxInputMapping = new Dictionary(); ClassificationLabel = new string[] { }; + ObjectLabel = new string[] { }; } public string LabelName { get; set; } @@ -37,6 +38,8 @@ public CodeGeneratorSettings() /// public string[] ClassificationLabel { get; set; } + public string[] ObjectLabel { get; set; } + public string OutputName { get; set; } public string OutputBaseDir { get; set; } @@ -57,6 +60,8 @@ public CodeGeneratorSettings() public bool IsImage { get; set; } + public bool IsObjectDetection { get; set; } + public IDictionary OnnxInputMapping { get; set; } internal TaskKind MlTask { get; set; } diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGeneratorFactory.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGeneratorFactory.cs index ef156c0cc2..ddf8bf07eb 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGeneratorFactory.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGeneratorFactory.cs @@ -20,7 +20,9 @@ internal enum SpecialTransformer ApplyOnnxModel = 0, ResizeImage = 1, ExtractPixel = 2, + ObjectDetectionResizeImage = 3, } + internal static class TransformGeneratorFactory { internal static ITransformGenerator GetInstance(PipelineNode node) @@ -78,7 +80,7 @@ internal static ITransformGenerator GetInstance(PipelineNode node) } } - // For AzureAttach + // For the AzureAttach if (Enum.TryParse(node.Name, out SpecialTransformer transformer)) { switch (transformer) @@ -92,6 +94,9 @@ internal static ITransformGenerator GetInstance(PipelineNode node) case SpecialTransformer.ApplyOnnxModel: result = new ApplyOnnxModel(node); break; + case SpecialTransformer.ObjectDetectionResizeImage: + result = new ObjectDetectionImageResizing(node); + break; default: return null; } diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGenerators.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGenerators.cs index fcc425d581..5fdb83bd97 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGenerators.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/TransformGenerators.cs @@ -400,6 +400,17 @@ public override string GenerateTransformer() } } + internal class ObjectDetectionImageResizing : TransformGeneratorBase + { + public ObjectDetectionImageResizing(PipelineNode node) : base(node) { } + internal override string MethodName => "ResizeImages"; + + public override string GenerateTransformer() + { + return @"ResizeImages(outputColumnName: ""ImageSource_featurized"", imageWidth: 800, imageHeight: 600, inputColumnName: ""ImageSource_featurized"")"; + } + } + internal class PixelExtract : TransformGeneratorBase { public PixelExtract(PipelineNode node) : base(node) { } diff --git a/src/Microsoft.ML.CodeGenerator/Microsoft.ML.CodeGenerator.csproj b/src/Microsoft.ML.CodeGenerator/Microsoft.ML.CodeGenerator.csproj index c6cb8c2d92..9d823595ca 100644 --- a/src/Microsoft.ML.CodeGenerator/Microsoft.ML.CodeGenerator.csproj +++ b/src/Microsoft.ML.CodeGenerator/Microsoft.ML.CodeGenerator.csproj @@ -26,17 +26,13 @@ TextTemplatingFilePreprocessor AzureModelBuilder.cs - - AzureImageModelOutputClass.cs + + AzureObjectDetectionModelOutputClass.cs TextTemplatingFilePreprocessor - - TextTemplatingFilePreprocessor + AzureImageModelOutputClass.cs - - TextTemplatingFilePreprocessor - LabelMapping.cs @@ -80,8 +76,8 @@ True AzureModelBuilder.tt - - OnnxModelOutputClass.tt + + AzureImageModelOutputClass.tt True True @@ -90,10 +86,15 @@ True AzureImageModelOutputClass.tt - + + OnnxModelOutputClass.tt + True + True + + True True - LabelMapping.tt + AzureObjectDetectionModelOutputClass.tt True diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/LabelMapping.cs b/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/AzureObjectDetectionModelOutputClass.cs similarity index 78% rename from src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/LabelMapping.cs rename to src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/AzureObjectDetectionModelOutputClass.cs index 8d336be8c1..65b87d4727 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/LabelMapping.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/AzureObjectDetectionModelOutputClass.cs @@ -18,7 +18,7 @@ namespace Microsoft.ML.CodeGenerator.Templates.Azure.Model /// Class to produce the template output /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] - internal partial class LabelMapping : LabelMappingBase + internal partial class AzureObjectDetectionModelOutputClass : AzureObjectDetectionModelOutputClassBase { /// /// Create the template output @@ -30,72 +30,47 @@ public virtual string TransformText() } else if(Target == CSharp.GenerateTarget.ModelBuilder){ MB_Annotation(); } - this.Write("\r\nusing Microsoft.ML.Data;\r\nusing Microsoft.ML.Transforms;\r\nusing System;\r\nusing " + - "System.Linq;\r\n\r\nnamespace "); + this.Write("\r\nusing System;\r\nusing Microsoft.ML.Data;\r\nusing System.Collections.Generic;\r\nusi" + + "ng System.Linq;\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write(@".Model -{ - [CustomMappingFactoryAttribute(nameof(LabelMapping))] - public class LabelMapping : CustomMappingFactory - { - // Custom mapping to determine the label with the highest probability - public static void Mapping(LabelMappingInput input, LabelMappingOutput output) - { -"); -if("Boolean".Equals(PredictionLabelType)){ - this.Write("\t\t\toutput.Prediction = input.label.GetValues().ToArray().First() == 1;\r\n"); - } else{ - this.Write(" output.Prediction = input.label.GetValues().ToArray().First();\r\n"); - } - this.Write("\r\n"); -if("MulticlassClassification".Equals(TaskType)){ - this.Write(" output.Score = input.probabilities.GetValues().ToArray();\r\n"); - } else { - this.Write(" output.Score = input.probabilities.GetValues().ToArray().First();\r\n"); - } - this.Write(@" } - // Factory method called when loading the model to get the mapping operation - public override Action GetMapping() - { - return Mapping; - } - } - public class LabelMappingInput - { - [ColumnName(""label"")] - public VBuffer label; - - [ColumnName(""probabilities"")] - public VBuffer probabilities; - } - public class LabelMappingOutput - { -"); -if("BinaryClassification".Equals(TaskType)){ - this.Write(" // ColumnName attribute is used to change the column name from\r\n\t\t// its " + - "default value, which is the name of the field\r\n [ColumnName(\"PredictedLab" + - "el\")]\r\n public bool Prediction { get; set; }\r\n"); - } if("MulticlassClassification".Equals(TaskType)){ - this.Write(" // ColumnName attribute is used to change the column name from\r\n\t\t// its " + - "default value, which is the name of the field\r\n [ColumnName(\"PredictedLab" + - "el\")]\r\n public "); - this.Write(this.ToStringHelper.ToStringWithCulture(PredictionLabelType)); - this.Write(" Prediction { get; set; }\r\n"); - } -if("MulticlassClassification".Equals(TaskType)){ - this.Write(" public float[] Score { get; set; }\r\n"); -}else{ - this.Write(" public float Score { get; set; }\r\n"); + this.Write(".Model\r\n{\r\n public class ModelOutput\r\n {\r\n public string[] ObjectTag" + + "s = new string[]{"); +foreach(var label in Labels){ + this.Write("\""); + this.Write(this.ToStringHelper.ToStringWithCulture(label)); + this.Write("\","); } - this.Write(" }\r\n}\r\n"); + this.Write("};\r\n\r\n [ColumnName(\"boxes\")]\r\n public float[] Boxes { get; set; }\r\n" + + "\r\n [ColumnName(\"labels\")]\r\n public long[] Labels { get; set; }\r\n\r\n" + + " [ColumnName(\"scores\")]\r\n public float[] Scores { get; set; }\r\n\r\n " + + " private BoundingBox[] BoundingBoxes\r\n {\r\n get\r\n " + + " {\r\n var boundingBoxes = new List();\r\n\r\n " + + " boundingBoxes = Enumerable.Range(0, this.Labels.Length)\r\n " + + " .Select((index) =>\r\n {\r\n " + + " var boxes = this.Boxes;\r\n var scores = thi" + + "s.Scores;\r\n var labels = this.Labels;\r\n\r\n " + + " return new BoundingBox()\r\n {\r\n " + + " Left = boxes[index * 4],\r\n " + + " Top = boxes[(index * 4) + 1],\r\n Ri" + + "ght = boxes[(index * 4) + 2],\r\n Bottom = boxes[" + + "(index * 4) + 3],\r\n Score = scores[index],\r\n " + + " Label = this.ObjectTags[labels[index]],\r\n " + + " };\r\n }).ToList();\r\n " + + " return boundingBoxes.ToArray();\r\n }\r\n }\r\n\r\n public ov" + + "erride string ToString()\r\n {\r\n return string.Join(\"\\n\", Boundi" + + "ngBoxes.Select(x => x.ToString()));\r\n }\r\n }\r\n\r\n public class Boundi" + + "ngBox\r\n {\r\n public float Top;\r\n\r\n public float Left;\r\n\r\n " + + " public float Right;\r\n\r\n public float Bottom;\r\n\r\n public string La" + + "bel;\r\n\r\n public float Score;\r\n\r\n public override string ToString()" + + "\r\n {\r\n return $\"Top: {this.Top}, Left: {this.Left}, Right: {th" + + "is.Right}, Bottom: {this.Bottom}, Label: {this.Label}, Score: {this.Score}\";\r\n " + + " }\r\n }\r\n}\r\n"); return this.GenerationEnvironment.ToString(); } public string Namespace {get;set;} internal CSharp.GenerateTarget Target {get;set;} -public string TaskType {get;set;} -public string PredictionLabelType {get;set;} -public string LabelMappingInputLabelType {get;set;} +public string[] Labels {get; set;} = new string[0]; void CLI_Annotation() @@ -124,7 +99,7 @@ void MB_Annotation() /// Base class for this transformation /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] - internal class LabelMappingBase + internal class AzureObjectDetectionModelOutputClassBase { #region Fields private global::System.Text.StringBuilder generationEnvironmentField; diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/AzureObjectDetectionModelOutputClass.tt b/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/AzureObjectDetectionModelOutputClass.tt new file mode 100644 index 0000000000..7cf4080a8a --- /dev/null +++ b/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/AzureObjectDetectionModelOutputClass.tt @@ -0,0 +1,89 @@ +<#@ template language="C#" linePragmas="false" visibility="internal"#> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ include file="..\..\Console\Annotation.ttinclude" #><#if(Target == CSharp.GenerateTarget.Cli){ #> +<#CLI_Annotation();#> +<# } else if(Target == CSharp.GenerateTarget.ModelBuilder){ #> +<#MB_Annotation();#> +<# } #> + +using System; +using Microsoft.ML.Data; +using System.Collections.Generic; +using System.Linq; + +namespace <#= Namespace #>.Model +{ + public class ModelOutput + { + public string[] ObjectTags = new string[]{<#foreach(var label in Labels){#>"<#=label#>",<#}#>}; + + [ColumnName("boxes")] + public float[] Boxes { get; set; } + + [ColumnName("labels")] + public long[] Labels { get; set; } + + [ColumnName("scores")] + public float[] Scores { get; set; } + + private BoundingBox[] BoundingBoxes + { + get + { + var boundingBoxes = new List(); + + boundingBoxes = Enumerable.Range(0, this.Labels.Length) + .Select((index) => + { + var boxes = this.Boxes; + var scores = this.Scores; + var labels = this.Labels; + + return new BoundingBox() + { + Left = boxes[index * 4], + Top = boxes[(index * 4) + 1], + Right = boxes[(index * 4) + 2], + Bottom = boxes[(index * 4) + 3], + Score = scores[index], + Label = this.ObjectTags[labels[index]], + }; + }).ToList(); + return boundingBoxes.ToArray(); + } + } + + public override string ToString() + { + return string.Join("\n", BoundingBoxes.Select(x => x.ToString())); + } + } + + public class BoundingBox + { + public float Top; + + public float Left; + + public float Right; + + public float Bottom; + + public string Label; + + public float Score; + + public override string ToString() + { + return $"Top: {this.Top}, Left: {this.Left}, Right: {this.Right}, Bottom: {this.Bottom}, Label: {this.Label}, Score: {this.Score}"; + } + } +} +<#+ +public string Namespace {get;set;} +internal CSharp.GenerateTarget Target {get;set;} +public string[] Labels {get; set;} = new string[0]; +#> \ No newline at end of file diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/LabelMapping.tt b/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/LabelMapping.tt deleted file mode 100644 index c17e4aaa03..0000000000 --- a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/LabelMapping.tt +++ /dev/null @@ -1,77 +0,0 @@ -<#@ template language="C#" linePragmas = "false" visibility = "internal" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ include file="..\..\Console\Annotation.ttinclude" #><#if(Target == CSharp.GenerateTarget.Cli){ #> -<#CLI_Annotation();#> -<# } else if(Target == CSharp.GenerateTarget.ModelBuilder){ #> -<#MB_Annotation();#> -<# } #> - -using Microsoft.ML.Data; -using Microsoft.ML.Transforms; -using System; -using System.Linq; - -namespace <#= Namespace #>.Model -{ - [CustomMappingFactoryAttribute(nameof(LabelMapping))] - public class LabelMapping : CustomMappingFactory - { - // Custom mapping to determine the label with the highest probability - public static void Mapping(LabelMappingInput input, LabelMappingOutput output) - { -<#if("Boolean".Equals(PredictionLabelType)){ #> - output.Prediction = input.label.GetValues().ToArray().First() == 1; -<# } else{ #> - output.Prediction = input.label.GetValues().ToArray().First(); -<# }#> - -<#if("MulticlassClassification".Equals(TaskType)){ #> - output.Score = input.probabilities.GetValues().ToArray(); -<# } else {#> - output.Score = input.probabilities.GetValues().ToArray().First(); -<# }#> - } - // Factory method called when loading the model to get the mapping operation - public override Action GetMapping() - { - return Mapping; - } - } - public class LabelMappingInput - { - [ColumnName("label")] - public VBuffer label; - - [ColumnName("probabilities")] - public VBuffer probabilities; - } - public class LabelMappingOutput - { -<#if("BinaryClassification".Equals(TaskType)){ #> - // ColumnName attribute is used to change the column name from - // its default value, which is the name of the field - [ColumnName("PredictedLabel")] - public bool Prediction { get; set; } -<# } if("MulticlassClassification".Equals(TaskType)){ #> - // ColumnName attribute is used to change the column name from - // its default value, which is the name of the field - [ColumnName("PredictedLabel")] - public <#= PredictionLabelType#> Prediction { get; set; } -<# }#> -<#if("MulticlassClassification".Equals(TaskType)){ #> - public float[] Score { get; set; } -<#}else{ #> - public float Score { get; set; } -<#}#> - } -} -<#+ -public string Namespace {get;set;} -internal CSharp.GenerateTarget Target {get;set;} -public string TaskType {get;set;} -public string PredictionLabelType {get;set;} -public string LabelMappingInputLabelType {get;set;} -#> \ No newline at end of file diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/OnnxModelOutputClass.tt b/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/OnnxModelOutputClass.tt deleted file mode 100644 index 377cfa74e8..0000000000 --- a/src/Microsoft.ML.CodeGenerator/Templates/Azure/Model/OnnxModelOutputClass.tt +++ /dev/null @@ -1,52 +0,0 @@ -<#@ template language="C#" linePragmas="false" visibility="internal"#> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ include file="..\..\Console\Annotation.ttinclude" #><#if(Target == CSharp.GenerateTarget.Cli){ #> -<#CLI_Annotation();#> -<# } else if(Target == CSharp.GenerateTarget.ModelBuilder){ #> -<#MB_Annotation();#> -<# } #> -using System; -using Microsoft.ML.Data; - -namespace <#= Namespace #>.Model -{ - public class ModelOutput - { -<#if("BinaryClassification".Equals(TaskType)){ #> - // ColumnName attribute is used to change the column name from - // its default value, which is the name of the field. - [ColumnName("label")] - public <#= PredictionLabelType#>[] Prediction { get; set; } - -<# } if("MulticlassClassification".Equals(TaskType)){ #> - // ColumnName attribute is used to change the column name from - // its default value, which is the name of the field. - [ColumnName("label")] - public <#= PredictionLabelType#>[] Prediction { get; set; } -<# } if("AzureImageClassification".Equals(TaskType)){ #> - // ColumnName attribute is used to change the column name from - // its default value, which is the name of the field. - [ColumnName("output1")] - public float[] Score { get; set; } -<# } -#> -<#if("MulticlassClassification".Equals(TaskType)){ #> - [ColumnName("probabilities")] - public float[] Score { get; set; } -<#}else if (!"AzureImageClassification".Equals(TaskType)){ #> - [ColumnName("probabilities")] - public float[] Score { get; set; } -<#}#> - - } -} - -<#+ -public string Namespace {get;set;} -internal CSharp.GenerateTarget Target {get;set;} -public string TaskType {get;set;} -public string PredictionLabelType {get;set;} -#> \ No newline at end of file diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.cs index 0b18750140..6ba08042c9 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.cs @@ -44,7 +44,7 @@ public class ConsumeModel public static string MLNetModelPath = Path.GetFullPath("""); this.Write(this.ToStringHelper.ToStringWithCulture(MLNetModelName)); this.Write("\");\r\n"); -if(IsAzureImage){ +if(IsAzureImage || IsAzureObjectDetection){ this.Write(" \r\n public static string OnnxModelPath = Path.GetFullPath(\""); this.Write(this.ToStringHelper.ToStringWithCulture(OnnxModelName)); this.Write("\");\r\n"); @@ -77,6 +77,7 @@ public static PredictionEngine CreatePredictionEngine() public string Namespace {get;set;} internal CSharp.GenerateTarget Target {get;set;} +public bool IsAzureObjectDetection {get; set;}=false; public bool IsAzureImage {get; set;}=false; public string MLNetModelName {get; set;} public string OnnxModelName {get; set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.tt index 72f5bbed63..8c3d11568d 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/ConsumeModel.tt @@ -24,7 +24,7 @@ namespace <#= Namespace #>.Model private static Lazy> PredictionEngine = new Lazy>(CreatePredictionEngine); public static string MLNetModelPath = Path.GetFullPath("<#= MLNetModelName #>"); -<#if(IsAzureImage){ #> +<#if(IsAzureImage || IsAzureObjectDetection){ #> public static string OnnxModelPath = Path.GetFullPath("<#= OnnxModelName #>"); <#} #> @@ -52,6 +52,7 @@ namespace <#= Namespace #>.Model <#+ public string Namespace {get;set;} internal CSharp.GenerateTarget Target {get;set;} +public bool IsAzureObjectDetection {get; set;}=false; public bool IsAzureImage {get; set;}=false; public string MLNetModelName {get; set;} public string OnnxModelName {get; set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.cs index aef6b95782..3c08480062 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.cs @@ -30,21 +30,51 @@ public virtual string TransformText() } else if(Target == CSharp.GenerateTarget.ModelBuilder){ MB_Annotation(); } - this.Write("\r\nusing System;\r\nusing Microsoft.ML.Data;\r\n\r\nnamespace "); + this.Write("\r\nusing System;\r\nusing Microsoft.ML.Data;\r\n"); +if(IsObjectDetection){ + this.Write("using System.Collections.Generic;\r\nusing System.Linq;\r\n"); + } + this.Write("\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write(".Model\r\n{\r\n public class ModelOutput\r\n {\r\n"); if("BinaryClassification".Equals(TaskType)){ this.Write(" // ColumnName attribute is used to change the column name from\r\n /" + "/ its default value, which is the name of the field.\r\n [ColumnName(\"Predi" + - "ctedLabel\")]\r\n public bool Prediction { get; set; }\r\n\r\n"); - } if("MulticlassClassification".Equals(TaskType)){ + "ctedLabel\")]\r\n public bool Prediction { get; set; }\r\n"); + } if("MulticlassClassification".Equals(TaskType) && !IsObjectDetection){ this.Write(" // ColumnName attribute is used to change the column name from\r\n /" + "/ its default value, which is the name of the field.\r\n [ColumnName(\"Predi" + "ctedLabel\")]\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(PredictionLabelType)); this.Write(" Prediction { get; set; }\r\n"); } -if("MulticlassClassification".Equals(TaskType)){ +if(IsObjectDetection){ + this.Write(" [ColumnName(\"boxes\")]\r\n public float[] Boxes { get; set; }\r\n\r\n " + + " [ColumnName(\"PredictedLabels\")]\r\n public string[] Labels { get; set; " + + "}\r\n\r\n [ColumnName(\"scores\")]\r\n public float[] Scores { get; set; }" + + "\r\n\r\n private BoundingBox[] BoundingBoxes\r\n {\r\n get\r\n " + + " {\r\n var boundingBoxes = new List();\r\n\r\n " + + " boundingBoxes = Enumerable.Range(0, this.Labels.Length)\r\n " + + " .Select((index) =>\r\n {\r\n " + + " var boxes = this.Boxes;\r\n var scores " + + "= this.Scores;\r\n var labels = this.Labels;\r\n\r\n " + + " return new BoundingBox()\r\n " + + " {\r\n Left = boxes[index * 4],\r\n " + + " Top = boxes[(index * 4) + 1],\r\n " + + " Right = boxes[(index * 4) + 2],\r\n Bottom = b" + + "oxes[(index * 4) + 3],\r\n Score = scores[index]," + + "\r\n Label = labels[index].ToString(),\r\n " + + " };\r\n }).ToList();\r\n " + + " return boundingBoxes.ToArray();\r\n }\r\n }\r\n\r\n public ove" + + "rride string ToString()\r\n {\r\n return string.Join(\"\\n\", Boundin" + + "gBoxes.Select(x => x.ToString()));\r\n }\r\n }\r\n\r\n public class Boundin" + + "gBox\r\n {\r\n public float Top;\r\n\r\n public float Left;\r\n\r\n " + + "public float Right;\r\n\r\n public float Bottom;\r\n\r\n public string Lab" + + "el;\r\n\r\n public float Score;\r\n\r\n public override string ToString()\r" + + "\n {\r\n return $\"Top: {this.Top}, Left: {this.Left}, Right: {thi" + + "s.Right}, Bottom: {this.Bottom}, Label: {this.Label}, Score: {this.Score}\";\r\n " + + " }\r\n"); +} else if("MulticlassClassification".Equals(TaskType)){ this.Write(" public float[] Score { get; set; }\r\n"); }else{ this.Write(" public float Score { get; set; }\r\n"); @@ -56,6 +86,7 @@ public virtual string TransformText() public string TaskType {get;set;} public string PredictionLabelType {get;set;} public string Namespace {get;set;} +public bool IsObjectDetection {get;set;} internal CSharp.GenerateTarget Target {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.tt index 728f81c3f8..39efa56b6d 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelOutputClass.tt @@ -11,6 +11,10 @@ using System; using Microsoft.ML.Data; +<#if(IsObjectDetection){ #> +using System.Collections.Generic; +using System.Linq; +<# }#> namespace <#= Namespace #>.Model { @@ -21,14 +25,74 @@ namespace <#= Namespace #>.Model // its default value, which is the name of the field. [ColumnName("PredictedLabel")] public bool Prediction { get; set; } - -<# } if("MulticlassClassification".Equals(TaskType)){ #> +<# } if("MulticlassClassification".Equals(TaskType) && !IsObjectDetection){ #> // ColumnName attribute is used to change the column name from // its default value, which is the name of the field. [ColumnName("PredictedLabel")] public <#= PredictionLabelType#> Prediction { get; set; } <# }#> -<#if("MulticlassClassification".Equals(TaskType)){ #> +<#if(IsObjectDetection){ #> + [ColumnName("boxes")] + public float[] Boxes { get; set; } + + [ColumnName("PredictedLabels")] + public string[] Labels { get; set; } + + [ColumnName("scores")] + public float[] Scores { get; set; } + + private BoundingBox[] BoundingBoxes + { + get + { + var boundingBoxes = new List(); + + boundingBoxes = Enumerable.Range(0, this.Labels.Length) + .Select((index) => + { + var boxes = this.Boxes; + var scores = this.Scores; + var labels = this.Labels; + + return new BoundingBox() + { + Left = boxes[index * 4], + Top = boxes[(index * 4) + 1], + Right = boxes[(index * 4) + 2], + Bottom = boxes[(index * 4) + 3], + Score = scores[index], + Label = labels[index].ToString(), + }; + }).ToList(); + return boundingBoxes.ToArray(); + } + } + + public override string ToString() + { + return string.Join("\n", BoundingBoxes.Select(x => x.ToString())); + } + } + + public class BoundingBox + { + public float Top; + + public float Left; + + public float Right; + + public float Bottom; + + public string Label; + + public float Score; + + public override string ToString() + { + return $"Top: {this.Top}, Left: {this.Left}, Right: {this.Right}, Bottom: {this.Bottom}, Label: {this.Label}, Score: {this.Score}"; + } +<#} else if("MulticlassClassification".Equals(TaskType)){ #> public float[] Score { get; set; } <#}else{ #> public float Score { get; set; } @@ -39,5 +103,6 @@ namespace <#= Namespace #>.Model public string TaskType {get;set;} public string PredictionLabelType {get;set;} public string Namespace {get;set;} +public bool IsObjectDetection {get;set;} internal CSharp.GenerateTarget Target {get;set;} #> diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.cs index 94ecdfe37c..d6f174f7b8 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.cs @@ -56,6 +56,8 @@ public virtual string TransformText() this.Write("\" />\r\n \r\n"); +} + if (IncludeOnnxRuntime){ } if (IncludeImageClassificationPackage){ this.Write(" PreserveNewest\r\n \r\n"); if (IncludeOnnxModel){ this.Write(" \r\n PreserveNewest\r\n \r\n \r\n " + - " PreserveNewest\r\n \r\n"); + "CopyToOutputDirectory>\r\n \r\n"); } this.Write(" \r\n\r\n \r\n"); if (Target==CSharp.GenerateTarget.Cli) { @@ -91,6 +92,7 @@ public virtual string TransformText() public bool IncludeImageTransformerPackage {get; set;} public bool IncludeImageClassificationPackage {get; set;} public bool IncludeOnnxModel {get; set;} +public bool IncludeOnnxRuntime {get; set;} public bool IncludeRecommenderPackage {get;set;} public string StablePackageVersion {get;set;} public string UnstablePackageVersion {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.tt index f87ff51595..c0f382f9c1 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/ModelProject.tt @@ -26,6 +26,8 @@ <#}#> +<# if (IncludeOnnxRuntime){ #> +<#}#> <# if (IncludeImageClassificationPackage){ #> @@ -43,9 +45,6 @@ PreserveNewest - - PreserveNewest - <#}#> @@ -64,6 +63,7 @@ public bool IncludeFastTreePackage {get;set;} public bool IncludeImageTransformerPackage {get; set;} public bool IncludeImageClassificationPackage {get; set;} public bool IncludeOnnxModel {get; set;} +public bool IncludeOnnxRuntime {get; set;} public bool IncludeRecommenderPackage {get;set;} public string StablePackageVersion {get;set;} public string UnstablePackageVersion {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs index 439e47c191..782c0f1fb1 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs @@ -53,12 +53,14 @@ public virtual string TransformText() this.Write(" ModelInput sampleData = new ModelInput();\r\n"); } this.Write("\r\n\t\t\t// Make a single prediction on the sample data and print results\r\n\t\t\tvar pre" + - "dictionResult = ConsumeModel.Predict(sampleData);\r\n\r\n\t\t\tConsole.WriteLine(\"Using" + - " model to make single prediction -- Comparing actual "); + "dictionResult = ConsumeModel.Predict(sampleData);\r\n\r\n "); +if(!IsObjectDetection){ + this.Write("\t\t\tConsole.WriteLine(\"Using model to make single prediction -- Comparing actual "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" with predicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" from sample data...\\n\\n\");\r\n"); +} if(SampleData != null) { foreach(var kv in SampleData){ this.Write("\t\t\tConsole.WriteLine($\""); @@ -76,12 +78,15 @@ public virtual string TransformText() this.Write("\t\t\tConsole.WriteLine($\"\\n\\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(": {predictionResult.Score}\\n\\n\");\r\n"); -} else if("MulticlassClassification".Equals(TaskType)){ +} else if("MulticlassClassification".Equals(TaskType) && !IsObjectDetection){ this.Write("\t\t\tConsole.WriteLine($\"\\n\\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" value {predictionResult.Prediction} \\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" scores: [{String.Join(\",\", predictionResult.Score)}]\\n\\n\");\r\n"); +} else if(IsObjectDetection){ + this.Write("\t\t\tConsole.WriteLine(\"\\n\\nPredicted Boxes:\\n\");\r\n Console.WriteLine(pr" + + "edictionResult);\r\n"); } this.Write(" Console.WriteLine(\"=============== End of process, hit any key to fin" + "ish ===============\");\r\n Console.ReadKey();\r\n }\r\n }\r\n}\r\n"); @@ -95,6 +100,7 @@ public virtual string TransformText() public bool AllowQuoting {get;set;} public bool AllowSparse {get;set;} public bool HasHeader {get;set;} +public bool IsObjectDetection {get; set;} public IList Features {get;set;} internal CSharp.GenerateTarget Target {get;set;} public IDictionary SampleData {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt index 4035d2ebaf..fe00c8489a 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt @@ -35,7 +35,9 @@ namespace <#= Namespace #>.ConsoleApp // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); + <#if(!IsObjectDetection){ #> Console.WriteLine("Using model to make single prediction -- Comparing actual <#= Utils.Normalize(LabelName) #> with predicted <#= Utils.Normalize(LabelName) #> from sample data...\n\n"); +<#} #> <# if(SampleData != null) {#> <#foreach(var kv in SampleData){#> Console.WriteLine($"<#= kv.Key #>: {sampleData.<#= kv.Key #>}"); @@ -45,8 +47,11 @@ namespace <#= Namespace #>.ConsoleApp Console.WriteLine($"\n\nPredicted <#= Utils.Normalize(LabelName) #>: {predictionResult.Prediction}\n\n"); <#} else if("Regression".Equals(TaskType) || "Recommendation".Equals(TaskType)){#> Console.WriteLine($"\n\nPredicted <#= Utils.Normalize(LabelName) #>: {predictionResult.Score}\n\n"); -<#} else if("MulticlassClassification".Equals(TaskType)){#> +<#} else if("MulticlassClassification".Equals(TaskType) && !IsObjectDetection){#> Console.WriteLine($"\n\nPredicted <#= Utils.Normalize(LabelName) #> value {predictionResult.Prediction} \nPredicted <#= Utils.Normalize(LabelName) #> scores: [{String.Join(",", predictionResult.Score)}]\n\n"); +<#} else if(IsObjectDetection){#> + Console.WriteLine("\n\nPredicted Boxes:\n"); + Console.WriteLine(predictionResult); <#} #> Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); @@ -61,6 +66,7 @@ public char Separator {get;set;} public bool AllowQuoting {get;set;} public bool AllowSparse {get;set;} public bool HasHeader {get;set;} +public bool IsObjectDetection {get; set;} public IList Features {get;set;} internal CSharp.GenerateTarget Target {get;set;} public IDictionary SampleData {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProject.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProject.cs index d7e89dc5c7..7ae7e11549 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProject.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProject.cs @@ -57,6 +57,8 @@ public virtual string TransformText() this.Write("\" />\r\n \r\n"); +} + if (IncludeOnnxRuntime){ } if (IncludeResNet18Package){ this.Write(" <#}#> +<# if (IncludeOnnxRuntime){ #> +<#}#> <# if (IncludeResNet18Package){ #> <#}#> @@ -59,6 +61,7 @@ public bool IncludeFastTreePackage {get;set;} public bool IncludeImageTransformerPackage {get; set;} public bool IncludeImageClassificationPackage {get; set;} public bool IncludeOnnxPackage {get; set;} +public bool IncludeOnnxRuntime {get; set;} public bool IncludeResNet18Package {get; set;} public bool IncludeRecommenderPackage {get;set;} public string StablePackageVersion {get;set;} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.LabelMapping.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.LabelMapping.cs.approved.txt index 4763e479c5..e69de29bb2 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.LabelMapping.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.LabelMapping.cs.approved.txt @@ -1,42 +0,0 @@ -// This file was auto-generated by ML.NET Model Builder. - -using Microsoft.ML.Data; -using Microsoft.ML.Transforms; -using System; -using System.Linq; - -namespace Test.Model -{ - [CustomMappingFactoryAttribute(nameof(LabelMapping))] - public class LabelMapping : CustomMappingFactory - { - // Custom mapping to determine the label with the highest probability - public static void Mapping(LabelMappingInput input, LabelMappingOutput output) - { - output.Prediction = input.label.GetValues().ToArray().First() == 1; - - output.Score = input.probabilities.GetValues().ToArray(); - } - // Factory method called when loading the model to get the mapping operation - public override Action GetMapping() - { - return Mapping; - } - } - public class LabelMappingInput - { - [ColumnName("label")] - public VBuffer label; - - [ColumnName("probabilities")] - public VBuffer probabilities; - } - public class LabelMappingOutput - { - // ColumnName attribute is used to change the column name from - // its default value, which is the name of the field - [ColumnName("PredictedLabel")] - public Boolean Prediction { get; set; } - public float[] Score { get; set; } - } -} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.test.Model.csproj.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.test.Model.csproj.approved.txt index 9ae818e04a..4cd9fba137 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.test.Model.csproj.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.test.Model.csproj.approved.txt @@ -16,9 +16,6 @@ PreserveNewest - - PreserveNewest - diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt index bf9adefa3a..3bc86db9e3 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt @@ -17,9 +17,6 @@ PreserveNewest - - PreserveNewest - diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.LabelMapping.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.LabelMapping.cs.approved.txt deleted file mode 100644 index a6f05a499e..0000000000 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.LabelMapping.cs.approved.txt +++ /dev/null @@ -1,48 +0,0 @@ -// This file was auto-generated by ML.NET Model Builder. -using Microsoft.ML.Data; -using Microsoft.ML.Transforms; -using System; -using System.Linq; - -namespace CodeGenTest.Model -{ - [CustomMappingFactoryAttribute(nameof(LabelMapping))] - public class LabelMapping : CustomMappingFactory - { - public static string[] Label { get; set; } = new string[] { "label1", "label2", "label3", }; - // This is the custom mapping. We now separate it into a method, so that we can use it both in training and in loading. - public static void Mapping(LabelMappingInput input, LabelMappingOutput output) - { - var values = input.output1.GetValues().ToArray(); - var maxVal = values.Max(); - var exp = values.Select(v => Math.Exp(v - maxVal)); - var sumExp = exp.Sum(); - - exp.Select(v => (float)(v / sumExp)).ToArray(); - output.score = exp.Select(v => (float)(v / sumExp)).ToArray(); - - var maxValue = output.score.Max(); - var maxValueIndex = Array.IndexOf(output.score, maxValue); - output.label = Label[maxValueIndex]; - } - // This factory method will be called when loading the model to get the mapping operation. - public override Action GetMapping() - { - return Mapping; - } - } - public class LabelMappingInput - { - [ColumnName("output1")] - public VBuffer output1; - } - public class LabelMappingOutput - { - - [ColumnName("PredictedLabel")] - public string label { get; set; } - - [ColumnName("Score")] - public float[] score { get; set; } - } -} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.NormalizeMapping.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.NormalizeMapping.cs.approved.txt deleted file mode 100644 index 4687096197..0000000000 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.NormalizeMapping.cs.approved.txt +++ /dev/null @@ -1,50 +0,0 @@ -// This file was auto-generated by ML.NET Model Builder. - -using Microsoft.ML.Data; -using Microsoft.ML.Transforms; -using System; -using System.Linq; - -namespace CodeGenTest.Model -{ - [CustomMappingFactoryAttribute(nameof(NormalizeMapping))] - public class NormalizeMapping : CustomMappingFactory - { - // This is the custom mapping. We now separate it into a method, so that we can use it both in training and in loading. - public static void Mapping(NormalizeInput input, NormalizeOutput output) - { - var values = input.Reshape.GetValues().ToArray(); - - var image_mean = new float[] { 0.485f, 0.456f, 0.406f }; - var image_std = new float[] { 0.229f, 0.224f, 0.225f }; - - for (int x = 0; x < values.Count(); x++) - { - var y = x % 3; - // Normalize by 255 first - values[x] /= 255; - values[x] = (values[x] - image_mean[y]) / image_std[y]; - }; - - output.Reshape = new VBuffer(values.Count(), values); - } - // This factory method will be called when loading the model to get the mapping operation. - public override Action GetMapping() - { - return Mapping; - } - } - public class NormalizeInput - { - [ColumnName("ImageSource_featurized")] - [VectorType(3, 224, 224)] - public VBuffer Reshape; - } - public class NormalizeOutput - { - [ColumnName("input1")] - [VectorType(3 * 224 * 224)] - public VBuffer Reshape; - } -} - diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.CodeGenTest.ConsoleApp.csproj.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.CodeGenTest.ConsoleApp.csproj.approved.txt new file mode 100644 index 0000000000..7eeab03036 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.CodeGenTest.ConsoleApp.csproj.approved.txt @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + + + diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt new file mode 100644 index 0000000000..42a5a0cc44 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.CodeGenTest.Model.csproj.approved.txt @@ -0,0 +1,24 @@ + + + + netstandard2.0 + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ConsumeModel.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ConsumeModel.cs.approved.txt new file mode 100644 index 0000000000..102d17059d --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ConsumeModel.cs.approved.txt @@ -0,0 +1,41 @@ +// This file was auto-generated by ML.NET Model Builder. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Microsoft.ML; +using CodeGenTest.Model; + +namespace CodeGenTest.Model +{ + public class ConsumeModel + { + private static Lazy> PredictionEngine = new Lazy>(CreatePredictionEngine); + + public static string MLNetModelPath = Path.GetFullPath("/path/to/model"); + + public static string OnnxModelPath = Path.GetFullPath("/path/to/onnxModel"); + + // For more info on consuming ML.NET models, visit https://aka.ms/mlnet-consume + // Method for consuming model in your app + public static ModelOutput Predict(ModelInput input) + { + ModelOutput result = PredictionEngine.Value.Predict(input); + return result; + } + + public static PredictionEngine CreatePredictionEngine() + { + // Create new MLContext + MLContext mlContext = new MLContext(); + + // Load model & create prediction engine + ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var modelInputSchema); + var predEngine = mlContext.Model.CreatePredictionEngine(mlModel); + + return predEngine; + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelBuilder.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelBuilder.cs.approved.txt new file mode 100644 index 0000000000..de9170c0f7 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelBuilder.cs.approved.txt @@ -0,0 +1,73 @@ +// This file was auto-generated by ML.NET Model Builder. +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.ML; +using Microsoft.ML.Data; +using CodeGenTest.Model; +namespace CodeGenTest.ConsoleApp +{ + public static class ModelBuilder + { + private static string TRAIN_DATA_FILEPATH = @"/path/to/dataset"; + private static string MLNET_MODEL = ConsumeModel.MLNetModelPath; + private static string ONNX_MODEL = ConsumeModel.OnnxModelPath; + + // Create MLContext to be shared across the model creation workflow objects + // Set a random seed for repeatable/deterministic results across multiple trainings. + private static MLContext mlContext = new MLContext(seed: 1); + + // Training in Azure produces an ONNX model; this method demonstrates creating an ML.NET model (MLModel.zip) from the ONNX model (bestModel.onnx), which you can then use with the ConsumeModel() method to make predictions + public static void CreateMLNetModelFromOnnx() + { + // Load data + IDataView inputDataView = mlContext.Data.LoadFromTextFile( + path: TRAIN_DATA_FILEPATH, + hasHeader: true, + separatorChar: '\t', + allowQuoting: true, + allowSparse: true); + + // Create an ML.NET pipeline to score using the ONNX model + // Notice that this pipeline is not trainable because it only contains transformers + IEstimator pipeline = BuildPipeline(mlContext); + + // Create ML.NET model from pipeline + ITransformer mlModel = pipeline.Fit(inputDataView); + + // Save model + SaveModel(mlContext, mlModel, MLNET_MODEL, inputDataView.Schema); + } + + public static IEstimator BuildPipeline(MLContext mlContext) + { + // Data process configuration with pipeline data transformations to: + // 1. Score using provided onnx model + // 2. Map scores to labels to make model output easier to understand and use + var pipeline = mlContext.Transforms.LoadImages("input", null, "ImagePath") + .Append(mlContext.Transforms.ResizeImages(outputColumnName: "ImageSource_featurized", imageWidth: 800, imageHeight: 600, inputColumnName: "ImageSource_featurized")) + .Append(mlContext.Transforms.ExtractPixels("input", "input")) + .Append(mlContext.Transforms.ApplyOnnxModel(modelFile: ONNX_MODEL)); + return pipeline; + } + + private static void SaveModel(MLContext mlContext, ITransformer mlModel, string modelRelativePath, DataViewSchema modelInputSchema) + { + // Save/persist the trained model to a .ZIP file + Console.WriteLine($"=============== Saving the model ==============="); + mlContext.Model.Save(mlModel, modelInputSchema, GetAbsolutePath(modelRelativePath)); + Console.WriteLine("The model is saved to {0}", GetAbsolutePath(modelRelativePath)); + } + + public static string GetAbsolutePath(string relativePath) + { + FileInfo _dataRoot = new FileInfo(typeof(Program).Assembly.Location); + string assemblyFolderPath = _dataRoot.Directory.FullName; + + string fullPath = Path.Combine(assemblyFolderPath, relativePath); + + return fullPath; + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelInput.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelInput.cs.approved.txt new file mode 100644 index 0000000000..41cf93f623 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelInput.cs.approved.txt @@ -0,0 +1,18 @@ +// This file was auto-generated by ML.NET Model Builder. + +using Microsoft.ML.Data; + +namespace CodeGenTest.Model +{ + public class ModelInput + { + [ColumnName("Label"), LoadColumn(0)] + public string Label { get; set; } + + + [ColumnName("ImagePath"), LoadColumn(1)] + public string ImagePath { get; set; } + + + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelOutput.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelOutput.cs.approved.txt new file mode 100644 index 0000000000..0810b19fc5 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.ModelOutput.cs.approved.txt @@ -0,0 +1,75 @@ +// This file was auto-generated by ML.NET Model Builder. + +using System; +using Microsoft.ML.Data; +using System.Collections.Generic; +using System.Linq; + +namespace CodeGenTest.Model +{ + public class ModelOutput + { + public string[] ObjectTags = new string[] { "label1", "label2", "label3", }; + + [ColumnName("boxes")] + public float[] Boxes { get; set; } + + [ColumnName("labels")] + public long[] Labels { get; set; } + + [ColumnName("scores")] + public float[] Scores { get; set; } + + private BoundingBox[] BoundingBoxes + { + get + { + var boundingBoxes = new List(); + + boundingBoxes = Enumerable.Range(0, this.Labels.Length) + .Select((index) => + { + var boxes = this.Boxes; + var scores = this.Scores; + var labels = this.Labels; + + return new BoundingBox() + { + Left = boxes[index * 4], + Top = boxes[(index * 4) + 1], + Right = boxes[(index * 4) + 2], + Bottom = boxes[(index * 4) + 3], + Score = scores[index], + Label = this.ObjectTags[labels[index]], + }; + }).ToList(); + return boundingBoxes.ToArray(); + } + } + + public override string ToString() + { + return string.Join("\n", BoundingBoxes.Select(x => x.ToString())); + } + } + + public class BoundingBox + { + public float Top; + + public float Left; + + public float Right; + + public float Bottom; + + public string Label; + + public float Score; + + public override string ToString() + { + return $"Top: {this.Top}, Left: {this.Left}, Right: {this.Right}, Bottom: {this.Bottom}, Label: {this.Label}, Score: {this.Score}"; + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.Program.cs.approved.txt new file mode 100644 index 0000000000..97c0edc623 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureObjectDetectionCodeGeneratorTest.Program.cs.approved.txt @@ -0,0 +1,24 @@ +// This file was auto-generated by ML.NET Model Builder. + +using System; +using CodeGenTest.Model; + +namespace CodeGenTest.ConsoleApp +{ + class Program + { + static void Main(string[] args) + { + // Create single instance of sample data from first line of dataset for model input + ModelInput sampleData = new ModelInput(); + + // Make a single prediction on the sample data and print results + var predictionResult = ConsumeModel.Predict(sampleData); + + Console.WriteLine("\n\nPredicted Boxes:\n"); + Console.WriteLine(predictionResult); + Console.WriteLine("=============== End of process, hit any key to finish ==============="); + Console.ReadKey(); + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionCSFileContentTest.approved.txt index ab96c8c8d9..c87448f3da 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionCSFileContentTest.approved.txt @@ -15,7 +15,6 @@ namespace TestNamespace.Model // its default value, which is the name of the field. [ColumnName("PredictedLabel")] public bool Prediction { get; set; } - public float Score { get; set; } } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.cs b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.cs index aa1ecc9c2a..32558e8bf8 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.cs @@ -227,13 +227,54 @@ public void AzureImageCodeGeneratorTest() OnnxModelName = @"/path/to/onnxModel", OnnxRuntimePacakgeVersion = "1.2.3", IsAzureAttach = true, + IsObjectDetection = false, IsImage = true, }; var codeGen = new AzureAttachCodeGenenrator(pipeline, columnInference, setting); foreach (var project in codeGen.ToSolution()) { - foreach(var projectFile in project) + foreach (var projectFile in project) + { + NamerFactory.AdditionalInformation = projectFile.Name; + Approvals.Verify(((ICSharpFile)projectFile).File); + } + } + } + + + // Tevin: added to test OD codeGen working + [Fact] + [UseReporter(typeof(DiffReporter))] + [MethodImpl(MethodImplOptions.NoInlining)] + public void AzureObjectDetectionCodeGeneratorTest() + { + (var pipeline, var columnInference) = GetMockedAzureObjectDetectionPipelineAndInference(@"/path/to/onnxModel"); + var setting = new CodeGeneratorSettings() + { + TrainDataset = @"/path/to/dataset", + ModelName = @"/path/to/model", + MlTask = TaskKind.ObjectDetection, + OutputName = @"CodeGenTest", + OutputBaseDir = @"/path/to/codegen", + LabelName = "Label", + Target = GenerateTarget.ModelBuilder, + StablePackageVersion = "stableversion", + UnstablePackageVersion = "unstableversion", + OnnxModelName = @"/path/to/onnxModel", + OnnxRuntimePacakgeVersion = @"1.2.3", + IsAzureAttach = true, + IsImage = false, + IsObjectDetection = true, + ClassificationLabel = new string[] { "label1", "label2", "label3" }, + ObjectLabel = new string[] { "label1", "label2", "label3" }, + }; + var codeGen = new AzureAttachCodeGenenrator(pipeline, columnInference, setting); + //codeGen.GenerateOutput(); // use this to see output in project. + foreach (var project in codeGen.ToSolution()) + { + foreach (var projectFile in project) { + NamerFactory.AdditionalInformation = projectFile.Name; Approvals.Verify(((ICSharpFile)projectFile).File); } @@ -263,6 +304,7 @@ public void AzureCodeGeneratorTest() OnnxRuntimePacakgeVersion = "1.2.3", IsAzureAttach = true, IsImage = false, + IsObjectDetection = false, OnnxInputMapping = mapping, }; var codeGen = new AzureAttachCodeGenenrator(pipeline, columnInference, setting); @@ -620,6 +662,55 @@ private CodeGenerator PrepareForRecommendationTask() return (_mockedPipeline, _columnInference); } + private (Pipeline, ColumnInferenceResults) GetMockedAzureObjectDetectionPipelineAndInference(string onnxModelPath) + { + var onnxPipeLineNode = new PipelineNode( + nameof(SpecialTransformer.ApplyOnnxModel), + PipelineNodeType.Transform, + new string[] { }, + new string[] { }, + null); + var loadImageNode = new PipelineNode(EstimatorName.ImageLoading.ToString(), PipelineNodeType.Transform, "ImagePath", "input"); + var resizeImageNode = new PipelineNode( + nameof(SpecialTransformer.ObjectDetectionResizeImage), + PipelineNodeType.Transform, + "input", + "input", + new Dictionary() + { + { "imageWidth", 800 }, + { "imageHeight", 600 }, + }); + var extractPixelsNode = new PipelineNode(nameof(SpecialTransformer.ExtractPixel), PipelineNodeType.Transform, "input", "input"); + var bestPipeLine = new Pipeline(new PipelineNode[] + { + loadImageNode, + resizeImageNode, + extractPixelsNode, + onnxPipeLineNode, + }); + + var textLoaderArgs = new TextLoader.Options() + { + Columns = new[] { + new TextLoader.Column("Label", DataKind.String, 0), + new TextLoader.Column("ImagePath", DataKind.String, 1), // 0? + }, + AllowQuoting = true, + AllowSparse = true, + HasHeader = true, + Separators = new[] { '\t' } + }; + + var columnInference = new ColumnInferenceResults() + { + TextLoaderOptions = textLoaderArgs, + ColumnInformation = new ColumnInformation() { LabelColumnName = "Label" } + }; + + return (bestPipeLine, columnInference); + } + private (Pipeline, ColumnInferenceResults) GetMockedRankingPipelineAndInference() { if (_mockedPipeline == null) diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestAzureObjectDetectionModelOutputClass_WithLabel.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestAzureObjectDetectionModelOutputClass_WithLabel.approved.txt new file mode 100644 index 0000000000..45519fa4a8 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestAzureObjectDetectionModelOutputClass_WithLabel.approved.txt @@ -0,0 +1,79 @@ +//***************************************************************************************** +//* * +//* This is an auto-generated file by Microsoft ML.NET CLI (Command-Line Interface) tool. * +//* * +//***************************************************************************************** + +using System; +using Microsoft.ML.Data; +using System.Collections.Generic; +using System.Linq; + +namespace Namespace.Model +{ + public class ModelOutput + { + public string[] ObjectTags = new string[]{"str1","str2","str3",}; + + [ColumnName("boxes")] + public float[] Boxes { get; set; } + + [ColumnName("labels")] + public long[] Labels { get; set; } + + [ColumnName("scores")] + public float[] Scores { get; set; } + + private BoundingBox[] BoundingBoxes + { + get + { + var boundingBoxes = new List(); + + boundingBoxes = Enumerable.Range(0, this.Labels.Length) + .Select((index) => + { + var boxes = this.Boxes; + var scores = this.Scores; + var labels = this.Labels; + + return new BoundingBox() + { + Left = boxes[index * 4], + Top = boxes[(index * 4) + 1], + Right = boxes[(index * 4) + 2], + Bottom = boxes[(index * 4) + 3], + Score = scores[index], + Label = this.ObjectTags[labels[index]], + }; + }).ToList(); + return boundingBoxes.ToArray(); + } + } + + public override string ToString() + { + return string.Join("\n", BoundingBoxes.Select(x => x.ToString())); + } + } + + public class BoundingBox + { + public float Top; + + public float Left; + + public float Right; + + public float Bottom; + + public string Label; + + public float Score; + + public override string ToString() + { + return $"Top: {this.Top}, Left: {this.Left}, Right: {this.Right}, Bottom: {this.Bottom}, Label: {this.Label}, Score: {this.Score}"; + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestConsumeModel_AzureObjectDetection.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestConsumeModel_AzureObjectDetection.approved.txt new file mode 100644 index 0000000000..a4c874b77b --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestConsumeModel_AzureObjectDetection.approved.txt @@ -0,0 +1,41 @@ +// This file was auto-generated by ML.NET Model Builder. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Microsoft.ML; +using Namespace.Model; + +namespace Namespace.Model +{ + public class ConsumeModel + { + private static Lazy> PredictionEngine = new Lazy>(CreatePredictionEngine); + + public static string MLNetModelPath = Path.GetFullPath("mlmodel.zip"); + + public static string OnnxModelPath = Path.GetFullPath("onnx.onnx"); + + // For more info on consuming ML.NET models, visit https://aka.ms/mlnet-consume + // Method for consuming model in your app + public static ModelOutput Predict(ModelInput input) + { + ModelOutput result = PredictionEngine.Value.Predict(input); + return result; + } + + public static PredictionEngine CreatePredictionEngine() + { + // Create new MLContext + MLContext mlContext = new MLContext(); + + // Load model & create prediction engine + ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var modelInputSchema); + var predEngine = mlContext.Model.CreatePredictionEngine(mlModel); + + return predEngine; + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestConsumeModel_NotAzureImage_NotObjectDetection.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestConsumeModel_NotAzureImage_NotObjectDetection.approved.txt new file mode 100644 index 0000000000..b1e4146d53 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestConsumeModel_NotAzureImage_NotObjectDetection.approved.txt @@ -0,0 +1,39 @@ +// This file was auto-generated by ML.NET Model Builder. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using Microsoft.ML; +using Namespace.Model; + +namespace Namespace.Model +{ + public class ConsumeModel + { + private static Lazy> PredictionEngine = new Lazy>(CreatePredictionEngine); + + public static string MLNetModelPath = Path.GetFullPath("mlmodel.zip"); + + // For more info on consuming ML.NET models, visit https://aka.ms/mlnet-consume + // Method for consuming model in your app + public static ModelOutput Predict(ModelInput input) + { + ModelOutput result = PredictionEngine.Value.Predict(input); + return result; + } + + public static PredictionEngine CreatePredictionEngine() + { + // Create new MLContext + MLContext mlContext = new MLContext(); + + // Load model & create prediction engine + ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var modelInputSchema); + var predEngine = mlContext.Model.CreatePredictionEngine(mlModel); + + return predEngine; + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt index a0ae7f5bef..2a64562680 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt @@ -20,7 +20,7 @@ namespace Namespace.ConsoleApp // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); - Console.WriteLine("Using model to make single prediction -- Comparing actual LabelName with predicted LabelName from sample data...\n\n"); + Console.WriteLine("Using model to make single prediction -- Comparing actual LabelName with predicted LabelName from sample data...\n\n"); Console.WriteLine($"key1: {sampleData.key1}"); Console.WriteLine($"key2: {sampleData.key2}"); Console.WriteLine($"key3: {sampleData.key3}"); diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs index e5006ac9c9..b87ed8d1aa 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs @@ -44,12 +44,13 @@ public void TestPredictProgram_WithSampleData() [Fact] [UseReporter(typeof(DiffReporter))] [MethodImpl(MethodImplOptions.NoInlining)] - public void TestConsumeModel_NonAzureImage() + public void TestConsumeModel_NotAzureImage_NotObjectDetection() { var consumeModel = new ConsumeModel() { Namespace = "Namespace", IsAzureImage = false, + IsAzureObjectDetection = false, MLNetModelName = @"mlmodel.zip", }; @@ -65,6 +66,24 @@ public void TestConsumeModel_AzureImage() { Namespace = "Namespace", IsAzureImage = true, + IsAzureObjectDetection = false, + MLNetModelName = @"mlmodel.zip", + OnnxModelName = "onnx.onnx" + }; + + Approvals.Verify(consumeModel.TransformText()); + } + + [Fact] + [UseReporter(typeof(DiffReporter))] + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestConsumeModel_AzureObjectDetection() + { + var consumeModel = new ConsumeModel() + { + Namespace = "Namespace", + IsAzureImage = false, + IsAzureObjectDetection = true, MLNetModelName = @"mlmodel.zip", OnnxModelName = "onnx.onnx" }; @@ -87,6 +106,21 @@ public void TestAzureImageModelOutputClass_WithLabel() Approvals.Verify(azureImageOutput.TransformText()); } + [Fact] + [UseReporter(typeof(DiffReporter))] + [MethodImpl(MethodImplOptions.NoInlining)] + public void TestAzureObjectDetectionModelOutputClass_WithLabel() + { + var azureImageOutput = new AzureObjectDetectionModelOutputClass + { + Namespace = "Namespace", + Labels = new string[] { "str1", "str2", "str3" }, + Target = CSharp.GenerateTarget.Cli + }; + + Approvals.Verify(azureImageOutput.TransformText()); + } + [Fact] [UseReporter(typeof(DiffReporter))] [MethodImpl(MethodImplOptions.NoInlining)]