profile influences configuration
This commit is contained in:
EugeneTes
2026-05-15 11:54:36 +02:00
parent 2be13501fa
commit 8704d0b5ac
42 changed files with 2467 additions and 1023 deletions

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Hawkeye.VisionBuilder.UI.RecipeYoloPatcher</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.6.0.20220608" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='CPU'">
<PackageReference Include="YoloV8" Version="4.1.5" />
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.18.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'!='CPU'">
<PackageReference Include="YoloV8.Gpu" Version="4.1.7" />
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu" Version="1.18.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hawkeye.VisionBuilder.Workflow\Hawkeye.VisionBuilder.Workflow.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,399 @@
#nullable disable
namespace Hawkeye.VisionBuilder.UI.RecipeYoloPatcher
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.lblModel = new System.Windows.Forms.Label();
this.txtModelPath = new System.Windows.Forms.TextBox();
this.btnBrowseModel = new System.Windows.Forms.Button();
this.btnLoadClasses = new System.Windows.Forms.Button();
this.lblClasses = new System.Windows.Forms.Label();
this.lstClasses = new System.Windows.Forms.CheckedListBox();
this.lblFiles = new System.Windows.Forms.Label();
this.lstFiles = new System.Windows.Forms.ListBox();
this.btnAddFiles = new System.Windows.Forms.Button();
this.btnRemoveFiles = new System.Windows.Forms.Button();
this.btnClearFiles = new System.Windows.Forms.Button();
this.grpBounds = new System.Windows.Forms.GroupBox();
this.lblMinArea = new System.Windows.Forms.Label();
this.numMinArea = new System.Windows.Forms.NumericUpDown();
this.lblMaxArea = new System.Windows.Forms.Label();
this.numMaxArea = new System.Windows.Forms.NumericUpDown();
this.lblMinWidth = new System.Windows.Forms.Label();
this.numMinWidth = new System.Windows.Forms.NumericUpDown();
this.lblMaxWidth = new System.Windows.Forms.Label();
this.numMaxWidth = new System.Windows.Forms.NumericUpDown();
this.lblMinHeight = new System.Windows.Forms.Label();
this.numMinHeight = new System.Windows.Forms.NumericUpDown();
this.lblMaxHeight = new System.Windows.Forms.Label();
this.numMaxHeight = new System.Windows.Forms.NumericUpDown();
this.btnApply = new System.Windows.Forms.Button();
this.btnStandardize = new System.Windows.Forms.Button();
this.txtLog = new System.Windows.Forms.TextBox();
this.grpBounds.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numMinArea)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMaxArea)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMinWidth)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMaxWidth)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMinHeight)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMaxHeight)).BeginInit();
this.SuspendLayout();
//
// lblModel
//
this.lblModel.AutoSize = true;
this.lblModel.Location = new System.Drawing.Point(10, 15);
this.lblModel.Name = "lblModel";
this.lblModel.Size = new System.Drawing.Size(46, 15);
this.lblModel.TabIndex = 0;
this.lblModel.Text = "Model:";
//
// txtModelPath
//
this.txtModelPath.Location = new System.Drawing.Point(70, 12);
this.txtModelPath.Name = "txtModelPath";
this.txtModelPath.ReadOnly = true;
this.txtModelPath.Size = new System.Drawing.Size(580, 23);
this.txtModelPath.TabIndex = 1;
//
// btnBrowseModel
//
this.btnBrowseModel.Location = new System.Drawing.Point(660, 10);
this.btnBrowseModel.Name = "btnBrowseModel";
this.btnBrowseModel.Size = new System.Drawing.Size(110, 25);
this.btnBrowseModel.TabIndex = 2;
this.btnBrowseModel.Text = "Browse...";
this.btnBrowseModel.UseVisualStyleBackColor = true;
this.btnBrowseModel.Click += new System.EventHandler(this.BtnBrowseModel_Click);
//
// btnLoadClasses
//
this.btnLoadClasses.Enabled = false;
this.btnLoadClasses.Location = new System.Drawing.Point(10, 45);
this.btnLoadClasses.Name = "btnLoadClasses";
this.btnLoadClasses.Size = new System.Drawing.Size(130, 25);
this.btnLoadClasses.TabIndex = 3;
this.btnLoadClasses.Text = "Load classes";
this.btnLoadClasses.UseVisualStyleBackColor = true;
this.btnLoadClasses.Click += new System.EventHandler(this.BtnLoadClasses_Click);
//
// lblClasses
//
this.lblClasses.AutoSize = true;
this.lblClasses.Location = new System.Drawing.Point(10, 85);
this.lblClasses.Name = "lblClasses";
this.lblClasses.Size = new System.Drawing.Size(228, 15);
this.lblClasses.TabIndex = 4;
this.lblClasses.Text = "Classes (check the ones to add/update):";
//
// lstClasses
//
this.lstClasses.CheckOnClick = true;
this.lstClasses.FormattingEnabled = true;
this.lstClasses.IntegralHeight = false;
this.lstClasses.Location = new System.Drawing.Point(10, 105);
this.lstClasses.Name = "lstClasses";
this.lstClasses.Size = new System.Drawing.Size(760, 180);
this.lstClasses.TabIndex = 5;
//
// lblFiles
//
this.lblFiles.AutoSize = true;
this.lblFiles.Location = new System.Drawing.Point(10, 295);
this.lblFiles.Name = "lblFiles";
this.lblFiles.Size = new System.Drawing.Size(173, 15);
this.lblFiles.TabIndex = 6;
this.lblFiles.Text = "Recipe files (*.hrcp, *.jhrcp):";
//
// lstFiles
//
this.lstFiles.FormattingEnabled = true;
this.lstFiles.HorizontalScrollbar = true;
this.lstFiles.IntegralHeight = false;
this.lstFiles.ItemHeight = 15;
this.lstFiles.Location = new System.Drawing.Point(10, 315);
this.lstFiles.Name = "lstFiles";
this.lstFiles.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended;
this.lstFiles.Size = new System.Drawing.Size(660, 130);
this.lstFiles.TabIndex = 7;
//
// btnAddFiles
//
this.btnAddFiles.Location = new System.Drawing.Point(680, 315);
this.btnAddFiles.Name = "btnAddFiles";
this.btnAddFiles.Size = new System.Drawing.Size(90, 25);
this.btnAddFiles.TabIndex = 8;
this.btnAddFiles.Text = "Add files...";
this.btnAddFiles.UseVisualStyleBackColor = true;
this.btnAddFiles.Click += new System.EventHandler(this.BtnAddFiles_Click);
//
// btnRemoveFiles
//
this.btnRemoveFiles.Location = new System.Drawing.Point(680, 345);
this.btnRemoveFiles.Name = "btnRemoveFiles";
this.btnRemoveFiles.Size = new System.Drawing.Size(90, 25);
this.btnRemoveFiles.TabIndex = 9;
this.btnRemoveFiles.Text = "Remove";
this.btnRemoveFiles.UseVisualStyleBackColor = true;
this.btnRemoveFiles.Click += new System.EventHandler(this.BtnRemoveFiles_Click);
//
// btnClearFiles
//
this.btnClearFiles.Location = new System.Drawing.Point(680, 375);
this.btnClearFiles.Name = "btnClearFiles";
this.btnClearFiles.Size = new System.Drawing.Size(90, 25);
this.btnClearFiles.TabIndex = 10;
this.btnClearFiles.Text = "Clear";
this.btnClearFiles.UseVisualStyleBackColor = true;
this.btnClearFiles.Click += new System.EventHandler(this.BtnClearFiles_Click);
//
// grpBounds
//
this.grpBounds.Controls.Add(this.lblMinArea);
this.grpBounds.Controls.Add(this.numMinArea);
this.grpBounds.Controls.Add(this.lblMaxArea);
this.grpBounds.Controls.Add(this.numMaxArea);
this.grpBounds.Controls.Add(this.lblMinWidth);
this.grpBounds.Controls.Add(this.numMinWidth);
this.grpBounds.Controls.Add(this.lblMaxWidth);
this.grpBounds.Controls.Add(this.numMaxWidth);
this.grpBounds.Controls.Add(this.lblMinHeight);
this.grpBounds.Controls.Add(this.numMinHeight);
this.grpBounds.Controls.Add(this.lblMaxHeight);
this.grpBounds.Controls.Add(this.numMaxHeight);
this.grpBounds.Location = new System.Drawing.Point(10, 455);
this.grpBounds.Name = "grpBounds";
this.grpBounds.Size = new System.Drawing.Size(760, 90);
this.grpBounds.TabIndex = 11;
this.grpBounds.TabStop = false;
this.grpBounds.Text = "Bounds (applied to selected classes)";
//
// lblMinArea
//
this.lblMinArea.AutoSize = true;
this.lblMinArea.Location = new System.Drawing.Point(10, 20);
this.lblMinArea.Name = "lblMinArea";
this.lblMinArea.Size = new System.Drawing.Size(56, 15);
this.lblMinArea.TabIndex = 0;
this.lblMinArea.Text = "Min Area";
//
// numMinArea
//
this.numMinArea.Location = new System.Drawing.Point(10, 40);
this.numMinArea.Maximum = new decimal(new int[] { 9999999, 0, 0, 0 });
this.numMinArea.Name = "numMinArea";
this.numMinArea.Size = new System.Drawing.Size(110, 23);
this.numMinArea.TabIndex = 1;
//
// lblMaxArea
//
this.lblMaxArea.AutoSize = true;
this.lblMaxArea.Location = new System.Drawing.Point(130, 20);
this.lblMaxArea.Name = "lblMaxArea";
this.lblMaxArea.Size = new System.Drawing.Size(59, 15);
this.lblMaxArea.TabIndex = 2;
this.lblMaxArea.Text = "Max Area";
//
// numMaxArea
//
this.numMaxArea.Location = new System.Drawing.Point(130, 40);
this.numMaxArea.Maximum = new decimal(new int[] { 9999999, 0, 0, 0 });
this.numMaxArea.Name = "numMaxArea";
this.numMaxArea.Size = new System.Drawing.Size(110, 23);
this.numMaxArea.TabIndex = 3;
this.numMaxArea.Value = new decimal(new int[] { 99999, 0, 0, 0 });
//
// lblMinWidth
//
this.lblMinWidth.AutoSize = true;
this.lblMinWidth.Location = new System.Drawing.Point(260, 20);
this.lblMinWidth.Name = "lblMinWidth";
this.lblMinWidth.Size = new System.Drawing.Size(64, 15);
this.lblMinWidth.TabIndex = 4;
this.lblMinWidth.Text = "Min Width";
//
// numMinWidth
//
this.numMinWidth.Location = new System.Drawing.Point(260, 40);
this.numMinWidth.Maximum = new decimal(new int[] { 9999999, 0, 0, 0 });
this.numMinWidth.Name = "numMinWidth";
this.numMinWidth.Size = new System.Drawing.Size(110, 23);
this.numMinWidth.TabIndex = 5;
//
// lblMaxWidth
//
this.lblMaxWidth.AutoSize = true;
this.lblMaxWidth.Location = new System.Drawing.Point(390, 20);
this.lblMaxWidth.Name = "lblMaxWidth";
this.lblMaxWidth.Size = new System.Drawing.Size(67, 15);
this.lblMaxWidth.TabIndex = 6;
this.lblMaxWidth.Text = "Max Width";
//
// numMaxWidth
//
this.numMaxWidth.Location = new System.Drawing.Point(390, 40);
this.numMaxWidth.Maximum = new decimal(new int[] { 9999999, 0, 0, 0 });
this.numMaxWidth.Name = "numMaxWidth";
this.numMaxWidth.Size = new System.Drawing.Size(110, 23);
this.numMaxWidth.TabIndex = 7;
this.numMaxWidth.Value = new decimal(new int[] { 99999, 0, 0, 0 });
//
// lblMinHeight
//
this.lblMinHeight.AutoSize = true;
this.lblMinHeight.Location = new System.Drawing.Point(520, 20);
this.lblMinHeight.Name = "lblMinHeight";
this.lblMinHeight.Size = new System.Drawing.Size(67, 15);
this.lblMinHeight.TabIndex = 8;
this.lblMinHeight.Text = "Min Height";
//
// numMinHeight
//
this.numMinHeight.Location = new System.Drawing.Point(520, 40);
this.numMinHeight.Maximum = new decimal(new int[] { 9999999, 0, 0, 0 });
this.numMinHeight.Name = "numMinHeight";
this.numMinHeight.Size = new System.Drawing.Size(110, 23);
this.numMinHeight.TabIndex = 9;
//
// lblMaxHeight
//
this.lblMaxHeight.AutoSize = true;
this.lblMaxHeight.Location = new System.Drawing.Point(640, 20);
this.lblMaxHeight.Name = "lblMaxHeight";
this.lblMaxHeight.Size = new System.Drawing.Size(70, 15);
this.lblMaxHeight.TabIndex = 10;
this.lblMaxHeight.Text = "Max Height";
//
// numMaxHeight
//
this.numMaxHeight.Location = new System.Drawing.Point(640, 40);
this.numMaxHeight.Maximum = new decimal(new int[] { 9999999, 0, 0, 0 });
this.numMaxHeight.Name = "numMaxHeight";
this.numMaxHeight.Size = new System.Drawing.Size(110, 23);
this.numMaxHeight.TabIndex = 11;
this.numMaxHeight.Value = new decimal(new int[] { 99999, 0, 0, 0 });
//
// btnApply
//
this.btnApply.Location = new System.Drawing.Point(10, 555);
this.btnApply.Name = "btnApply";
this.btnApply.Size = new System.Drawing.Size(375, 32);
this.btnApply.TabIndex = 12;
this.btnApply.Text = "Apply";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.BtnApply_Click);
//
// btnStandardize
//
this.btnStandardize.Location = new System.Drawing.Point(395, 555);
this.btnStandardize.Name = "btnStandardize";
this.btnStandardize.Size = new System.Drawing.Size(375, 32);
this.btnStandardize.TabIndex = 13;
this.btnStandardize.Text = "Standardize names";
this.btnStandardize.UseVisualStyleBackColor = true;
this.btnStandardize.Click += new System.EventHandler(this.BtnStandardize_Click);
//
// txtLog
//
this.txtLog.Location = new System.Drawing.Point(10, 595);
this.txtLog.Multiline = true;
this.txtLog.Name = "txtLog";
this.txtLog.ReadOnly = true;
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtLog.Size = new System.Drawing.Size(760, 80);
this.txtLog.TabIndex = 14;
this.txtLog.WordWrap = false;
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(784, 690);
this.Controls.Add(this.txtLog);
this.Controls.Add(this.btnStandardize);
this.Controls.Add(this.btnApply);
this.Controls.Add(this.grpBounds);
this.Controls.Add(this.btnClearFiles);
this.Controls.Add(this.btnRemoveFiles);
this.Controls.Add(this.btnAddFiles);
this.Controls.Add(this.lstFiles);
this.Controls.Add(this.lblFiles);
this.Controls.Add(this.lstClasses);
this.Controls.Add(this.lblClasses);
this.Controls.Add(this.btnLoadClasses);
this.Controls.Add(this.btnBrowseModel);
this.Controls.Add(this.txtModelPath);
this.Controls.Add(this.lblModel);
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Recipe Yolo Patcher";
this.grpBounds.ResumeLayout(false);
this.grpBounds.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numMinArea)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMaxArea)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMinWidth)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMaxWidth)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMinHeight)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMaxHeight)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label lblModel;
private System.Windows.Forms.TextBox txtModelPath;
private System.Windows.Forms.Button btnBrowseModel;
private System.Windows.Forms.Button btnLoadClasses;
private System.Windows.Forms.Label lblClasses;
private System.Windows.Forms.CheckedListBox lstClasses;
private System.Windows.Forms.Label lblFiles;
private System.Windows.Forms.ListBox lstFiles;
private System.Windows.Forms.Button btnAddFiles;
private System.Windows.Forms.Button btnRemoveFiles;
private System.Windows.Forms.Button btnClearFiles;
private System.Windows.Forms.GroupBox grpBounds;
private System.Windows.Forms.Label lblMinArea;
private System.Windows.Forms.NumericUpDown numMinArea;
private System.Windows.Forms.Label lblMaxArea;
private System.Windows.Forms.NumericUpDown numMaxArea;
private System.Windows.Forms.Label lblMinWidth;
private System.Windows.Forms.NumericUpDown numMinWidth;
private System.Windows.Forms.Label lblMaxWidth;
private System.Windows.Forms.NumericUpDown numMaxWidth;
private System.Windows.Forms.Label lblMinHeight;
private System.Windows.Forms.NumericUpDown numMinHeight;
private System.Windows.Forms.Label lblMaxHeight;
private System.Windows.Forms.NumericUpDown numMaxHeight;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.Button btnStandardize;
private System.Windows.Forms.TextBox txtLog;
}
}

View File

@@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Compunet.YoloV8;
using Hawkeye.VisionBuilder.Workflow;
using Hawkeye.VisionBuilder.Workflow.Operations.AI;
namespace Hawkeye.VisionBuilder.UI.RecipeYoloPatcher;
public partial class MainForm : Form
{
private sealed class ClassEntry
{
public int Id { get; init; }
public string Name { get; init; } = "";
public override string ToString() => $"{Id}: {Name}";
}
public MainForm()
{
InitializeComponent();
}
private void BtnBrowseModel_Click(object? sender, EventArgs e)
{
using var dialog = new OpenFileDialog
{
Filter = "ONNX models (*.onnx)|*.onnx",
Title = "Select Yolo model"
};
var defaultDir = Path.GetFullPath(@"..\Data\Models");
if (Directory.Exists(defaultDir)) dialog.InitialDirectory = defaultDir;
if (dialog.ShowDialog(this) == DialogResult.OK)
{
txtModelPath.Text = dialog.FileName;
btnLoadClasses.Enabled = true;
lstClasses.Items.Clear();
}
}
private void BtnLoadClasses_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtModelPath.Text) || !File.Exists(txtModelPath.Text))
{
MessageBox.Show(this, "Pick a valid .onnx model first.", "Recipe Yolo Patcher",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
lstClasses.Items.Clear();
Cursor = Cursors.WaitCursor;
try
{
using var predictor = YoloV8Predictor.Create(txtModelPath.Text);
foreach (var cls in predictor.Metadata.Names)
{
lstClasses.Items.Add(new ClassEntry { Id = cls.Id, Name = cls.Name });
}
Log($"Loaded {lstClasses.Items.Count} class(es) from {Path.GetFileName(txtModelPath.Text)}.");
}
catch (Exception ex)
{
MessageBox.Show(this, $"Failed to load model:\r\n{ex.Message}", "Recipe Yolo Patcher",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
Cursor = Cursors.Default;
}
}
private void BtnAddFiles_Click(object? sender, EventArgs e)
{
using var dialog = new OpenFileDialog
{
Filter = "Recipes (*.hrcp;*.jhrcp)|*.hrcp;*.jhrcp",
Multiselect = true,
Title = "Select recipe files"
};
var defaultDir = Path.GetFullPath(@"..\Data\Recipes");
if (Directory.Exists(defaultDir)) dialog.InitialDirectory = defaultDir;
if (dialog.ShowDialog(this) == DialogResult.OK)
{
foreach (var f in dialog.FileNames)
{
if (!lstFiles.Items.Contains(f)) lstFiles.Items.Add(f);
}
}
}
private void BtnRemoveFiles_Click(object? sender, EventArgs e)
{
var selected = lstFiles.SelectedItems.Cast<object>().ToList();
foreach (var item in selected) lstFiles.Items.Remove(item);
}
private void BtnClearFiles_Click(object? sender, EventArgs e) => lstFiles.Items.Clear();
private void BtnApply_Click(object? sender, EventArgs e)
{
var checkedClasses = lstClasses.CheckedItems.Cast<ClassEntry>().ToList();
if (checkedClasses.Count == 0)
{
MessageBox.Show(this, "Check at least one class to apply.", "Recipe Yolo Patcher",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var files = lstFiles.Items.Cast<string>().ToList();
if (files.Count == 0)
{
MessageBox.Show(this, "Add at least one recipe file.", "Recipe Yolo Patcher",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var bounds = new PatchBounds(
(int)numMinArea.Value, (int)numMaxArea.Value,
(int)numMinWidth.Value, (int)numMaxWidth.Value,
(int)numMinHeight.Value, (int)numMaxHeight.Value);
Cursor = Cursors.WaitCursor;
btnApply.Enabled = false;
try
{
foreach (var file in files)
{
try
{
var (added, updated) = PatchRecipe(file, checkedClasses, bounds);
Log($"OK: {Path.GetFileName(file)} — added {added}, updated {updated}");
}
catch (Exception ex)
{
Log($"FAIL: {Path.GetFileName(file)} — {ex.Message}");
}
}
Log("Done.");
}
finally
{
Cursor = Cursors.Default;
btnApply.Enabled = true;
}
}
private void BtnStandardize_Click(object? sender, EventArgs e)
{
var files = lstFiles.Items.Cast<string>().ToList();
if (files.Count == 0)
{
MessageBox.Show(this, "Add at least one recipe file.", "Recipe Yolo Patcher",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
Cursor = Cursors.WaitCursor;
btnApply.Enabled = false;
btnStandardize.Enabled = false;
try
{
foreach (var file in files)
{
try
{
var renamed = StandardizeLabels(file);
Log($"OK: {Path.GetFileName(file)} — renamed {renamed} label(s)");
}
catch (Exception ex)
{
Log($"FAIL: {Path.GetFileName(file)} — {ex.Message}");
}
}
Log("Done.");
}
finally
{
Cursor = Cursors.Default;
btnApply.Enabled = true;
btnStandardize.Enabled = true;
}
}
private static int StandardizeLabels(string path)
{
var (wf, ext) = LoadRecipe(path);
int renamed = 0;
foreach (var op in wf.Operations.OfType<YoloPickDetected>())
{
var newLabel = ToPascalCase(op.Label);
if (newLabel != op.Label)
{
op.Label = newLabel;
renamed++;
}
}
if (renamed > 0) SaveRecipe(wf, path, ext);
return renamed;
}
private static string ToPascalCase(string? label)
{
if (string.IsNullOrWhiteSpace(label)) return label ?? string.Empty;
var parts = label.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
var sb = new StringBuilder(label.Length);
foreach (var p in parts)
{
sb.Append(char.ToUpperInvariant(p[0]));
if (p.Length > 1) sb.Append(p, 1, p.Length - 1);
}
return sb.ToString();
}
private static (WorkflowList wf, string ext) LoadRecipe(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
WorkflowList wf;
if (ext == ".hrcp")
{
wf = new WorkflowList();
using var fs = File.OpenRead(path);
using var br = new BinaryReader(fs);
wf.Load(br, new OperationDiscoveryService(wf));
}
else if (ext == ".jhrcp")
{
wf = WorkflowList.LoadJSONFromFile(path);
}
else
{
throw new InvalidOperationException($"Unsupported extension '{ext}'.");
}
return (wf, ext);
}
private static void SaveRecipe(WorkflowList wf, string path, string ext)
{
if (ext == ".hrcp")
{
using var fs = File.Create(path);
using var bw = new BinaryWriter(fs);
wf.Save(bw);
}
else
{
wf.SaveJSON(path);
}
}
private readonly record struct PatchBounds(int MinArea, int MaxArea, int MinWidth, int MaxWidth, int MinHeight, int MaxHeight);
private static (int added, int updated) PatchRecipe(string path, IReadOnlyList<ClassEntry> classes, PatchBounds b)
{
var (wf, ext) = LoadRecipe(path);
int anchor = -1;
for (int i = 0; i < wf.Operations.Count; i++)
{
if (wf.Operations[i] is YoloPickDetected) anchor = i;
}
if (anchor < 0) anchor = wf.Operations.Count - 1;
var nameMap = wf.Operations.OfType<YoloPickDetected>()
.Where(op => !string.IsNullOrEmpty(op.Label))
.GroupBy(op => op.Label)
.ToDictionary(g => g.Key, g => g.First());
int added = 0, updated = 0;
foreach (var cls in classes)
{
if (nameMap.TryGetValue(cls.Name, out var existing))
{
existing.MinArea = b.MinArea;
existing.MaxArea = b.MaxArea;
existing.MinWidth = b.MinWidth;
existing.MaxWidth = b.MaxWidth;
existing.MinHeight = b.MinHeight;
existing.MaxHeight = b.MaxHeight;
updated++;
}
else
{
var op = new YoloPickDetected
{
Label = cls.Name,
TypeId = cls.Id + 1,
SlotName = "Image",
MinArea = b.MinArea,
MaxArea = b.MaxArea,
MinWidth = b.MinWidth,
MaxWidth = b.MaxWidth,
MinHeight = b.MinHeight,
MaxHeight = b.MaxHeight
};
wf.Operations.Insert(++anchor, op);
nameMap[cls.Name] = op;
added++;
}
}
SaveRecipe(wf, path, ext);
return (added, updated);
}
private void Log(string msg)
{
txtLog.AppendText(msg + Environment.NewLine);
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,14 @@
using System;
using System.Windows.Forms;
namespace Hawkeye.VisionBuilder.UI.RecipeYoloPatcher;
internal static class Program
{
[STAThread]
private static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}