check point
This commit is contained in:
484
.gitignore
vendored
Normal file
484
.gitignore
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from `dotnet new gitignore`
|
||||
|
||||
# dotenv files
|
||||
.env
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
.idea/
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# Vim temporary swap files
|
||||
*.swp
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.6.0.20220608" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Hawkeye.VisionBuilder.Workflow\Hawkeye.VisionBuilder.Workflow.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="gold_b97.hrcp">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
11
Hawkeye.VisionBuilder.UI.RecipeConverter/Program.cs
Normal file
11
Hawkeye.VisionBuilder.UI.RecipeConverter/Program.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Xml;
|
||||
using Hawkeye.VisionBuilder.Workflow;
|
||||
|
||||
WorkflowList workflowList = new WorkflowList();
|
||||
var file = File.OpenRead("gold_b97.hrcp");
|
||||
using (var br = new BinaryReader(file))
|
||||
{
|
||||
workflowList.Load(br,new OperationDiscoveryService(workflowList));
|
||||
}
|
||||
|
||||
|
||||
BIN
Hawkeye.VisionBuilder.UI.RecipeConverter/gold_b97.hrcp
Normal file
BIN
Hawkeye.VisionBuilder.UI.RecipeConverter/gold_b97.hrcp
Normal file
Binary file not shown.
150
Hawkeye.VisionBuilder.UI.RecipeConverter/gold_b97.xhrcp
Normal file
150
Hawkeye.VisionBuilder.UI.RecipeConverter/gold_b97.xhrcp
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,100 @@
|
||||
using OpenCvSharp;
|
||||
using System.Drawing;
|
||||
using Ninject;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
|
||||
{
|
||||
public class EmulationCameraImageSource:IImageSource, IInitializable
|
||||
{
|
||||
private readonly EmulationSettings _settings;
|
||||
private readonly string _name;
|
||||
private string _currentFolder;
|
||||
private string[] _selectedFiles;
|
||||
|
||||
public EmulationCameraImageSource(EmulationSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public async Task<Mat> GetImage(CancellationToken token)
|
||||
{
|
||||
if (_selectedFiles.Length == 0) return null;
|
||||
var id = (_index) % _selectedFiles.Length;
|
||||
if (id == 0)
|
||||
{
|
||||
if (_index > 0)
|
||||
OnLoopOver();
|
||||
if (_settings.SingleRun && _index > 0)
|
||||
{
|
||||
// infinite sleep
|
||||
await Task.Delay(Timeout.Infinite, token);
|
||||
}
|
||||
Console.WriteLine("starting new emulation cycle");
|
||||
await Task.Delay(_settings.CycleDelay, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(_settings.PerImageDelay, token);
|
||||
}
|
||||
|
||||
var path = _selectedFiles[id];
|
||||
_index++;
|
||||
|
||||
|
||||
|
||||
Console.WriteLine($"Getting image {path}");
|
||||
|
||||
return Cv2.ImRead(path);
|
||||
}
|
||||
public void SetFolder(string imagesFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentFolder = imagesFolder;
|
||||
var path = Path.Combine(@"..\Data\Emulation", imagesFolder);
|
||||
_selectedFiles = Directory.GetFiles(path, "*.bmp")
|
||||
.Concat(Directory.GetFiles(path, "*.png"))
|
||||
.Concat(Directory.GetFiles(path, "*.jpg"))
|
||||
.Where(x => !Path.GetFileNameWithoutExtension(x).Contains("_analysis"))
|
||||
.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFolderAbsolute(string imagesFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentFolder = imagesFolder;
|
||||
var path = imagesFolder;
|
||||
_selectedFiles = Directory.GetFiles(path, "*.bmp")
|
||||
.Concat(Directory.GetFiles(path, "*.png"))
|
||||
.Concat(Directory.GetFiles(path, "*.jpg"))
|
||||
.Where(x => !Path.GetFileNameWithoutExtension(x).Contains("_analysis"))
|
||||
.ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public event Action OnLoopOver = delegate { };
|
||||
private int _index = 0;
|
||||
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if(string.IsNullOrEmpty(_settings.EmulationPath)) return;
|
||||
SetFolderAbsolute(_settings.EmulationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Inspectron.Settings;
|
||||
using VisionBuilder.UI.Common;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.UI.Sources.Emulation;
|
||||
|
||||
public class EmulationSettings : ISettings
|
||||
{
|
||||
public string CameraName { get; }
|
||||
|
||||
public EmulationSettings(string cameraName)
|
||||
{
|
||||
CameraName = cameraName;
|
||||
}
|
||||
|
||||
public int CycleDelay { get; set; } = 1;
|
||||
public int PerImageDelay { get; set; } = 5000;
|
||||
public bool SingleRun { get; set; } = false;
|
||||
public string EmulationPath { get; set; }
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => CycleDelay, CameraName+"/Emulation", nameof(CycleDelay));
|
||||
settings.RegisterSimple(this, () => PerImageDelay, CameraName + "/Emulation", nameof(PerImageDelay));
|
||||
settings.RegisterSimple(this, () => SingleRun, CameraName + "/Emulation", nameof(SingleRun));
|
||||
settings.RegisterSimple(this, () => EmulationPath, CameraName + "/Emulation", nameof(EmulationPath));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
1704
Hawkeye.VisionBuilder.Workflow/Annotations.cs
Normal file
1704
Hawkeye.VisionBuilder.Workflow/Annotations.cs
Normal file
File diff suppressed because it is too large
Load Diff
284
Hawkeye.VisionBuilder.Workflow/BaseOperation.cs
Normal file
284
Hawkeye.VisionBuilder.Workflow/BaseOperation.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using Microsoft.Scripting.Hosting;
|
||||
using Ninject.Infrastructure.Language;
|
||||
using OpenCvSharp;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using RectangleElement = Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle.RectangleElement;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow
|
||||
{
|
||||
public abstract class BaseOperation
|
||||
{
|
||||
private static readonly ScriptScope _scope;
|
||||
private static readonly ScriptEngine _engine;
|
||||
|
||||
static BaseOperation()
|
||||
{
|
||||
_engine = IronPython.Hosting.Python.CreateEngine();
|
||||
_scope = _engine.CreateScope();
|
||||
}
|
||||
protected BaseOperation()
|
||||
{
|
||||
TypeName = MakeOperationName(this.GetType().Name);
|
||||
Label = MakeOperationName(this.GetType().Name,false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void SetScriptValue<T>(string name, T value)
|
||||
{
|
||||
_scope.SetVariable(name,value);
|
||||
}
|
||||
|
||||
public static T GetScriptValue<T>(ScriptValue scriptValue)
|
||||
{
|
||||
return _engine.Execute<T>(scriptValue.Script,_scope);
|
||||
}
|
||||
[NotForTool]
|
||||
public bool CanHaveProcessingError { get; set; } = true;
|
||||
[NotForTool]
|
||||
public string Status { get; set; }
|
||||
|
||||
protected bool CheckImageExists(Context context)
|
||||
{
|
||||
if (context.ActiveImage == null)
|
||||
{
|
||||
Status = "No image available";
|
||||
Result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool CheckColorful(Context context)
|
||||
{
|
||||
if (context.ActiveImage.ImageData.Type() != MatType.CV_8UC3)
|
||||
{
|
||||
Status="Image is not colorful";
|
||||
Result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool CheckGrayscale(Context context)
|
||||
{
|
||||
if (context.ActiveImage.ImageData.Type() != MatType.CV_8UC1)
|
||||
{
|
||||
Status="Image is not grayscale";
|
||||
Result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
[NotForTool]
|
||||
public bool NeedsUpdate { get; set; } = true;
|
||||
[NotForTool]
|
||||
public Guid Id { get; protected set; }= Guid.NewGuid();
|
||||
public static string MakeOperationName(string typeName,bool useSpaces=true)
|
||||
{
|
||||
return Regex.Replace(typeName.Replace("Operation", ""), "(\\B[A-Z]+?(?=[A-Z][^A-Z])|\\B[A-Z]+?(?=[^A-Z]))", (useSpaces?" ":"")+"$1");
|
||||
}
|
||||
|
||||
public bool Result { get; set; } = true;
|
||||
public string ResultString
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ToString();
|
||||
}
|
||||
}
|
||||
[NotForTool]
|
||||
public string TypeName { get; private set; }
|
||||
[NotForTool]
|
||||
public string Label { get; set; }
|
||||
|
||||
public TimeSpan ExecutionTime { get; set; }
|
||||
public string ExecutionTimeString
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((int)ExecutionTime.TotalMilliseconds) + " ms";
|
||||
}
|
||||
}
|
||||
|
||||
public void Interpret(Context context)
|
||||
{
|
||||
|
||||
|
||||
Result = true;
|
||||
Status = "";
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
InterpretInternal(context);
|
||||
sw.Stop();
|
||||
ExecutionTime = sw.Elapsed;
|
||||
_scope.SetVariable(Label,this);
|
||||
|
||||
}
|
||||
|
||||
protected abstract void InterpretInternal(Context context);
|
||||
|
||||
public Dictionary<string, object> GetParameters()
|
||||
{
|
||||
|
||||
var props = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
|
||||
.Where(x=>!x.HasAttribute(typeof(NotForToolAttribute)));
|
||||
return props.ToDictionary(x => x.Name, x => x.GetValue(this)!);
|
||||
}
|
||||
|
||||
public virtual void SetParameters(Dictionary<string, object> parameters)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> pair in parameters)
|
||||
{
|
||||
this.GetType().GetProperty(pair.Key,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
|
||||
?.SetValue(this,pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void SaveXML(XmlWriter writer)
|
||||
{
|
||||
writer.WriteAttributeString("Id", Id.ToString());
|
||||
writer.WriteAttributeString("Label", Label);
|
||||
|
||||
// Save all public properties that are not marked with NotForTool
|
||||
var properties = GetParameters();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.Value != null)
|
||||
{
|
||||
writer.WriteStartElement("Property");
|
||||
writer.WriteAttributeString("Name", property.Key);
|
||||
writer.WriteAttributeString("Type", property.Value.GetType().FullName);
|
||||
|
||||
// Handle different types appropriately
|
||||
if (property.Value is string || property.Value.GetType().IsPrimitive || property.Value.GetType().IsEnum)
|
||||
{
|
||||
writer.WriteString(property.Value.ToString());
|
||||
}
|
||||
else if (property.Value is Guid guid)
|
||||
{
|
||||
writer.WriteString(guid.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// For complex types, convert to string representation
|
||||
writer.WriteString(property.Value.ToString());
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void LoadXML(XmlReader reader)
|
||||
{
|
||||
if (reader.GetAttribute("Id") != null)
|
||||
Id = new Guid(reader.GetAttribute("Id"));
|
||||
if (reader.GetAttribute("Label") != null)
|
||||
Label = reader.GetAttribute("Label");
|
||||
|
||||
var properties = new Dictionary<string, object>();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Property")
|
||||
{
|
||||
string propertyName = reader.GetAttribute("Name");
|
||||
string propertyType = reader.GetAttribute("Type");
|
||||
string propertyValue = reader.ReadElementContentAsString();
|
||||
|
||||
if (!string.IsNullOrEmpty(propertyName) && !string.IsNullOrEmpty(propertyValue))
|
||||
{
|
||||
// Convert string value back to appropriate type
|
||||
object convertedValue = ConvertFromString(propertyValue, propertyType);
|
||||
if (convertedValue != null)
|
||||
{
|
||||
properties[propertyName] = convertedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == GetOperationElementName())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.Count > 0)
|
||||
{
|
||||
SetParameters(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetOperationElementName()
|
||||
{
|
||||
return this.GetType().Name;
|
||||
}
|
||||
|
||||
private object ConvertFromString(string value, string typeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = Type.GetType(typeName);
|
||||
if (type == null) return value;
|
||||
|
||||
if (type == typeof(string))
|
||||
return value;
|
||||
else if (type == typeof(int))
|
||||
return int.Parse(value);
|
||||
else if (type == typeof(double))
|
||||
return double.Parse(value);
|
||||
else if (type == typeof(float))
|
||||
return float.Parse(value);
|
||||
else if (type == typeof(bool))
|
||||
return bool.Parse(value);
|
||||
else if (type == typeof(Guid))
|
||||
return new Guid(value);
|
||||
else if (type.IsEnum)
|
||||
return Enum.Parse(type, value);
|
||||
else
|
||||
return value; // Fallback to string
|
||||
}
|
||||
catch
|
||||
{
|
||||
return value; // Fallback to string if conversion fails
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
public virtual void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Id.ToString());
|
||||
bw.Write(Label);
|
||||
}
|
||||
|
||||
public virtual void Load(BinaryReader br)
|
||||
{
|
||||
Id = new Guid(br.ReadString());
|
||||
Label = br.ReadString();
|
||||
}
|
||||
|
||||
public void SetError(string eMessage)
|
||||
{
|
||||
Status=eMessage;
|
||||
Result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Hawkeye.VisionBuilder.Workflow/BaseOperationDecorator.cs
Normal file
24
Hawkeye.VisionBuilder.Workflow/BaseOperationDecorator.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow;
|
||||
|
||||
public class BaseOperationDecorator
|
||||
{
|
||||
private readonly BaseOperation _baseOperation;
|
||||
|
||||
public BaseOperationDecorator(BaseOperation baseOperation)
|
||||
{
|
||||
_baseOperation = baseOperation;
|
||||
}
|
||||
|
||||
public bool Result => Operation.Result;
|
||||
public string ResultString => Operation.ToString();
|
||||
public string TypeName => Operation.TypeName;
|
||||
public string Label
|
||||
{
|
||||
get => Operation.Label;
|
||||
set => Operation.Label = value;
|
||||
}
|
||||
|
||||
public string ExecutionTimeString => Operation.ExecutionTimeString;
|
||||
|
||||
public BaseOperation Operation => _baseOperation;
|
||||
}
|
||||
157
Hawkeye.VisionBuilder.Workflow/CLAUDE.md
Normal file
157
Hawkeye.VisionBuilder.Workflow/CLAUDE.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Hawkeye.VisionBuilder.Workflow is a computer vision workflow system built on .NET 8.0 that processes images through configurable operation pipelines. The system supports both CPU and GPU configurations with ONNX runtime for AI models.
|
||||
|
||||
## Build Configuration
|
||||
|
||||
The project uses conditional package references based on configuration:
|
||||
- **CPU Configuration**: Uses `YoloV8` (4.1.5) and `Microsoft.ML.OnnxRuntime` (1.18.0)
|
||||
- **GPU Configuration**: Uses `YoloV8.Gpu` (4.1.7) and `Microsoft.ML.OnnxRuntime.Gpu` (1.18.0)
|
||||
|
||||
Build the project with:
|
||||
```bash
|
||||
dotnet build -c Debug # For GPU configuration
|
||||
dotnet build -c CPU # For CPU-only configuration
|
||||
```
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Operation System
|
||||
- **BaseOperation**: Abstract base class for all image processing operations. All operations inherit from this and implement `InterpretInternal(Context context)`
|
||||
- **Context**: Holds the current processing state including `ActiveImage`, `LastCameraImage`, `GraphicsElements`, and image `Memory`
|
||||
- **OperationDiscoveryService**: Dynamically discovers and instantiates operations using reflection and Ninject DI
|
||||
|
||||
### Operation Categories
|
||||
Operations are organized in folders by category:
|
||||
- `AI/`: AI model operations (YOLO, ONNX models, color classification)
|
||||
- `Simple/`: Basic operations (blob detection, edge finding)
|
||||
- `Filters/`: Image filtering operations
|
||||
- `Morphology/`: Morphological operations (dilation, erosion, etc.)
|
||||
- `Image/`: Image manipulation (convert, channels, etc.)
|
||||
- `Basic/`: Fundamental operations (threshold, blur, etc.)
|
||||
|
||||
### Key Components
|
||||
- **HawkeyeImage**: Wrapper around OpenCV Mat with filename metadata
|
||||
- **WorkflowConfiguration**: Manages camera types, Python paths, outputs, and recipe selection
|
||||
- **IImageSource**: Interface for image input sources with event-driven architecture
|
||||
- **Graphics Elements**: Drawable elements (rectangles, lines, polygons) for ROI definition
|
||||
|
||||
### Python Integration
|
||||
- Uses IronPython for scripting capabilities
|
||||
- Python models executed via `PythonModelProxy` and `PythonModelProxyRPC`
|
||||
- Default Python path: `~/miniconda3/envs/ai3_cpu/python.exe`
|
||||
|
||||
### Operation Attributes
|
||||
- `[Category("name")]`: Groups operations in UI
|
||||
- `[NotForTool]`: Excludes properties from parameter serialization
|
||||
- `[IgnoreOperation]`: Excludes operations from discovery
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Creating New Operations
|
||||
1. Inherit from `BaseOperation`
|
||||
2. Add `[Category("CategoryName")]` attribute
|
||||
3. Implement `InterpretInternal(Context context)` method
|
||||
4. Use helper methods: `CheckImageExists()`, `CheckColorful()`, `CheckGrayscale()`
|
||||
5. Set `Result` and `Status` properties for operation feedback
|
||||
|
||||
### Image Processing Flow
|
||||
1. Operations receive `Context` with current `ActiveImage`
|
||||
2. Process image using OpenCV operations
|
||||
3. Update context with results (new images, graphics elements)
|
||||
4. Set operation status and result state
|
||||
|
||||
### Memory Management
|
||||
Operations can store/retrieve images using `context.Memory` dictionary for intermediate results.
|
||||
|
||||
## Recipe Serialization System
|
||||
|
||||
The workflow system supports two serialization formats for saving and loading recipes:
|
||||
|
||||
### Binary Serialization (.hrcp files)
|
||||
The original format using `BinaryWriter` and `BinaryReader`:
|
||||
- **WorkflowList.Save(BinaryWriter)**: Saves configuration, operations, and recipe image
|
||||
- **WorkflowList.Load(BinaryReader, OperationDiscoveryService)**: Loads complete workflow
|
||||
- **BaseOperation.Save(BinaryWriter)**: Saves operation ID and label
|
||||
- **BaseOperation.Load(BinaryReader)**: Loads operation ID and label
|
||||
|
||||
### XML Serialization (.xhrcp files)
|
||||
The new human-readable format using `XmlWriter` and `XmlReader`:
|
||||
- **WorkflowList.SaveXML(XmlWriter)**: Saves complete workflow as structured XML
|
||||
- **WorkflowList.LoadXML(XmlReader, OperationDiscoveryService)**: Loads workflow from XML
|
||||
- **BaseOperation.SaveXML(XmlWriter)**: Saves operation properties and metadata
|
||||
- **BaseOperation.LoadXML(XmlReader)**: Loads operation properties with type conversion
|
||||
|
||||
### XML Structure
|
||||
```xml
|
||||
<Workflow>
|
||||
<Configuration RuntimeCameraType="..." DevelopmentCameraType="..." ... />
|
||||
<Operations Count="N">
|
||||
<Operation Type="AssemblyQualifiedName" Id="..." Label="...">
|
||||
<Property Name="PropertyName" Type="System.Int32">Value</Property>
|
||||
...
|
||||
</Operation>
|
||||
</Operations>
|
||||
<RecipeImage Length="N">Base64EncodedImageData</RecipeImage>
|
||||
</Workflow>
|
||||
```
|
||||
|
||||
### Implementing Custom Operation Serialization
|
||||
|
||||
For operations that need custom XML serialization beyond the default property serialization:
|
||||
|
||||
#### Override SaveXML for Custom Save Logic:
|
||||
```csharp
|
||||
public override void SaveXML(XmlWriter writer)
|
||||
{
|
||||
base.SaveXML(writer); // Save standard properties
|
||||
|
||||
// Custom serialization logic
|
||||
writer.WriteStartElement("CustomData");
|
||||
writer.WriteAttributeString("SpecialProperty", SpecialValue.ToString());
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
```
|
||||
|
||||
#### Override LoadXML for Custom Load Logic:
|
||||
```csharp
|
||||
public override void LoadXML(XmlReader reader)
|
||||
{
|
||||
base.LoadXML(reader); // Load standard properties
|
||||
|
||||
// Custom deserialization logic
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element && reader.Name == "CustomData")
|
||||
{
|
||||
SpecialValue = reader.GetAttribute("SpecialProperty");
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == GetOperationElementName())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Override GetOperationElementName for Custom Element Names:
|
||||
```csharp
|
||||
protected override string GetOperationElementName()
|
||||
{
|
||||
return "MyCustomOperation"; // Used for XML end element detection
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Property Types in XML Serialization
|
||||
The default XML serialization handles these types automatically:
|
||||
- Primitive types (int, double, float, bool)
|
||||
- String
|
||||
- Guid
|
||||
- Enums
|
||||
- Complex types (converted to string representation)
|
||||
|
||||
For complex types requiring special serialization, override the SaveXML/LoadXML methods.
|
||||
@@ -0,0 +1,44 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
|
||||
|
||||
public class CategoryItem:IFilterParent
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Name);
|
||||
writer.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
var type = filter.GetType();
|
||||
writer.Write(type.FullName);
|
||||
filter.Save(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryItem Load(BinaryReader reader)
|
||||
{
|
||||
var category = new CategoryItem();
|
||||
category.Name = reader.ReadString();
|
||||
var count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var typeName = reader.ReadString();
|
||||
var type = Type.GetType(typeName);
|
||||
var filter = (IFilter)Activator.CreateInstance(type);
|
||||
filter.Load(reader);
|
||||
filter.Parent = category;
|
||||
category.Filters.Add(filter);
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
|
||||
|
||||
public class CategoryTree
|
||||
{
|
||||
public List<CategoryItem> CategoryItems { get; set; }=new List<CategoryItem>();
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(CategoryItems.Count);
|
||||
foreach (var category in CategoryItems)
|
||||
{
|
||||
category.Save(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryTree Load(BinaryReader reader)
|
||||
{
|
||||
var tree = new CategoryTree();
|
||||
var count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tree.CategoryItems.Add(CategoryItem.Load(reader));
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
public static CategoryTree LoadFromFile(string path)
|
||||
{
|
||||
using var file = File.OpenRead(path);
|
||||
using var reader = new BinaryReader(file);
|
||||
return Load(reader);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
|
||||
{
|
||||
public class ColorConfig
|
||||
{
|
||||
private int _intensityMaxGrey;
|
||||
|
||||
public ColorConfig()
|
||||
{
|
||||
MaxBlobSize = 2000;
|
||||
HueHighSaturationMinGrey = 20;
|
||||
HueHighSaturationMaxGrey = 50;
|
||||
HighSaturationMinGrey = 100;
|
||||
HighSaturationMaxGrey = 255;
|
||||
ClosingCircleRadius = 8.0;
|
||||
IntensityMaxGrey = 255;
|
||||
IntensityMinGrey = 0;
|
||||
MedianRadius = 10;
|
||||
MedianMargin = 1;
|
||||
UseMedianFilter = true;
|
||||
}
|
||||
|
||||
public int MaxBlobSize { get; set; }
|
||||
public int HueHighSaturationMinGrey { get; set; }
|
||||
public int HueHighSaturationMaxGrey { get; set; }
|
||||
public int HighSaturationMinGrey { get; set; }
|
||||
public int HighSaturationMaxGrey { get; set; }
|
||||
|
||||
public int IntensityMaxGrey
|
||||
{
|
||||
get { return _intensityMaxGrey; }
|
||||
set { _intensityMaxGrey = value; }
|
||||
}
|
||||
|
||||
public int IntensityMinGrey { get; set; }
|
||||
|
||||
public double ClosingCircleRadius { get; set; }
|
||||
|
||||
public int MedianRadius { get; set; }
|
||||
public int MedianMargin { get; set; }
|
||||
|
||||
public bool UseMedianFilter { get; set; }
|
||||
|
||||
public bool GroupColorEnabled { get; set; }
|
||||
public List<HSIValue> GroupColorValues { get; set; }=new List<HSIValue>();
|
||||
public HSIValue HSI { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class AddFilterItem : IFilter, IFilterParent
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
using var tracker = new ResourcesTracker();
|
||||
var temp = tracker.NewMat(res.Size(), MatType.CV_8UC1, Scalar.Black);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
filter.Transform(ref image, ref temp);
|
||||
}
|
||||
res = res + temp;
|
||||
temp.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
bw.Write(filter.GetType().FullName);
|
||||
filter.Save(bw);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var type = reader.ReadString();
|
||||
var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
|
||||
filter.Load(reader);
|
||||
filter.Parent = this;
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Add";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Text;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class BlobsFilterItem:IFilter
|
||||
{
|
||||
public int MinArea { get; set; } = 0;
|
||||
public int MaxArea { get; set; } = 999999;
|
||||
public int MinRadius { get; set; } = 0;
|
||||
public int MaxRadius { get; set; } = 999999;
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
var contours = res
|
||||
.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
Mat result = new Mat(res.Rows, res.Cols, MatType.CV_8U, new Scalar(0));
|
||||
|
||||
foreach (Point[] contour in contours)
|
||||
{
|
||||
var area = Cv2.ContourArea(contour);
|
||||
if (area < MinArea || area > MaxArea)
|
||||
continue;
|
||||
var radius = GetContourRadius(contour);
|
||||
if (radius < MinRadius || radius > MaxRadius)
|
||||
continue;
|
||||
|
||||
Cv2.DrawContours(result, new[] { contour }, -1, new Scalar(255), -1);
|
||||
}
|
||||
|
||||
res = result;
|
||||
}
|
||||
int GetContourRadius(Point[] contour)
|
||||
{
|
||||
Cv2.MinEnclosingCircle(contour, out var center, out var radius);
|
||||
return (int)radius;
|
||||
}
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(MinArea);
|
||||
writer.Write(MaxArea);
|
||||
writer.Write(MinRadius);
|
||||
writer.Write(MaxRadius);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
MinArea = reader.ReadInt32();
|
||||
MaxArea = reader.ReadInt32();
|
||||
MinRadius = reader.ReadInt32();
|
||||
MaxRadius = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Blobs: ");
|
||||
sb.Append($"Area:{MinArea}-{MaxArea}, ");
|
||||
sb.Append($"Radius:{MinRadius}-{MaxRadius}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class BlurFilterItem:IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
public int Radius { get; set; } = 15;
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
image = image.Blur(new Size(Radius, Radius));
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Radius);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
Radius = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Blur "+Radius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public interface IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
void Transform(ref Mat image, ref Mat res);
|
||||
void Save(BinaryWriter writer);
|
||||
void Load(BinaryReader reader);
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public interface IFilterParent
|
||||
{
|
||||
public List<IFilter> Filters { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class IntersectionFilterItem : IFilter, IFilterParent
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
using var tracker = new ResourcesTracker();
|
||||
var temp = tracker.NewMat(res.Size(), MatType.CV_8UC1, Scalar.Black);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
filter.Transform(ref image, ref temp);
|
||||
}
|
||||
res = res & temp;
|
||||
temp.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
bw.Write(filter.GetType().FullName);
|
||||
filter.Save(bw);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var type = reader.ReadString();
|
||||
var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
|
||||
filter.Load(reader);
|
||||
filter.Parent = this;
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Intersection";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Diagnostics;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class MorphologyFilterItem:IFilter
|
||||
{
|
||||
public enum EMorphologyType
|
||||
{
|
||||
Erode,
|
||||
Dilate,
|
||||
Close,
|
||||
Open
|
||||
}
|
||||
|
||||
public int Size { get; set; } = 1;
|
||||
public EMorphologyType MorphologyType { get; set; }=EMorphologyType.Erode;
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
Size = reader.ReadInt32();
|
||||
MorphologyType = (EMorphologyType)reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public MorphologyFilterItem()
|
||||
{
|
||||
}
|
||||
|
||||
public MorphologyFilterItem(CategoryItem parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
if(Size==0)
|
||||
return;
|
||||
|
||||
var sw=Stopwatch.StartNew();
|
||||
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse,new Size((int)(Size), (int)(Size)));
|
||||
|
||||
switch (MorphologyType)
|
||||
{
|
||||
case EMorphologyType.Dilate:
|
||||
//Cv2.Dilate(res, res,kernel);
|
||||
Cv2.MorphologyEx(res, res, MorphTypes.Dilate, kernel);
|
||||
break;
|
||||
case EMorphologyType.Erode:
|
||||
Cv2.Erode(res, res,kernel);
|
||||
break;
|
||||
case EMorphologyType.Close:
|
||||
Cv2.MorphologyEx(res, res, MorphTypes.Close, kernel);
|
||||
break;
|
||||
case EMorphologyType.Open:
|
||||
Cv2.MorphologyEx(res, res, MorphTypes.Open, kernel);
|
||||
break;
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Size);
|
||||
writer.Write((int)MorphologyType);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{MorphologyType} {Size}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Windows.Markup;
|
||||
using OpenCvSharp;
|
||||
using Size = OpenCvSharp.Size;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class RangeFilterItem : IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public RangeFilterItem(CategoryItem parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public RangeFilterItem()
|
||||
{
|
||||
}
|
||||
|
||||
public int HMin { get; set; } = 0;
|
||||
public int HMax { get; set; } = 100;
|
||||
public int SMin { get; set; } = 0;
|
||||
public int SMax { get; set; } = 255;
|
||||
public int VMin { get; set; } = 0;
|
||||
public int VMax { get; set; } = 255;
|
||||
public int Dilation { get; set; } = 0;
|
||||
public int Closing { get; set; } = 0;
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"H: {HMin}-{HMax}, S: {SMin}-{SMax}, V: {VMin}-{VMax}";
|
||||
}
|
||||
|
||||
public void Transform(ref Mat hsv, ref Mat res)
|
||||
{
|
||||
var lower = new Scalar(HMin, SMin, VMin);
|
||||
var upper = new Scalar(HMax, SMax, VMax);
|
||||
var mask = hsv.InRange(lower, upper);
|
||||
|
||||
if (Dilation > 0)
|
||||
{
|
||||
// generate circular kernel
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(Dilation, Dilation));
|
||||
mask = mask.Dilate(kernel);
|
||||
kernel.Dispose();
|
||||
}
|
||||
|
||||
if (Closing > 0)
|
||||
{
|
||||
// generate circular kernel
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(Closing, Closing));
|
||||
mask = mask.MorphologyEx(MorphTypes.Close, kernel);
|
||||
kernel.Dispose();
|
||||
}
|
||||
|
||||
res+=mask;
|
||||
|
||||
mask.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(HMin);
|
||||
writer.Write(HMax);
|
||||
writer.Write(SMin);
|
||||
writer.Write(SMax);
|
||||
writer.Write(VMin);
|
||||
writer.Write(VMax);
|
||||
writer.Write(Dilation);
|
||||
writer.Write(Closing);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
HMin = reader.ReadInt32();
|
||||
HMax = reader.ReadInt32();
|
||||
SMin = reader.ReadInt32();
|
||||
SMax = reader.ReadInt32();
|
||||
VMin = reader.ReadInt32();
|
||||
VMax = reader.ReadInt32();
|
||||
Dilation = reader.ReadInt32();
|
||||
Closing = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public static RangeFilterItem Load(BinaryReader reader, CategoryItem parent)
|
||||
{
|
||||
var filter = new RangeFilterItem(parent);
|
||||
filter.HMin = reader.ReadInt32();
|
||||
filter.HMax = reader.ReadInt32();
|
||||
filter.SMin = reader.ReadInt32();
|
||||
filter.SMax = reader.ReadInt32();
|
||||
filter.VMin = reader.ReadInt32();
|
||||
filter.VMax = reader.ReadInt32();
|
||||
filter.Dilation = reader.ReadInt32();
|
||||
filter.Closing = reader.ReadInt32();
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class SobelFilterItem:IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
var filtered=image.Sobel(MatType.CV_32F, 1, 1, 3);
|
||||
filtered.ConvertTo(image, MatType.CV_8UC3);
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System.Diagnostics;
|
||||
using OpenCvSharp;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class SpatialFilterItem : IFilter
|
||||
{
|
||||
private Mat? _mask=null;
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public bool Subtract { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
|
||||
public void ReloadMask()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
CreateMask();
|
||||
}else
|
||||
if (!string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
_mask = new Mat(FilePath,ImreadModes.Grayscale);
|
||||
}
|
||||
}
|
||||
|
||||
public SpatialFilterItem()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CreateMask()
|
||||
{
|
||||
_mask = new Mat(13005 + 256, 256, MatType.CV_8UC1, (Scalar)0);
|
||||
}
|
||||
|
||||
public SpatialFilterItem(CategoryItem parent) : this()
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public Mat Mask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mask == null)
|
||||
{
|
||||
if (FilePath != null)
|
||||
{
|
||||
ReloadMask();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateMask();
|
||||
}
|
||||
}
|
||||
|
||||
return _mask;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mask = value;
|
||||
}
|
||||
}
|
||||
|
||||
struct PixelStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 65536*3)]
|
||||
public byte[] Pixels;
|
||||
}
|
||||
struct PixelStructMask
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3394816)]
|
||||
public byte[] Pixels;
|
||||
}
|
||||
public static Mat lastInput;
|
||||
public unsafe void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
|
||||
//var pixelStruct = Marshal.PtrToStructure<PixelStruct>(new IntPtr(image.DataPointer));
|
||||
var candyBytes = GetPixels<byte>(image);
|
||||
var candidates = new Mat(image.Size(), MatType.CV_8UC1, (Scalar)0);
|
||||
Mat tmp=new Mat();
|
||||
image.CopyTo(tmp);
|
||||
lastInput = tmp;
|
||||
//var pixelStructMask = Marshal.PtrToStructure<PixelStructMask>(new IntPtr(Mask.DataPointer));
|
||||
|
||||
var maskBytes = GetPixels<byte>(Mask);
|
||||
|
||||
var len = candyBytes.Length;
|
||||
var w = image.Width;
|
||||
var mw = Mask.Width;
|
||||
|
||||
var lenDiv3 = len / 3;
|
||||
|
||||
for (var q = 0; q < lenDiv3; q++)
|
||||
{
|
||||
var i = q * 3;
|
||||
|
||||
var p = i / 3;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
var yp = (int)candyBytes[i + 0];
|
||||
var xp = candyBytes[i + 1];
|
||||
var vp = ((byte) (candyBytes[i + 2] / 5f)) * 255;
|
||||
|
||||
var hp = yp + vp;
|
||||
|
||||
var maskColor = maskBytes[hp * mw + xp];
|
||||
if (maskColor > 0)
|
||||
candidates.Set<byte>(y, x, 255);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (Subtract && !IsSelected)
|
||||
{
|
||||
res -= candidates;
|
||||
}
|
||||
else
|
||||
{
|
||||
res += candidates;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
string Hash(Span<byte> bytes) {
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(bytes.ToArray());
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
unsafe Span<T> GetPixels<T>(Mat image) where T : unmanaged
|
||||
{
|
||||
|
||||
return new Span<T>(image.DataPointer, image.Width * image.Height * image.Channels());
|
||||
|
||||
}
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(FilePath??"");
|
||||
writer.Write(Subtract);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
FilePath = reader.ReadString();
|
||||
Console.WriteLine("Loaded path:"+FilePath);
|
||||
if(FilePath.StartsWith("..\\../"))
|
||||
FilePath = FilePath.Replace("..\\../", "..\\");
|
||||
Subtract = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var totalColors = 0;
|
||||
if (Mask != null)
|
||||
{
|
||||
totalColors = Mask.CountNonZero();
|
||||
}
|
||||
return $"{(Subtract ? "-" : "+")}Spatial Filter({totalColors} colors)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
[Obsolete]
|
||||
public class SpatialFilterItemGPU:IFilter
|
||||
{
|
||||
private Mat? _mask = null;
|
||||
|
||||
private MemoryBuffer1D<byte, Stride1D.Dense> _maskGpu;
|
||||
|
||||
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public bool Subtract { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
|
||||
public void ReloadMask()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
CreateMask();
|
||||
}
|
||||
else
|
||||
if (!string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
_mask = new Mat(FilePath).CvtColor(ColorConversionCodes.BGR2GRAY);
|
||||
}
|
||||
LoadMaskToGpu();
|
||||
}
|
||||
|
||||
private unsafe void LoadMaskToGpu()
|
||||
{
|
||||
var maskBytes = new Span<byte>(Mask.DataPointer, Mask.Width * Mask.Height).ToArray();
|
||||
if (_maskGpu != null)
|
||||
{
|
||||
_maskGpu.Dispose();
|
||||
}
|
||||
_maskGpu = Kernels.Accelerator.Allocate1D<byte>(maskBytes);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static MemoryBuffer1D<byte, Stride1D.Dense>? _imageMemory;
|
||||
private static MemoryBuffer1D<byte, Stride1D.Dense>? _resMemory;
|
||||
|
||||
|
||||
public SpatialFilterItemGPU()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void CreateMask()
|
||||
{
|
||||
_mask = new Mat(13005 + 256, 256, MatType.CV_8UC1, (Scalar)0);
|
||||
}
|
||||
|
||||
public SpatialFilterItemGPU(CategoryItem parent) : this()
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public Mat Mask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mask == null)
|
||||
{
|
||||
if (FilePath != null)
|
||||
{
|
||||
ReloadMask();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateMask();
|
||||
}
|
||||
}
|
||||
|
||||
return _mask;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mask = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static Mat? _lastLoadedImage;
|
||||
|
||||
|
||||
public unsafe void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var w = image.Width;
|
||||
var mw = Mask.Width;
|
||||
|
||||
|
||||
|
||||
if (_lastLoadedImage != image)
|
||||
{
|
||||
var imageBytes = new ReadOnlySpan<byte>(image.DataPointer, image.Width * image.Height * 3);
|
||||
_imageMemory ??= Kernels.Accelerator.Allocate1D<byte>(imageBytes.Length);
|
||||
_imageMemory.View.BaseView.CopyFromCPU(imageBytes);
|
||||
_lastLoadedImage = image;
|
||||
}
|
||||
|
||||
var len = (int)_imageMemory.Length;
|
||||
|
||||
_resMemory ??= Kernels.Accelerator.Allocate1D<byte>(len / 3);
|
||||
|
||||
|
||||
|
||||
Kernels.SpatialKernel(len/3, w, mw, _imageMemory.View, _maskGpu.View, _resMemory.View);
|
||||
var arr = _resMemory.GetAsArray1D();
|
||||
|
||||
|
||||
var candidates=new Mat(new[]{image.Height,image.Width}, MatType.CV_8UC1);
|
||||
candidates.SetArray(arr);
|
||||
|
||||
|
||||
if (Subtract && !IsSelected)
|
||||
{
|
||||
res -= candidates;
|
||||
}
|
||||
else
|
||||
{
|
||||
res |= candidates;
|
||||
|
||||
}
|
||||
|
||||
|
||||
candidates.Dispose();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(FilePath ?? "");
|
||||
writer.Write(Subtract);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
FilePath = reader.ReadString();
|
||||
Console.WriteLine("Loaded path:" + FilePath);
|
||||
if (FilePath.StartsWith("..\\../"))
|
||||
FilePath = FilePath.Replace("..\\../", "..\\");
|
||||
Subtract = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var totalColors = 0;
|
||||
if (Mask != null)
|
||||
{
|
||||
totalColors = Mask.CountNonZero();
|
||||
}
|
||||
return $"{(Subtract ? "-" : "+")}Spatial Filter({totalColors} colors)";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class SubtractFilterItem : IFilter,IFilterParent
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
using var tracker = new ResourcesTracker();
|
||||
var temp = tracker.NewMat(res.Size(),MatType.CV_8UC1,Scalar.Black);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
filter.Transform(ref image, ref temp);
|
||||
}
|
||||
res = res - temp;
|
||||
temp.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
bw.Write(filter.GetType().FullName);
|
||||
filter.Save(bw);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var type = reader.ReadString();
|
||||
var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
|
||||
filter.Load(reader);
|
||||
filter.Parent = this;
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Subtract";
|
||||
}
|
||||
}
|
||||
125
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs
Normal file
125
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
|
||||
{
|
||||
public class HSIValue
|
||||
{
|
||||
|
||||
public HSIValue()
|
||||
{
|
||||
|
||||
}
|
||||
public int MinSize { get; set; } = 1;
|
||||
public int MinComponentSize { get; set; } = 1;
|
||||
public int PreFilterId { get; set; } = 0;
|
||||
public int PreFilterSize { get; set; } = 10;
|
||||
|
||||
public int Closing { get; set; }
|
||||
|
||||
public int Circle { get; set; } = 1;
|
||||
public string Tag { get; set; }
|
||||
private Range<int> _hue = new Range<int>() {Minimum = 0,Maximum = 10};
|
||||
private Range<int> _saturation = new Range<int>() { Minimum = 0, Maximum = 10 };
|
||||
private Range<int> _intensity = new Range<int>() { Minimum = 0, Maximum = 10 };
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public Range<int> Hue
|
||||
{
|
||||
get { return _hue; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _hue)) return;
|
||||
_hue = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Range<int> Saturation
|
||||
{
|
||||
get { return _saturation; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _saturation)) return;
|
||||
_saturation = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Range<int> Intensity
|
||||
{
|
||||
get { return _intensity; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _intensity)) return;
|
||||
_intensity = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Active { get; set; }
|
||||
public List<HSIValue> SubColors { get; set; }=new List<HSIValue>();
|
||||
public Rectangle GetRectangle()
|
||||
{
|
||||
Rectangle th = new Rectangle((int) (Hue.Minimum), (int) (Saturation.Minimum),
|
||||
(int) (Hue.Maximum) - (int) (Hue.Minimum), (int) (Saturation.Maximum) - (int) (Saturation.Minimum));
|
||||
return th;
|
||||
}
|
||||
[XmlIgnore]
|
||||
public bool ShowStar { get; set; }
|
||||
|
||||
public int Dilation { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} {Hue}:{Saturation}:{Intensity}{(ShowStar?"*":"")}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static explicit operator ColorConfig(HSIValue value)
|
||||
{
|
||||
var config = new ColorConfig()
|
||||
{
|
||||
ClosingCircleRadius = value.Circle,
|
||||
HueHighSaturationMaxGrey = value.Hue.Maximum,
|
||||
HueHighSaturationMinGrey = value.Hue.Minimum,
|
||||
HighSaturationMaxGrey = value.Saturation.Maximum,
|
||||
HighSaturationMinGrey = value.Saturation.Minimum,
|
||||
IntensityMaxGrey = value.Intensity.Maximum,
|
||||
IntensityMinGrey = value.Intensity.Minimum,
|
||||
MaxBlobSize = value.MinSize,
|
||||
MedianMargin = 1,
|
||||
MedianRadius = 10,
|
||||
UseMedianFilter = true,
|
||||
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
public static implicit operator HSIValue(ColorConfig config)
|
||||
{
|
||||
return new HSIValue()
|
||||
{
|
||||
Hue = new Range<int>(config.HueHighSaturationMinGrey, config.HueHighSaturationMaxGrey),
|
||||
Saturation = new Range<int>(config.HighSaturationMinGrey, config.HighSaturationMaxGrey),
|
||||
Intensity = new Range<int>(config.IntensityMinGrey, config.IntensityMaxGrey),
|
||||
Circle = (int)config.ClosingCircleRadius,
|
||||
MinSize = config.MaxBlobSize
|
||||
};
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs
Normal file
157
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
|
||||
|
||||
public static class Kernels
|
||||
{
|
||||
static Kernels()
|
||||
{
|
||||
Init();
|
||||
AppDomain.CurrentDomain.ProcessExit +=
|
||||
Destruct;
|
||||
}
|
||||
|
||||
private static void Destruct(object? sender, EventArgs e)
|
||||
{
|
||||
_accelerator.Dispose();
|
||||
_gpuContext.Dispose();
|
||||
}
|
||||
|
||||
|
||||
private static object _lock = new object();
|
||||
private static ILGPU.Context _gpuContext;
|
||||
private static Accelerator _accelerator;
|
||||
private static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> _spatialKernel;
|
||||
private static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>> _dilationKernel;
|
||||
|
||||
public static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> SpatialKernel => _spatialKernel;
|
||||
|
||||
public static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>> Dilation => _dilationKernel;
|
||||
|
||||
public static Accelerator Accelerator
|
||||
{
|
||||
get => _accelerator;
|
||||
set => _accelerator = value;
|
||||
}
|
||||
|
||||
static Dictionary<Mat, MemoryBuffer1D<byte,Stride1D.Dense>> _imageCache = new ();
|
||||
|
||||
public static unsafe MemoryBuffer1D<byte, Stride1D.Dense> TransformImageCached(Mat image)
|
||||
{
|
||||
if (_imageCache.ContainsKey(image))
|
||||
return _imageCache[image];
|
||||
var imageBytes = new Span<byte>(image.DataPointer, image.Width * image.Height * 3);
|
||||
|
||||
var res=Kernels.Accelerator.Allocate1D<byte>(imageBytes.ToArray());
|
||||
_imageCache[image] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static void Init()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
||||
if (_gpuContext != null) return;
|
||||
|
||||
_gpuContext = ILGPU.Context.Create(builder => builder.AllAccelerators());
|
||||
_accelerator = _gpuContext.Devices.First(x => x.AcceleratorType == AcceleratorType.Cuda)
|
||||
.CreateAccelerator(_gpuContext);
|
||||
Console.WriteLine("GPU acceleration initialized");
|
||||
|
||||
|
||||
_spatialKernel = _accelerator
|
||||
.LoadAutoGroupedStreamKernel<Index1D, int, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>>(
|
||||
Kernels.TransformSpatialKernel);
|
||||
|
||||
_dilationKernel = _accelerator
|
||||
.LoadAutoGroupedStreamKernel<Index1D, int, int, ArrayView<byte>,ArrayView<byte>>(
|
||||
Kernels.DilationKernel);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformSpatialKernel(Index1D index, int w, int mw, ArrayView<byte> candyBytes, ArrayView<byte> maskBytes, ArrayView<byte> res)
|
||||
{
|
||||
var i = index;
|
||||
var p = i * 3;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
|
||||
|
||||
var yp = (int)candyBytes[p + 0];
|
||||
var xp = candyBytes[p + 1];
|
||||
var vp = (byte)(candyBytes[p + 2] / 5f) * 255;
|
||||
|
||||
var hp = yp + vp;
|
||||
var maskColor = maskBytes[hp * mw + xp];
|
||||
if (maskColor > 0)
|
||||
res[i] = 255;
|
||||
else
|
||||
res[i] = 0;
|
||||
}
|
||||
|
||||
private static void DilationKernel(Index1D index, int w, int size, ArrayView<byte> maskBytes,
|
||||
ArrayView<byte> res)
|
||||
{
|
||||
var i = index;
|
||||
var p = i ;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
|
||||
var halfSize = size-1 / 2;
|
||||
|
||||
|
||||
if (maskBytes[p] > 0)
|
||||
{
|
||||
for (int j = -halfSize; j <= halfSize; j++)
|
||||
{
|
||||
for (int k = -halfSize; k <= halfSize; k++)
|
||||
{
|
||||
var yp = y + j;
|
||||
var xp = x + k;
|
||||
if (yp < 0 || yp >= w || xp < 0 || xp >= w)
|
||||
continue;
|
||||
res[yp * w + xp] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void ErosionKernel(Index1D index, int w, int size, ArrayView<byte> maskBytes,
|
||||
ArrayView<byte> res)
|
||||
{
|
||||
var i = index;
|
||||
var p = i ;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
|
||||
var halfSize = size-1 / 2;
|
||||
|
||||
|
||||
if (maskBytes[p] <255)
|
||||
{
|
||||
for (int j = -halfSize; j <= halfSize; j++)
|
||||
{
|
||||
for (int k = -halfSize; k <= halfSize; k++)
|
||||
{
|
||||
var yp = y + j;
|
||||
var xp = x + k;
|
||||
if (yp < 0 || yp >= w || xp < 0 || xp >= w)
|
||||
continue;
|
||||
res[yp * w + xp] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs
Normal file
64
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
|
||||
{
|
||||
/// <summary>The Range class.</summary>
|
||||
/// <typeparam name="T">Generic parameter.</typeparam>
|
||||
public class Range<T> where T : IComparable<T>
|
||||
{
|
||||
|
||||
public Range()
|
||||
{
|
||||
|
||||
}
|
||||
public Range(T min,T max)
|
||||
{
|
||||
this.Maximum = max;
|
||||
this.Minimum = min;
|
||||
}
|
||||
|
||||
/// <summary>Minimum value of the range.</summary>
|
||||
public T Minimum { get; set; }
|
||||
|
||||
/// <summary>Maximum value of the range.</summary>
|
||||
public T Maximum { get; set; }
|
||||
|
||||
/// <summary>Presents the Range in readable format.</summary>
|
||||
/// <returns>String representation of the Range</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0} - {1}]", this.Minimum, this.Maximum);
|
||||
}
|
||||
|
||||
/// <summary>Determines if the range is valid.</summary>
|
||||
/// <returns>True if range is valid, else false</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return this.Minimum.CompareTo(this.Maximum) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>Determines if the provided value is inside the range.</summary>
|
||||
/// <param name="value">The value to test</param>
|
||||
/// <returns>True if the value is inside Range, else false</returns>
|
||||
public bool ContainsValue(T value)
|
||||
{
|
||||
return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0);
|
||||
}
|
||||
|
||||
/// <summary>Determines if this Range is inside the bounds of another range.</summary>
|
||||
/// <param name="Range">The parent range to test on</param>
|
||||
/// <returns>True if range is inclusive, else false</returns>
|
||||
public bool IsInsideRange(Range<T> range)
|
||||
{
|
||||
return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
|
||||
}
|
||||
|
||||
/// <summary>Determines if another range is inside the bounds of this range.</summary>
|
||||
/// <param name="Range">The child range to test</param>
|
||||
/// <returns>True if range is inside, else false</returns>
|
||||
public bool ContainsRange(Range<T> range)
|
||||
{
|
||||
return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
|
||||
public class BadTweakLearning
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public int ThresholdLevel { get; set; } = 80;
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Enabled);
|
||||
bw.Write(ThresholdLevel);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
Enabled = br.ReadBoolean();
|
||||
ThresholdLevel = br.ReadInt32();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
|
||||
public enum ECameraType
|
||||
{
|
||||
Emulation=0,
|
||||
Hawkeye=1
|
||||
|
||||
}
|
||||
8
Hawkeye.VisionBuilder.Workflow/Configuration/EOutputs.cs
Normal file
8
Hawkeye.VisionBuilder.Workflow/Configuration/EOutputs.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
|
||||
public enum EOutputs
|
||||
{
|
||||
None,
|
||||
Firmata,
|
||||
NetServer
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
|
||||
public class RecipeSelectionConfig
|
||||
{
|
||||
public bool SerialEnabled { get; set; } = false;
|
||||
public string ComPort { get; set; } = "COM3";
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(SerialEnabled);
|
||||
bw.Write(ComPort);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
SerialEnabled = br.ReadBoolean();
|
||||
ComPort = br.ReadString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Security.AccessControl;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
|
||||
public class WorkflowConfiguration
|
||||
{
|
||||
public WorkflowConfiguration()
|
||||
{
|
||||
var userFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
PythonPath = Path.Combine(userFolder, @"miniconda3\envs\ai3_cpu\python.exe");
|
||||
}
|
||||
|
||||
public ECameraType RuntimeCameraType { get; set; } = ECameraType.Emulation;
|
||||
public ECameraType DevelopmentCameraType { get; set; } = ECameraType.Emulation;
|
||||
|
||||
public string EmulationPath { get; set; } = "..\\Data\\Emulation";
|
||||
public string PythonPath { get; set; }
|
||||
|
||||
public EOutputs Outputs { get; set; }
|
||||
public int ResultPin { get; set; }
|
||||
public string SerialPort { get; set; } = "";
|
||||
public int Delay { get; set; } = 0;
|
||||
|
||||
|
||||
public BadTweakLearning BadTweakLearning { get; set; } = new BadTweakLearning();
|
||||
public RecipeSelectionConfig RecipeSelectionConfig { get; set; }=new RecipeSelectionConfig();
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write((int)RuntimeCameraType);
|
||||
bw.Write((int)DevelopmentCameraType);
|
||||
bw.Write((int)Outputs);
|
||||
bw.Write(EmulationPath);
|
||||
bw.Write(PythonPath);
|
||||
bw.Write(ResultPin);
|
||||
bw.Write(SerialPort);
|
||||
bw.Write(Delay);
|
||||
BadTweakLearning.Save(bw);
|
||||
RecipeSelectionConfig.Save(bw);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
RuntimeCameraType = (ECameraType) br.ReadInt32();
|
||||
DevelopmentCameraType = (ECameraType) br.ReadInt32();
|
||||
Outputs = (EOutputs) br.ReadInt32();
|
||||
EmulationPath = br.ReadString();
|
||||
PythonPath = br.ReadString();
|
||||
ResultPin = br.ReadInt32();
|
||||
SerialPort = br.ReadString();
|
||||
Delay = br.ReadInt32();
|
||||
BadTweakLearning.Load(br);
|
||||
RecipeSelectionConfig.Load(br);
|
||||
}
|
||||
}
|
||||
13
Hawkeye.VisionBuilder.Workflow/Context.cs
Normal file
13
Hawkeye.VisionBuilder.Workflow/Context.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow;
|
||||
|
||||
public class Context
|
||||
{
|
||||
public HawkeyeImage? ActiveImage { get; set; }
|
||||
public HawkeyeImage? LastCameraImage { get; set; }
|
||||
public List<IGraphicsElement> GraphicsElements { get; set; }=new List<IGraphicsElement>();
|
||||
public Dictionary<string, HawkeyeImage> Memory { get; set; } = new Dictionary<string, HawkeyeImage>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
public class CSharpDataTransferMQ
|
||||
{
|
||||
|
||||
private object _syncObject = new object();
|
||||
private readonly ResponseSocket _server;
|
||||
|
||||
public CSharpDataTransferMQ()
|
||||
{
|
||||
this._server = new ResponseSocket();
|
||||
_server.Bind("tcp://*:5555");
|
||||
|
||||
}
|
||||
|
||||
public byte[] TransferData(string command, byte mode, byte[] data)
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
return TransferDataInternal(command,mode,data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private byte[] TransferDataInternal(string command,byte mode,byte[] data)
|
||||
{
|
||||
Console.WriteLine("Waiting for request");
|
||||
_server.ReceiveFrameBytes();
|
||||
|
||||
MemoryStream ms = new MemoryStream();
|
||||
BinaryWriter bw = new BinaryWriter(ms);
|
||||
var functionBytes=System.Text.Encoding.ASCII.GetBytes(command);
|
||||
bw.Write(functionBytes.Length);
|
||||
bw.Write(functionBytes);
|
||||
bw.Write(mode);
|
||||
bw.Write(data);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Console.WriteLine("Sending response");
|
||||
_server.SendFrame(ms.ToArray());
|
||||
|
||||
Console.WriteLine("Waiting for request");
|
||||
var result= _server.ReceiveFrameBytes();
|
||||
|
||||
_server.SendFrame("OK");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using MessagePack;
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
[MessagePackObject]
|
||||
public class MethodCall
|
||||
{
|
||||
[Key(0)]
|
||||
public string Name { get; set; }
|
||||
[Key(1)]
|
||||
public List<object> Parameters { get; set; }
|
||||
|
||||
public MethodCall()
|
||||
{
|
||||
}
|
||||
|
||||
public MethodCall(string name, params object[] parameters)
|
||||
{
|
||||
Name=name;
|
||||
Parameters=new List<object>(parameters);
|
||||
}
|
||||
}
|
||||
[MessagePackObject]
|
||||
public class TransferData
|
||||
{
|
||||
[Key(0)]
|
||||
public ETransferType DataType { get; set; }
|
||||
[Key(1)]
|
||||
public int Int { get; set; }
|
||||
[Key(2)]
|
||||
public string String { get; set; }
|
||||
[Key(3)]
|
||||
public byte[] Bytes { get; set; }
|
||||
[Key(4)]
|
||||
public float[] Floats { get; set; }
|
||||
|
||||
public static implicit operator TransferData(int value)
|
||||
{
|
||||
return new TransferData()
|
||||
{
|
||||
DataType = ETransferType.Int,
|
||||
Int = value
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator TransferData(string value)
|
||||
{
|
||||
return new TransferData()
|
||||
{
|
||||
DataType = ETransferType.String,
|
||||
String = value
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator TransferData(byte[] value)
|
||||
{
|
||||
return new TransferData()
|
||||
{
|
||||
DataType = ETransferType.Bytes,
|
||||
Bytes = value
|
||||
};
|
||||
}
|
||||
public static implicit operator TransferData(float[] value)
|
||||
{
|
||||
return new TransferData()
|
||||
{
|
||||
DataType = ETransferType.Floats,
|
||||
Floats = value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ETransferType
|
||||
{
|
||||
Void=0,
|
||||
Int=1,
|
||||
String=2,
|
||||
Bytes=3,
|
||||
Floats=4
|
||||
}
|
||||
public class CSharpDataTransferMQRPC
|
||||
{
|
||||
private object _syncObject = new object();
|
||||
private readonly ResponseSocket _server;
|
||||
|
||||
public CSharpDataTransferMQRPC()
|
||||
{
|
||||
this._server = new ResponseSocket();
|
||||
_server.Bind("tcp://*:5555");
|
||||
|
||||
|
||||
}
|
||||
|
||||
public T TransferData<T>(MethodCall call)
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
return TransferDataInternal<T>(call);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private T TransferDataInternal<T>(MethodCall call)
|
||||
{
|
||||
Console.WriteLine("Waiting for request");
|
||||
_server.ReceiveFrameBytes();
|
||||
|
||||
|
||||
Console.WriteLine("Sending response");
|
||||
|
||||
var serialized = MessagePack.MessagePackSerializer.Serialize(call);
|
||||
|
||||
_server.SendFrame(serialized);
|
||||
|
||||
|
||||
|
||||
Console.WriteLine("Waiting for request");
|
||||
var result = _server.ReceiveFrameBytes();
|
||||
|
||||
var decoded=MessagePackSerializer.Deserialize<object>(result);
|
||||
|
||||
|
||||
|
||||
_server.SendFrame("OK");
|
||||
|
||||
|
||||
return (T)Convert.ChangeType(decoded, typeof(T));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
public static class ChildProcessTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Add the process to be tracked. If our current process is killed, the child processes
|
||||
/// that we are tracking will be automatically killed, too. If the child process terminates
|
||||
/// first, that's fine, too.</summary>
|
||||
/// <param name="process"></param>
|
||||
public static void AddProcess(Process process)
|
||||
{
|
||||
if (s_jobHandle != IntPtr.Zero)
|
||||
{
|
||||
bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
|
||||
if (!success && !process.HasExited)
|
||||
throw new Win32Exception();
|
||||
}
|
||||
}
|
||||
|
||||
static ChildProcessTracker()
|
||||
{
|
||||
// This feature requires Windows 8 or later. To support Windows 7 requires
|
||||
// registry settings to be added if you are using Visual Studio plus an
|
||||
// app.manifest change.
|
||||
// https://stackoverflow.com/a/4232259/386091
|
||||
// https://stackoverflow.com/a/9507862/386091
|
||||
if (Environment.OSVersion.Version < new Version(6, 2))
|
||||
return;
|
||||
|
||||
// The job name is optional (and can be null) but it helps with diagnostics.
|
||||
// If it's not null, it has to be unique. Use SysInternals' Handle command-line
|
||||
// utility: handle -a ChildProcessTracker
|
||||
string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
|
||||
s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);
|
||||
|
||||
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
|
||||
|
||||
// This is the key flag. When our process is killed, Windows will automatically
|
||||
// close the job handle, and when that happens, we want the child processes to
|
||||
// be killed, too.
|
||||
info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
|
||||
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
||||
extendedInfo.BasicLimitInformation = info;
|
||||
|
||||
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
||||
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
|
||||
|
||||
if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
|
||||
extendedInfoPtr, (uint)length))
|
||||
{
|
||||
throw new Win32Exception();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(extendedInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
|
||||
IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
||||
|
||||
// Windows will automatically close any open job handles when our process terminates.
|
||||
// This can be verified by using SysInternals' Handle utility. When the job handle
|
||||
// is closed, the child processes will be killed.
|
||||
private static readonly IntPtr s_jobHandle;
|
||||
}
|
||||
|
||||
public enum JobObjectInfoType
|
||||
{
|
||||
AssociateCompletionPortInformation = 7,
|
||||
BasicLimitInformation = 2,
|
||||
BasicUIRestrictions = 4,
|
||||
EndOfJobTimeInformation = 6,
|
||||
ExtendedLimitInformation = 9,
|
||||
SecurityLimitInformation = 5,
|
||||
GroupInformation = 11
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
||||
{
|
||||
public Int64 PerProcessUserTimeLimit;
|
||||
public Int64 PerJobUserTimeLimit;
|
||||
public JOBOBJECTLIMIT LimitFlags;
|
||||
public UIntPtr MinimumWorkingSetSize;
|
||||
public UIntPtr MaximumWorkingSetSize;
|
||||
public UInt32 ActiveProcessLimit;
|
||||
public Int64 Affinity;
|
||||
public UInt32 PriorityClass;
|
||||
public UInt32 SchedulingClass;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum JOBOBJECTLIMIT : uint
|
||||
{
|
||||
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct IO_COUNTERS
|
||||
{
|
||||
public UInt64 ReadOperationCount;
|
||||
public UInt64 WriteOperationCount;
|
||||
public UInt64 OtherOperationCount;
|
||||
public UInt64 ReadTransferCount;
|
||||
public UInt64 WriteTransferCount;
|
||||
public UInt64 OtherTransferCount;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
||||
{
|
||||
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
||||
public IO_COUNTERS IoInfo;
|
||||
public UIntPtr ProcessMemoryLimit;
|
||||
public UIntPtr JobMemoryLimit;
|
||||
public UIntPtr PeakProcessMemoryUsed;
|
||||
public UIntPtr PeakJobMemoryUsed;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
public static class PythonModelProxy
|
||||
{
|
||||
|
||||
private static bool _isInitialized = false;
|
||||
private static PythonScriptRunner _pythonScriptRunner;
|
||||
private static CSharpDataTransferMQ _cSharpDataTransfer;
|
||||
|
||||
public static void Initialize(string pythonPath, string scriptFile= "Hawkeye.ModelLoader.py")
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
if (!File.Exists(pythonPath)) return;
|
||||
_isInitialized = true;
|
||||
|
||||
var relativePath = @"..\Data\AI";
|
||||
var absolutePath = Path.GetFullPath(relativePath);
|
||||
_cSharpDataTransfer = new CSharpDataTransferMQ();
|
||||
_pythonScriptRunner = new PythonScriptRunner(pythonPath, scriptFile, absolutePath);
|
||||
_pythonScriptRunner.Start();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static CSharpDataTransferMQ GetInterface()
|
||||
{
|
||||
if (!_isInitialized) return null;
|
||||
return _cSharpDataTransfer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
public static class PythonModelProxyRPC
|
||||
{
|
||||
private static bool _isInitialized = false;
|
||||
private static PythonScriptRunner _pythonScriptRunner;
|
||||
private static CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
|
||||
public static void Initialize(string scriptFile = @"..\Data\models\Segment128Half.py")
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
var pythonPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\miniconda3\envs\ai4\python.exe");
|
||||
//var pythonPath = Environment.ExpandEnvironmentVariables(@"/root/ai3/bin/python3");
|
||||
var scriptRelPath = scriptFile;
|
||||
var scriptAbsPath = Path.GetFullPath(scriptRelPath);
|
||||
var workDirectory = Path.GetDirectoryName(scriptAbsPath);
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
|
||||
|
||||
if (!File.Exists(pythonPath)) return;
|
||||
_isInitialized = true;
|
||||
|
||||
_cSharpDataTransferMQRPC = new CSharpDataTransferMQRPC();
|
||||
_pythonScriptRunner = new PythonScriptRunner(pythonPath, scriptAbsPath, workDirectory);
|
||||
|
||||
_pythonScriptRunner.Start();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static CSharpDataTransferMQRPC GetInterface()
|
||||
{
|
||||
if (!_isInitialized) Initialize();
|
||||
return _cSharpDataTransferMQRPC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
public class PythonScriptRunner
|
||||
{
|
||||
private Process process;
|
||||
private readonly string _pythonPath;
|
||||
private string scriptPath;
|
||||
private readonly string _workingDirectory;
|
||||
|
||||
public PythonScriptRunner(string pythonPath, string scriptPath,string workingDirectory)
|
||||
{
|
||||
_pythonPath = pythonPath;
|
||||
this.scriptPath = scriptPath;
|
||||
_workingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo();
|
||||
|
||||
|
||||
startInfo.FileName = _pythonPath; // set the path to the Python executable if not in PATH
|
||||
startInfo.Arguments = "\""+scriptPath+ "\"";
|
||||
startInfo.WorkingDirectory = _workingDirectory;
|
||||
|
||||
// enable running in the background
|
||||
startInfo.UseShellExecute = false;
|
||||
//startInfo.CreateNoWindow = true;
|
||||
// create and start the process
|
||||
process = new Process();
|
||||
|
||||
|
||||
process.StartInfo = startInfo;
|
||||
process.Start();
|
||||
// on windows only
|
||||
if(Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
ChildProcessTracker.AddProcess(process);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
process.Dispose();
|
||||
process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
public class WslScriptRunner
|
||||
{
|
||||
private Process process;
|
||||
private readonly string _pythonPath;
|
||||
private string scriptPath;
|
||||
private readonly string _workingDirectory;
|
||||
|
||||
public WslScriptRunner(string pythonPath, string scriptPath,string workingDirectory)
|
||||
{
|
||||
_pythonPath = pythonPath;
|
||||
this.scriptPath = scriptPath;
|
||||
_workingDirectory = workingDirectory;
|
||||
}
|
||||
public static string ConvertToWslPath(string windowsPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(windowsPath))
|
||||
throw new ArgumentException("Path cannot be null or empty.", nameof(windowsPath));
|
||||
|
||||
// Replace backslashes with forward slashes
|
||||
string wslPath = windowsPath.Replace('\\', '/');
|
||||
|
||||
// Extract the drive letter and convert it to WSL format
|
||||
if (wslPath.Length > 1 && wslPath[1] == ':')
|
||||
{
|
||||
char driveLetter = char.ToLower(wslPath[0]);
|
||||
wslPath = $"/mnt/{driveLetter}{wslPath.Substring(2)}";
|
||||
}
|
||||
|
||||
return wslPath;
|
||||
}
|
||||
public void Start()
|
||||
{
|
||||
|
||||
|
||||
|
||||
//startInfo.CreateNoWindow = true;
|
||||
// create and start the process
|
||||
process = new Process();
|
||||
var arg =
|
||||
$@"--distribution ubuntu --user root --cd ""{ConvertToWslPath(_workingDirectory)}"" -- /root/ai3/bin/python3 ""{ConvertToWslPath(scriptPath)}""";
|
||||
process.StartInfo = new ProcessStartInfo("wsl",arg)
|
||||
{
|
||||
UseShellExecute = false
|
||||
};
|
||||
process.Start();
|
||||
// on windows only
|
||||
if(Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
ChildProcessTracker.AddProcess(process);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (process != null && !process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
process.Dispose();
|
||||
process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Align;
|
||||
|
||||
public class AlignElement:IGraphicsElement
|
||||
{
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
public Vector2 Location { get; set; }
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
canvas.DrawCross(Location);
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.ArrayHorizontal;
|
||||
|
||||
public class ArrayHorizontalAnchorNode:INode
|
||||
{
|
||||
private readonly ArrayHorizontalElement _parent;
|
||||
|
||||
public ArrayHorizontalAnchorNode(ArrayHorizontalElement parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
_parent.Location = position - _parent.Offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.ArrayHorizontal;
|
||||
|
||||
public class ArrayHorizontalElement:IGraphicsElement
|
||||
{
|
||||
public bool Editable { get; set; }
|
||||
|
||||
public Dictionary<int,bool> IsGood=new Dictionary<int, bool>();
|
||||
|
||||
public Vector2 Location { get; set; }
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
|
||||
public Vector2 BlockSize { get; set; }
|
||||
public int Margin { get; set; } = 5;
|
||||
public int BlockCount { get; set; } = 5;
|
||||
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
GenerateLocations().Select((l, i) => (l, i)).ToList().ForEach(((Vector2 l, int i) x) => canvas.DrawRectangle(x.l, BlockSize, IsGood.GetValueOrDefault(x.i,true)));
|
||||
canvas.DrawNode(Offset + Location);
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<Vector2> GenerateLocations()
|
||||
{
|
||||
for (int i = 0; i < BlockCount; i++)
|
||||
yield return Offset + Location + (new Vector2(1, 0) * i * (BlockSize.X + Margin));
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
if ((Location + Offset - position).Length() < thDistance)
|
||||
{
|
||||
return new ArrayHorizontalAnchorNode(this);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
Location += Offset;
|
||||
Offset = pivot;
|
||||
Location -= Offset;
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
Offset = pivot;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Editable);
|
||||
bw.Write(Location.X);
|
||||
bw.Write(Location.Y);
|
||||
bw.Write(Offset.X);
|
||||
bw.Write(Offset.Y);
|
||||
bw.Write(BlockSize.X);
|
||||
bw.Write(BlockSize.Y);
|
||||
bw.Write(BlockCount);
|
||||
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
Editable = br.ReadBoolean();
|
||||
Location = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
Offset = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
BlockSize = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
BlockCount = br.ReadInt32();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Edge;
|
||||
|
||||
public class EdgeElement:IGraphicsElement
|
||||
{
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
public Vector2 Location { get; set; }
|
||||
public float Rotation { get; set; }
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
float endX = Location.X + MathF.Cos(Rotation)*1000;
|
||||
float endY = Location.Y + MathF.Sin(Rotation) * 1000;
|
||||
float startX = Location.X - MathF.Cos(Rotation) * 1000;
|
||||
float startY = Location.Y - MathF.Sin(Rotation) * 1000;
|
||||
canvas.DrawLine(new Vector2(startX,startY), new Vector2(endX, endY), LineType.Construction);
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Edge;
|
||||
|
||||
public struct EdgeOrigin
|
||||
{
|
||||
public Vector2 Location { get; set; }
|
||||
public float Rotation { get; set; }
|
||||
public bool Found { get; set; }
|
||||
public float endX => Location.X + MathF.Cos(Rotation) * 1000;
|
||||
public float endY => Location.Y + MathF.Sin(Rotation) * 1000;
|
||||
public float startX => Location.X - MathF.Cos(Rotation) * 1000;
|
||||
public float startY => Location.Y - MathF.Sin(Rotation) * 1000;
|
||||
public float Slope=>(endY-startY)/(endX-startX);
|
||||
public float YIntercept => startY - (Slope * startX);
|
||||
public bool Intersection(EdgeOrigin other, out float x, out float y)
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
// Line 1
|
||||
Vector2 line1Start = new Vector2(startX, startY);
|
||||
|
||||
|
||||
// Line 2
|
||||
Vector2 line2Start = new Vector2(other.startX, other.startY);
|
||||
|
||||
|
||||
// Calculate the slopes of the lines
|
||||
double line1Slope = Slope;
|
||||
double line2Slope = other.Slope;
|
||||
|
||||
if (line1Slope == line2Slope) return false;
|
||||
|
||||
// Calculate the y-intercepts of the lines
|
||||
double line1Intercept = line1Start.Y - line1Slope * line1Start.X;
|
||||
double line2Intercept = line2Start.Y - line2Slope * line2Start.X;
|
||||
|
||||
// Calculate the intersection point
|
||||
double intersectionX = (line2Intercept - line1Intercept) / (line1Slope - line2Slope);
|
||||
double intersectionY = line1Slope * intersectionX + line1Intercept;
|
||||
|
||||
// Create the intersection point object
|
||||
x = (float)intersectionX;
|
||||
y = (float)intersectionY;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Location.ToString()} {Rotation} rad";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.FixedRectangle;
|
||||
|
||||
public class FixedRectangleAnchorNode:INode
|
||||
{
|
||||
private readonly FixedRectangleElement _owner;
|
||||
|
||||
public FixedRectangleAnchorNode(FixedRectangleElement owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
|
||||
_owner.Location=position-_owner.Offset;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.FixedRectangle;
|
||||
|
||||
public class FixedRectangleElement : IGraphicsElement
|
||||
{
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
public Vector2 Location { get; set; }
|
||||
public Vector2 Offset { get; set; }
|
||||
public Vector2 Size { get; set; }
|
||||
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
canvas.DrawRectangle(Offset + Location, Size, IsGood);
|
||||
canvas.DrawNode(Offset + Location);
|
||||
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
var location = Offset + Location;
|
||||
Cv2.Rectangle(mat,
|
||||
new Rect((int)location.X, (int)location.Y, (int)Size.X,
|
||||
(int)Size.Y),
|
||||
Scalar.White, -1);
|
||||
}
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
if ((Location + Offset - position).Length() < thDistance)
|
||||
{
|
||||
return new FixedRectangleAnchorNode(this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
Location += Offset;
|
||||
Offset = pivot;
|
||||
Location -= Offset;
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
Offset = pivot;
|
||||
}
|
||||
|
||||
public FixedRectangleElement()
|
||||
{
|
||||
Location = Vector2.Zero;
|
||||
Size = Vector2.Zero;
|
||||
Offset = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
13
Hawkeye.VisionBuilder.Workflow/Datatypes/Elements/ICanvas.cs
Normal file
13
Hawkeye.VisionBuilder.Workflow/Datatypes/Elements/ICanvas.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
|
||||
|
||||
public interface ICanvas
|
||||
{
|
||||
void DrawRectangle(Vector2 start, Vector2 size, bool isGood);
|
||||
void DrawLine(Vector2 start, Vector2 end, LineType type);
|
||||
void DrawPoly(Vector2[] points, bool isGood);
|
||||
void DrawCross(Vector2 location);
|
||||
void DrawNode(Vector2 location);
|
||||
void DrawArrow(Vector2 location, Vector2 direction);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Parent;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
|
||||
|
||||
public interface IGraphicsElement
|
||||
{
|
||||
|
||||
|
||||
|
||||
public bool Editable { get; set; }
|
||||
|
||||
public void Draw(ICanvas canvas);
|
||||
public void FillMat(Mat mat);
|
||||
public INode? GetNode(Vector2 position, float thDistance);
|
||||
public void SetPivot(Vector2 pivot);
|
||||
public void MovePivot(Vector2 pivot);
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
|
||||
|
||||
public interface INode
|
||||
{
|
||||
public void SetPosition(Vector2 position);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Line;
|
||||
|
||||
public class LineElement:IGraphicsElement
|
||||
{
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
|
||||
public Vector2 Start { get; set; }
|
||||
public Vector2 End { get; set; }
|
||||
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
canvas.DrawLine(Start, End,LineType.Construction);
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
|
||||
|
||||
public enum LineType
|
||||
{
|
||||
Good,
|
||||
Bad,
|
||||
Construction
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Parent;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin;
|
||||
|
||||
public class OriginElement
|
||||
{
|
||||
private static readonly OriginElement _default = new OriginElement();
|
||||
private Vector2 _location;
|
||||
|
||||
public static OriginElement Default => _default;
|
||||
|
||||
public Vector2 Location
|
||||
{
|
||||
get => _location;
|
||||
set => _location = value;
|
||||
}
|
||||
|
||||
|
||||
public OriginElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Parent;
|
||||
|
||||
public interface IHaveParentCoordinates
|
||||
{
|
||||
public Vector2 Location { get; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Parent;
|
||||
|
||||
public class NoParentCoordinates:IHaveParentCoordinates
|
||||
{
|
||||
private NoParentCoordinates()
|
||||
{
|
||||
}
|
||||
|
||||
public static NoParentCoordinates Instance { get; }=new NoParentCoordinates();
|
||||
public Vector2 Location { get; set; }=Vector2.Zero;
|
||||
public float Rotation { get; set; } = 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Parent;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly;
|
||||
|
||||
public class PolyElement : IGraphicsElement
|
||||
{
|
||||
|
||||
public IGraphicsElement Parent { get; set; }
|
||||
public Vector2 Location { get; set; }
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
canvas.DrawPoly(Points, IsGood);
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Vector2[] Points { get; set; } = Array.Empty<Vector2>();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
public class RectangleAnchorNode:INode
|
||||
{
|
||||
private readonly RectangleElement _owner;
|
||||
|
||||
public RectangleAnchorNode(RectangleElement owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
|
||||
_owner.Location=position-_owner.Offset;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Parent;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
public class RectangleElement : IGraphicsElement
|
||||
{
|
||||
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
|
||||
public Vector2 Location { get; set; }
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
public Vector2 Size { get; set; }
|
||||
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
|
||||
canvas.DrawRectangle(Offset+Location, Size,IsGood);
|
||||
canvas.DrawNode(Offset+Location);
|
||||
canvas.DrawNode(Offset+Location + Size);
|
||||
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
var location = Offset + Location;
|
||||
Cv2.Rectangle(mat,
|
||||
new Rect((int)location.X, (int)location.Y, (int)Size.X,
|
||||
(int)Size.Y),
|
||||
Scalar.White, -1);
|
||||
}
|
||||
|
||||
|
||||
public INode? GetNode(Vector2 position,float thDistance)
|
||||
{
|
||||
if ((Location + Offset - position).Length()< thDistance)
|
||||
{
|
||||
return new RectangleAnchorNode(this);
|
||||
}
|
||||
if((Size + Location+Offset - position).Length() < thDistance)
|
||||
{
|
||||
return new RectangleSizeNode(this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
Location += Offset;
|
||||
Offset =pivot;
|
||||
Location-=Offset;
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
Offset = pivot;
|
||||
}
|
||||
|
||||
public RectangleElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Editable);
|
||||
bw.Write(IsGood);
|
||||
bw.Write(Location.X);
|
||||
bw.Write(Location.Y);
|
||||
bw.Write(Offset.X);
|
||||
bw.Write(Offset.Y);
|
||||
bw.Write(Size.X);
|
||||
bw.Write(Size.Y);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
Editable = br.ReadBoolean();
|
||||
IsGood = br.ReadBoolean();
|
||||
Location = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
Offset = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
Size = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
}
|
||||
|
||||
public void SaveXML(XmlWriter writer)
|
||||
{
|
||||
writer.WriteStartElement("Editable");
|
||||
writer.WriteString(Editable.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("IsGood");
|
||||
writer.WriteString(IsGood.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("LocationX");
|
||||
writer.WriteString(Location.X.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("LocationY");
|
||||
writer.WriteString(Location.Y.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("OffsetX");
|
||||
writer.WriteString(Offset.X.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("OffsetY");
|
||||
writer.WriteString(Offset.Y.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("SizeX");
|
||||
writer.WriteString(Size.X.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("SizeY");
|
||||
writer.WriteString(Size.Y.ToString());
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
public void LoadXML(XmlReader reader)
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Editable")
|
||||
{
|
||||
Editable = bool.Parse(reader.ReadElementContentAsString());
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "IsGood")
|
||||
{
|
||||
IsGood = bool.Parse(reader.ReadElementContentAsString());
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "LocationX")
|
||||
{
|
||||
var x = float.Parse(reader.ReadElementContentAsString());
|
||||
Location = new Vector2(x, Location.Y);
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "LocationY")
|
||||
{
|
||||
var y = float.Parse(reader.ReadElementContentAsString());
|
||||
Location = new Vector2(Location.X, y);
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "OffsetX")
|
||||
{
|
||||
var x = float.Parse(reader.ReadElementContentAsString());
|
||||
Offset = new Vector2(x, Offset.Y);
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "OffsetY")
|
||||
{
|
||||
var y = float.Parse(reader.ReadElementContentAsString());
|
||||
Offset = new Vector2(Offset.X, y);
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "SizeX")
|
||||
{
|
||||
var x = float.Parse(reader.ReadElementContentAsString());
|
||||
Size = new Vector2(x, Size.Y);
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.Element && reader.Name == "SizeY")
|
||||
{
|
||||
var y = float.Parse(reader.ReadElementContentAsString());
|
||||
Size = new Vector2(Size.X, y);
|
||||
}
|
||||
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "RectangleElement")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
public class RectangleSizeNode:INode
|
||||
{
|
||||
private readonly RectangleElement _owner;
|
||||
|
||||
public RectangleSizeNode(RectangleElement owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
_owner.Size=position-_owner.Location - _owner.Offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.RotatedRectangle;
|
||||
|
||||
public class RotatedRectangleAnchorNode:INode
|
||||
{
|
||||
private readonly RotatedRectangleElement _owner;
|
||||
|
||||
public RotatedRectangleAnchorNode(RotatedRectangleElement owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
|
||||
_owner.Location=position-_owner.Offset;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
using OpenCvSharp;
|
||||
using Point = OpenCvSharp.Point;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.RotatedRectangle;
|
||||
|
||||
public class RotatedRectangleElement:IGraphicsElement
|
||||
{
|
||||
public bool Editable { get; set; }
|
||||
public bool IsGood { get; set; }
|
||||
|
||||
public Vector2 Location { get; set; }
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
public Vector2 Height { get; set; }
|
||||
public float HalfWidth { get; set; }
|
||||
|
||||
|
||||
public void Draw(ICanvas canvas)
|
||||
{
|
||||
var type = IsGood ? LineType.Good : LineType.Bad;
|
||||
var perpendicular = new Vector2(Height.Y, -Height.X);
|
||||
perpendicular.Normalize();
|
||||
|
||||
canvas.DrawLine(Offset + Location - perpendicular * HalfWidth, Offset + Location+perpendicular*HalfWidth, type);
|
||||
canvas.DrawLine(Offset + Location + Height - perpendicular * HalfWidth, Offset + Location + Height + perpendicular * HalfWidth, type);
|
||||
canvas.DrawLine(
|
||||
Offset + Location + perpendicular * HalfWidth
|
||||
, Offset + Location + Height + perpendicular * HalfWidth
|
||||
, type);
|
||||
canvas.DrawLine(
|
||||
Offset + Location - perpendicular * HalfWidth
|
||||
, Offset + Location + Height - perpendicular * HalfWidth
|
||||
, type);
|
||||
canvas.DrawNode(Offset + Location);
|
||||
canvas.DrawNode(Offset + Location + Height);
|
||||
canvas.DrawNode(Offset + Location + Height + perpendicular * HalfWidth);
|
||||
|
||||
canvas.DrawArrow(Offset + Location, Height);
|
||||
|
||||
}
|
||||
|
||||
public void FillMat(Mat mat)
|
||||
{
|
||||
var perpendicular = new Vector2(Height.Y, -Height.X);
|
||||
perpendicular.Normalize();
|
||||
var p1 = Offset + Location - perpendicular * HalfWidth;
|
||||
var p2 = Offset + Location + perpendicular * HalfWidth;
|
||||
var p3 = Offset + Location + Height + perpendicular * HalfWidth;
|
||||
var p4 = Offset + Location + Height - perpendicular * HalfWidth;
|
||||
mat.FillPoly(new IEnumerable<Point>[]
|
||||
{
|
||||
new List<Point>()
|
||||
{
|
||||
new Point(p1.X,p1.Y),
|
||||
new Point(p2.X,p2.Y),
|
||||
new Point(p3.X,p3.Y),
|
||||
new Point(p4.X,p4.Y),
|
||||
}
|
||||
},Scalar.White);
|
||||
}
|
||||
|
||||
|
||||
public INode? GetNode(Vector2 position, float thDistance)
|
||||
{
|
||||
if ((Location + Offset - position).Length() < thDistance)
|
||||
{
|
||||
return new RotatedRectangleAnchorNode(this);
|
||||
}
|
||||
if ((Height + Location + Offset - position).Length() < thDistance)
|
||||
{
|
||||
return new RotatedRectangleHeightNode(this);
|
||||
}
|
||||
|
||||
var perpendicular = new Vector2(Height.Y, -Height.X);
|
||||
perpendicular.Normalize();
|
||||
if (((Offset + Location + Height + perpendicular * HalfWidth) - position).Length() < thDistance)
|
||||
{
|
||||
return new RotatedRectangleHalfWidthNode(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetPivot(Vector2 pivot)
|
||||
{
|
||||
Location += Offset;
|
||||
Offset = pivot;
|
||||
Location -= Offset;
|
||||
}
|
||||
|
||||
public void MovePivot(Vector2 pivot)
|
||||
{
|
||||
Offset = pivot;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Editable);
|
||||
bw.Write(IsGood);
|
||||
bw.Write(Location.X);
|
||||
bw.Write(Location.Y);
|
||||
bw.Write(Offset.X);
|
||||
bw.Write(Offset.Y);
|
||||
bw.Write(Height.X);
|
||||
bw.Write(Height.Y);
|
||||
bw.Write(HalfWidth);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader br)
|
||||
{
|
||||
Editable = br.ReadBoolean();
|
||||
IsGood = br.ReadBoolean();
|
||||
Location = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
Offset = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
Height = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
HalfWidth = br.ReadSingle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.RotatedRectangle;
|
||||
|
||||
public class RotatedRectangleHalfWidthNode:INode
|
||||
{
|
||||
private readonly RotatedRectangleElement _owner;
|
||||
|
||||
public RotatedRectangleHalfWidthNode(RotatedRectangleElement owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
_owner.HalfWidth = (position - (_owner.Height + _owner.Location + _owner.Offset)).Length();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.RotatedRectangle;
|
||||
|
||||
public class RotatedRectangleHeightNode:INode
|
||||
{
|
||||
private readonly RotatedRectangleElement _owner;
|
||||
|
||||
public RotatedRectangleHeightNode(RotatedRectangleElement owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 position)
|
||||
{
|
||||
_owner.Height=position-_owner.Location;
|
||||
}
|
||||
}
|
||||
7
Hawkeye.VisionBuilder.Workflow/Datatypes/FilePath.cs
Normal file
7
Hawkeye.VisionBuilder.Workflow/Datatypes/FilePath.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
public class FilePath
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Format { get; set; } = "Model file(*.h5)|*.h5";
|
||||
}
|
||||
10
Hawkeye.VisionBuilder.Workflow/Datatypes/HawkeyeImage.cs
Normal file
10
Hawkeye.VisionBuilder.Workflow/Datatypes/HawkeyeImage.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
public class HawkeyeImage
|
||||
{
|
||||
|
||||
public Mat ImageData { get; init; }
|
||||
public string Filename { get; init; }
|
||||
}
|
||||
68
Hawkeye.VisionBuilder.Workflow/Datatypes/LutData.cs
Normal file
68
Hawkeye.VisionBuilder.Workflow/Datatypes/LutData.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
public class LutData
|
||||
{
|
||||
public LutData()
|
||||
{
|
||||
LUT = new byte[256 * 4];
|
||||
|
||||
Points = new Vector2[4][];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Points[i] = new Vector2[2];
|
||||
|
||||
Points[i][0] = new Vector2(0, 0);
|
||||
Points[i][1] = new Vector2(255, 255);
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
LUT[i] = (byte)i;
|
||||
LUT[i+256] = (byte)i;
|
||||
LUT[i+256*2] = (byte)i;
|
||||
LUT[i+256*3] = (byte)i;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] LUT { get; set; }
|
||||
public Vector2[][] Points { get; set; }
|
||||
|
||||
public void Serialize(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(LUT.Length);
|
||||
bw.Write(LUT);
|
||||
bw.Write(Points.Length);
|
||||
for (int i = 0; i < Points.Length; i++)
|
||||
{
|
||||
bw.Write(Points[i].Length);
|
||||
for (int j = 0; j < Points[i].Length; j++)
|
||||
{
|
||||
bw.Write(Points[i][j].X);
|
||||
bw.Write(Points[i][j].Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Deserialize(BinaryReader br)
|
||||
{
|
||||
var length = br.ReadInt32();
|
||||
LUT = br.ReadBytes(length);
|
||||
length = br.ReadInt32();
|
||||
Points = new Vector2[length][];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var length2 = br.ReadInt32();
|
||||
Points[i] = new Vector2[length2];
|
||||
for (int j = 0; j < length2; j++)
|
||||
{
|
||||
Points[i][j] = new Vector2(br.ReadSingle(), br.ReadSingle());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
6
Hawkeye.VisionBuilder.Workflow/Datatypes/Pattern.cs
Normal file
6
Hawkeye.VisionBuilder.Workflow/Datatypes/Pattern.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
public class Pattern
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
public class SelectFromList
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public Func<List<string>> GetList { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Configurations>Debug;Release;CPU</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ILGPU" Version="1.5.1" />
|
||||
<PackageReference Include="IronPython" Version="3.4.0" />
|
||||
<PackageReference Include="MessagePack" Version="2.5.108" />
|
||||
|
||||
<PackageReference Include="NetMQ" Version="4.0.1.11" />
|
||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
||||
<PackageReference Include="OpenCvSharp4" Version="4.6.0.20220608" />
|
||||
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.6.0.20220608" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
|
||||
</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>
|
||||
<Folder Include="Datatypes\Elements\Edge\" />
|
||||
<Folder Include="Datatypes\Elements\Line\" />
|
||||
<Folder Include="Overrides\" />
|
||||
<Folder Include="Operations\Edge\" />
|
||||
<Folder Include="Operations\Transformations\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
8
Hawkeye.VisionBuilder.Workflow/Links/IHaveEdge.cs
Normal file
8
Hawkeye.VisionBuilder.Workflow/Links/IHaveEdge.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Edge;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
public interface IHaveEdge
|
||||
{
|
||||
public EdgeOrigin EdgeOrigin { get; }
|
||||
}
|
||||
8
Hawkeye.VisionBuilder.Workflow/Links/IHaveImage.cs
Normal file
8
Hawkeye.VisionBuilder.Workflow/Links/IHaveImage.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
public interface IHaveImage
|
||||
{
|
||||
public HawkeyeImage Image { get; }
|
||||
}
|
||||
12
Hawkeye.VisionBuilder.Workflow/Links/IHaveOrigin.cs
Normal file
12
Hawkeye.VisionBuilder.Workflow/Links/IHaveOrigin.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
public interface IHaveOrigin
|
||||
{
|
||||
public Guid Id { get; }
|
||||
public OriginElement Origin { get; }
|
||||
}
|
||||
8
Hawkeye.VisionBuilder.Workflow/Links/IHaveSearchArea.cs
Normal file
8
Hawkeye.VisionBuilder.Workflow/Links/IHaveSearchArea.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
public interface IHaveSearchArea
|
||||
{
|
||||
public RectangleElement SearchArea { get; set; }
|
||||
}
|
||||
37
Hawkeye.VisionBuilder.Workflow/Links/NoOrigin.cs
Normal file
37
Hawkeye.VisionBuilder.Workflow/Links/NoOrigin.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Edge;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
public class NoOrigin:BaseOperation,IHaveOrigin,IHaveEdge
|
||||
{
|
||||
private NoOrigin()
|
||||
{
|
||||
}
|
||||
|
||||
public static NoOrigin Instance { get; } = new NoOrigin();
|
||||
|
||||
public Guid Id { get; } = new Guid();
|
||||
public OriginElement Origin { get; } = new OriginElement();
|
||||
public string Label { get; set; } = "No origin";
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IHaveOrigin Parent { get; set; }
|
||||
|
||||
public EdgeOrigin EdgeOrigin { get; }
|
||||
}
|
||||
314
Hawkeye.VisionBuilder.Workflow/MathHelper.cs
Normal file
314
Hawkeye.VisionBuilder.Workflow/MathHelper.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains commonly used precalculated values and mathematical operations.
|
||||
/// </summary>
|
||||
public static class MathHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the mathematical constant e(2.71828175).
|
||||
/// </summary>
|
||||
public const float E = (float)Math.E;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the log base ten of e(0.4342945).
|
||||
/// </summary>
|
||||
public const float Log10E = 0.4342945f;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the log base two of e(1.442695).
|
||||
/// </summary>
|
||||
public const float Log2E = 1.442695f;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the value of pi(3.14159274).
|
||||
/// </summary>
|
||||
public const float Pi = (float)Math.PI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the value of pi divided by two(1.57079637).
|
||||
/// </summary>
|
||||
public const float PiOver2 = (float)(Math.PI / 2.0);
|
||||
|
||||
/// <summary>
|
||||
/// Represents the value of pi divided by four(0.7853982).
|
||||
/// </summary>
|
||||
public const float PiOver4 = (float)(Math.PI / 4.0);
|
||||
|
||||
/// <summary>
|
||||
/// Represents the value of pi times two(6.28318548).
|
||||
/// </summary>
|
||||
public const float TwoPi = (float)(Math.PI * 2.0);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Cartesian coordinate for one axis of a point that is defined by a given triangle and two normalized barycentric (areal) coordinates.
|
||||
/// </summary>
|
||||
/// <param name="value1">The coordinate on one axis of vertex 1 of the defining triangle.</param>
|
||||
/// <param name="value2">The coordinate on the same axis of vertex 2 of the defining triangle.</param>
|
||||
/// <param name="value3">The coordinate on the same axis of vertex 3 of the defining triangle.</param>
|
||||
/// <param name="amount1">The normalized barycentric (areal) coordinate b2, equal to the weighting factor for vertex 2, the coordinate of which is specified in value2.</param>
|
||||
/// <param name="amount2">The normalized barycentric (areal) coordinate b3, equal to the weighting factor for vertex 3, the coordinate of which is specified in value3.</param>
|
||||
/// <returns>Cartesian coordinate of the specified point with respect to the axis being used.</returns>
|
||||
public static float Barycentric(float value1, float value2, float value3, float amount1, float amount2)
|
||||
{
|
||||
return value1 + (value2 - value1) * amount1 + (value3 - value1) * amount2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a Catmull-Rom interpolation using the specified positions.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first position in the interpolation.</param>
|
||||
/// <param name="value2">The second position in the interpolation.</param>
|
||||
/// <param name="value3">The third position in the interpolation.</param>
|
||||
/// <param name="value4">The fourth position in the interpolation.</param>
|
||||
/// <param name="amount">Weighting factor.</param>
|
||||
/// <returns>A position that is the result of the Catmull-Rom interpolation.</returns>
|
||||
public static float CatmullRom(float value1, float value2, float value3, float value4, float amount)
|
||||
{
|
||||
// Using formula from http://www.mvps.org/directx/articles/catmull/
|
||||
// Internally using doubles not to lose precission
|
||||
double amountSquared = amount * amount;
|
||||
double amountCubed = amountSquared * amount;
|
||||
return (float)(0.5 * (2.0 * value2 +
|
||||
(value3 - value1) * amount +
|
||||
(2.0 * value1 - 5.0 * value2 + 4.0 * value3 - value4) * amountSquared +
|
||||
(3.0 * value2 - value1 - 3.0 * value3 + value4) * amountCubed));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restricts a value to be within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to clamp.</param>
|
||||
/// <param name="min">The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c> will be returned.</param>
|
||||
/// <param name="max">The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c> will be returned.</param>
|
||||
/// <returns>The clamped value.</returns>
|
||||
public static float Clamp(float value, float min, float max)
|
||||
{
|
||||
// First we check to see if we're greater than the max
|
||||
value = (value > max) ? max : value;
|
||||
|
||||
// Then we check to see if we're less than the min.
|
||||
value = (value < min) ? min : value;
|
||||
|
||||
// There's no check to see if min > max.
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restricts a value to be within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to clamp.</param>
|
||||
/// <param name="min">The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c> will be returned.</param>
|
||||
/// <param name="max">The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c> will be returned.</param>
|
||||
/// <returns>The clamped value.</returns>
|
||||
public static int Clamp(int value, int min, int max)
|
||||
{
|
||||
value = (value > max) ? max : value;
|
||||
value = (value < min) ? min : value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the absolute value of the difference of two values.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Source value.</param>
|
||||
/// <returns>Distance between the two values.</returns>
|
||||
public static float Distance(float value1, float value2)
|
||||
{
|
||||
return Math.Abs(value1 - value2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a Hermite spline interpolation.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source position.</param>
|
||||
/// <param name="tangent1">Source tangent.</param>
|
||||
/// <param name="value2">Source position.</param>
|
||||
/// <param name="tangent2">Source tangent.</param>
|
||||
/// <param name="amount">Weighting factor.</param>
|
||||
/// <returns>The result of the Hermite spline interpolation.</returns>
|
||||
public static float Hermite(float value1, float tangent1, float value2, float tangent2, float amount)
|
||||
{
|
||||
// All transformed to double not to lose precission
|
||||
// Otherwise, for high numbers of param:amount the result is NaN instead of Infinity
|
||||
double v1 = value1, v2 = value2, t1 = tangent1, t2 = tangent2, s = amount, result;
|
||||
double sCubed = s * s * s;
|
||||
double sSquared = s * s;
|
||||
|
||||
if (amount == 0f)
|
||||
result = value1;
|
||||
else if (amount == 1f)
|
||||
result = value2;
|
||||
else
|
||||
result = (2 * v1 - 2 * v2 + t2 + t1) * sCubed +
|
||||
(3 * v2 - 3 * v1 - 2 * t1 - t2) * sSquared +
|
||||
t1 * s +
|
||||
v1;
|
||||
return (float)result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two values.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Destination value.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of value2.</param>
|
||||
/// <returns>Interpolated value.</returns>
|
||||
/// <remarks>This method performs the linear interpolation based on the following formula:
|
||||
/// <code>value1 + (value2 - value1) * amount</code>.
|
||||
/// Passing amount a value of 0 will cause value1 to be returned, a value of 1 will cause value2 to be returned.
|
||||
/// See <see cref="MathHelper.LerpPrecise"/> for a less efficient version with more precision around edge cases.
|
||||
/// </remarks>
|
||||
public static float Lerp(float value1, float value2, float amount)
|
||||
{
|
||||
return value1 + (value2 - value1) * amount;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two values.
|
||||
/// This method is a less efficient, more precise version of <see cref="MathHelper.Lerp"/>.
|
||||
/// See remarks for more info.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Destination value.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of value2.</param>
|
||||
/// <returns>Interpolated value.</returns>
|
||||
/// <remarks>This method performs the linear interpolation based on the following formula:
|
||||
/// <code>((1 - amount) * value1) + (value2 * amount)</code>.
|
||||
/// Passing amount a value of 0 will cause value1 to be returned, a value of 1 will cause value2 to be returned.
|
||||
/// This method does not have the floating point precision issue that <see cref="MathHelper.Lerp"/> has.
|
||||
/// i.e. If there is a big gap between value1 and value2 in magnitude (e.g. value1=10000000000000000, value2=1),
|
||||
/// right at the edge of the interpolation range (amount=1), <see cref="MathHelper.Lerp"/> will return 0 (whereas it should return 1).
|
||||
/// This also holds for value1=10^17, value2=10; value1=10^18,value2=10^2... so on.
|
||||
/// For an in depth explanation of the issue, see below references:
|
||||
/// Relevant Wikipedia Article: https://en.wikipedia.org/wiki/Linear_interpolation#Programming_language_support
|
||||
/// Relevant StackOverflow Answer: http://stackoverflow.com/questions/4353525/floating-point-linear-interpolation#answer-23716956
|
||||
/// </remarks>
|
||||
public static float LerpPrecise(float value1, float value2, float amount)
|
||||
{
|
||||
return ((1 - amount) * value1) + (value2 * amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the greater of two values.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Source value.</param>
|
||||
/// <returns>The greater value.</returns>
|
||||
public static float Max(float value1, float value2)
|
||||
{
|
||||
return value1 > value2 ? value1 : value2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the greater of two values.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Source value.</param>
|
||||
/// <returns>The greater value.</returns>
|
||||
public static int Max(int value1, int value2)
|
||||
{
|
||||
return value1 > value2 ? value1 : value2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the lesser of two values.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Source value.</param>
|
||||
/// <returns>The lesser value.</returns>
|
||||
public static float Min(float value1, float value2)
|
||||
{
|
||||
return value1 < value2 ? value1 : value2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the lesser of two values.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Source value.</param>
|
||||
/// <returns>The lesser value.</returns>
|
||||
public static int Min(int value1, int value2)
|
||||
{
|
||||
return value1 < value2 ? value1 : value2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates between two values using a cubic equation.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source value.</param>
|
||||
/// <param name="value2">Source value.</param>
|
||||
/// <param name="amount">Weighting value.</param>
|
||||
/// <returns>Interpolated value.</returns>
|
||||
public static float SmoothStep(float value1, float value2, float amount)
|
||||
{
|
||||
// It is expected that 0 < amount < 1
|
||||
// If amount < 0, return value1
|
||||
// If amount > 1, return value2
|
||||
float result = MathHelper.Clamp(amount, 0f, 1f);
|
||||
result = MathHelper.Hermite(value1, 0f, value2, 0f, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
/// <param name="radians">The angle in radians.</param>
|
||||
/// <returns>The angle in degrees.</returns>
|
||||
/// <remarks>
|
||||
/// This method uses double precission internally,
|
||||
/// though it returns single float
|
||||
/// Factor = 180 / pi
|
||||
/// </remarks>
|
||||
public static float ToDegrees(float radians)
|
||||
{
|
||||
return (float)(radians * 57.295779513082320876798154814105);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts degrees to radians.
|
||||
/// </summary>
|
||||
/// <param name="degrees">The angle in degrees.</param>
|
||||
/// <returns>The angle in radians.</returns>
|
||||
/// <remarks>
|
||||
/// This method uses double precission internally,
|
||||
/// though it returns single float
|
||||
/// Factor = pi / 180
|
||||
/// </remarks>
|
||||
public static float ToRadians(float degrees)
|
||||
{
|
||||
return (float)(degrees * 0.017453292519943295769236907684886);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduces a given angle to a value between π and -π.
|
||||
/// </summary>
|
||||
/// <param name="angle">The angle to reduce, in radians.</param>
|
||||
/// <returns>The new angle, in radians.</returns>
|
||||
public static float WrapAngle(float angle)
|
||||
{
|
||||
if ((angle > -Pi) && (angle <= Pi))
|
||||
return angle;
|
||||
angle %= TwoPi;
|
||||
if (angle <= -Pi)
|
||||
return angle + TwoPi;
|
||||
if (angle > Pi)
|
||||
return angle - TwoPi;
|
||||
return angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if value is powered by two.
|
||||
/// </summary>
|
||||
/// <param name="value">A value.</param>
|
||||
/// <returns><c>true</c> if <c>value</c> is powered by two; otherwise <c>false</c>.</returns>
|
||||
public static bool IsPowerOfTwo(int value)
|
||||
{
|
||||
return (value > 0) && ((value & (value - 1)) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
2352
Hawkeye.VisionBuilder.Workflow/Matrix.cs
Normal file
2352
Hawkeye.VisionBuilder.Workflow/Matrix.cs
Normal file
File diff suppressed because it is too large
Load Diff
8
Hawkeye.VisionBuilder.Workflow/OperationDescription.cs
Normal file
8
Hawkeye.VisionBuilder.Workflow/OperationDescription.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow;
|
||||
|
||||
public class OperationDescription
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Category { get; set; }
|
||||
public Type Type { get; set; }
|
||||
}
|
||||
58
Hawkeye.VisionBuilder.Workflow/OperationDiscoveryService.cs
Normal file
58
Hawkeye.VisionBuilder.Workflow/OperationDiscoveryService.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using Ninject;
|
||||
using Ninject.Infrastructure.Language;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow;
|
||||
|
||||
public class OperationDiscoveryService
|
||||
{
|
||||
|
||||
private readonly WorkflowList _workflowList;
|
||||
private readonly StandardKernel _kernel;
|
||||
|
||||
public OperationDiscoveryService( WorkflowList workflowList)
|
||||
{
|
||||
|
||||
_workflowList = workflowList;
|
||||
_kernel = new StandardKernel();
|
||||
|
||||
_kernel.Bind<WorkflowList>().ToConstant(workflowList);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public List<OperationDescription> DiscoverOperations()
|
||||
{
|
||||
return this.GetType().Assembly.GetTypes()
|
||||
.Where(x => x.IsSubclassOf(typeof(BaseOperation))&&!x.HasAttribute(typeof(IgnoreOperationAttribute)))
|
||||
.Where(x=>x.Name!=nameof(NoOrigin))
|
||||
.Select(x=>new OperationDescription()
|
||||
{
|
||||
Name = BaseOperation.MakeOperationName(x.Name),
|
||||
Category = GetCategory(x),
|
||||
Type = x
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private string GetCategory(Type t)
|
||||
{
|
||||
var category=t.GetCustomAttribute(typeof(Category));
|
||||
if (category!=null)
|
||||
{
|
||||
return (category as Category).Name;
|
||||
}
|
||||
|
||||
return "General";
|
||||
}
|
||||
|
||||
public BaseOperation CreateInstance(Type type)
|
||||
{
|
||||
return (BaseOperation)_kernel.Get(type);
|
||||
}
|
||||
|
||||
}
|
||||
125
Hawkeye.VisionBuilder.Workflow/Operations/AI/AnomalyAI.cs
Normal file
125
Hawkeye.VisionBuilder.Workflow/Operations/AI/AnomalyAI.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
public class AnomalyAI: BaseOperation
|
||||
{
|
||||
|
||||
private readonly string _modelName;
|
||||
|
||||
public AnomalyAI()
|
||||
{
|
||||
|
||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
||||
CanHaveProcessingError = false;
|
||||
}
|
||||
|
||||
private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
private int _imageWidth;
|
||||
private int _imageHeight;
|
||||
|
||||
private bool _initialized = false;
|
||||
private FilePath _modelFilePath = new FilePath();
|
||||
|
||||
|
||||
public string MemorySlotName { get; set; } = "Anomaly";
|
||||
|
||||
public FilePath ModelFilePath
|
||||
{
|
||||
get => _modelFilePath;
|
||||
set
|
||||
{
|
||||
_modelFilePath = value;
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
if (!CheckImageExists(context)) return;
|
||||
if (!CheckColorful(context)) return;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath?.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return;
|
||||
}
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
||||
|
||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||
_imageWidth = size[2];
|
||||
_imageHeight = size[1];
|
||||
|
||||
var outputSize = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_output_size"));
|
||||
var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray();
|
||||
|
||||
|
||||
var currentImage = context.ActiveImage;
|
||||
|
||||
var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var resized = rightColor.Resize(new Size(_imageWidth,_imageHeight));
|
||||
int dataSize = resized.Rows * resized.Cols * resized.ElemSize();
|
||||
var byteArray = new byte[dataSize];
|
||||
Marshal.Copy(resized.Data, byteArray, 0, dataSize);
|
||||
|
||||
|
||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall( "predict_anomaly", byteArray));
|
||||
|
||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
||||
|
||||
var defect = result.Take(1).ToArray()[0];
|
||||
|
||||
if (defect > 50)
|
||||
{
|
||||
|
||||
|
||||
byte[] resultColor = result.Skip(1).ToArray();
|
||||
|
||||
|
||||
|
||||
// black and white mask
|
||||
var mask = new Mat(30, 48, MatType.CV_8UC1, resultColor);
|
||||
|
||||
var maskResized = mask.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));
|
||||
|
||||
context.ActiveImage = new HawkeyeImage() {ImageData = maskResized};
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ActiveImage = new HawkeyeImage() { ImageData = new Mat(currentImage.ImageData.Size(), MatType.CV_8UC1, new Scalar(0)) };
|
||||
}
|
||||
|
||||
Result = true;
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(_modelFilePath.Path);
|
||||
bw.Write(_modelFilePath.Format);
|
||||
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
_modelFilePath.Path = br.ReadString();
|
||||
_modelFilePath.Format = br.ReadString();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.FixedRectangle;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.ArrayHorizontal;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
[IgnoreOperation]
|
||||
public class ArrayModelMatchingOperation:BaseOperation
|
||||
{
|
||||
private readonly WorkflowList _workflowList;
|
||||
private readonly CSharpDataTransferMQ _transfer;
|
||||
public Guid ReferenceId { get; set; } = new Guid();
|
||||
public ArrayHorizontalElement SearchArea { get; set; }
|
||||
public string ModelName { get; set; } = "cls_candy5.h5";
|
||||
public Action ExportImages { get; set; }
|
||||
public int Margin { get; set; } = 5;
|
||||
public int Amount { get; set; } = 4;
|
||||
|
||||
private int _channels = 3;
|
||||
private List<Mat> _lastCutImages;
|
||||
|
||||
public ArrayModelMatchingOperation(WorkflowList workflowList)
|
||||
{
|
||||
_workflowList = workflowList;
|
||||
_transfer = PythonModelProxy.GetInterface();
|
||||
|
||||
|
||||
SearchArea = new ArrayHorizontalElement()
|
||||
{
|
||||
Editable = true,
|
||||
Location = Vector2.One * 100,
|
||||
BlockSize = new Vector2(100, 100),
|
||||
|
||||
};
|
||||
ExportImages = () =>
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(@"..\Data\Export", ModelName)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(@"..\Data\Export", ModelName));
|
||||
}
|
||||
|
||||
// save last cut images
|
||||
var i = 0;
|
||||
foreach (var cutImage in _lastCutImages)
|
||||
{
|
||||
cutImage.SaveImage(Path.Combine(@"..\Data\Export", ModelName, $"{Guid.NewGuid().ToString()}.png"));
|
||||
}
|
||||
|
||||
};
|
||||
ReloadModelInfo();
|
||||
}
|
||||
|
||||
private void ReloadModelInfo()
|
||||
{
|
||||
var responseBytes = _transfer.TransferData(ModelName, 2, Array.Empty<byte>());
|
||||
int[] sizes = new int[3];
|
||||
Buffer.BlockCopy(responseBytes, 0, sizes, 0, 12);
|
||||
SearchArea.BlockSize = new Vector2(sizes[1], sizes[0]);
|
||||
_channels = sizes[2];
|
||||
}
|
||||
|
||||
byte[] MatToBytes(Mat mat)
|
||||
{
|
||||
if (mat.Channels() == 3)
|
||||
{
|
||||
mat = mat.CvtColor(_channels == 3 ? ColorConversionCodes.BGR2RGB : ColorConversionCodes.BGR2GRAY);
|
||||
}
|
||||
|
||||
|
||||
IntPtr dataPtr = mat.Data;
|
||||
|
||||
// Calculate the size of the image data
|
||||
int dataSize = mat.Rows * mat.Cols * mat.ElemSize();
|
||||
|
||||
|
||||
|
||||
|
||||
// Copy the image data into a byte array
|
||||
byte[] byteArray = new byte[dataSize];
|
||||
|
||||
Marshal.Copy(dataPtr, byteArray, 0, dataSize);
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
Result = true;
|
||||
if (!File.Exists(Path.Combine(@"..\Data\AI", ModelName)))
|
||||
{
|
||||
Status = $"Model `{ModelName}` not found";
|
||||
Result = false;
|
||||
return;
|
||||
}
|
||||
if (!CheckImageExists(context)) return;
|
||||
switch (_channels)
|
||||
{
|
||||
case 3 when !CheckColorful(context):
|
||||
case 1 when !CheckGrayscale(context):
|
||||
return;
|
||||
}
|
||||
|
||||
var refLocation = _workflowList.GetOriginById(ReferenceId).Origin.Location;
|
||||
SearchArea.MovePivot(refLocation);
|
||||
|
||||
SearchArea.Margin=Margin;
|
||||
SearchArea.BlockCount=Amount;
|
||||
var img = context.ActiveImage.ImageData;
|
||||
|
||||
_lastCutImages = SearchArea
|
||||
.GenerateLocations()
|
||||
.Select(x => img[(int) x.Y, (int) x.Y + (int) SearchArea.BlockSize.Y, (int) x.X,
|
||||
(int) x.X + (int) SearchArea.BlockSize.X]).ToList();
|
||||
var images= _lastCutImages.Select(MatToBytes).ToList();
|
||||
|
||||
|
||||
var bytes= images.SelectMany(x => x).ToArray();
|
||||
|
||||
var len = images.Count;
|
||||
var bytes2 = new byte[bytes.Length + 4];
|
||||
bytes2[3] = (byte)(len >> 24);
|
||||
bytes2[2] = (byte)(len >> 16);
|
||||
bytes2[1] = (byte)(len >> 8);
|
||||
bytes2[0] = (byte)(len >> 0);
|
||||
|
||||
Array.Copy(bytes, 0, bytes2, 4, bytes.Length);
|
||||
|
||||
var answer = _transfer.TransferData(ModelName, 0,bytes2);
|
||||
float[] floatArray = new float[answer.Length / 4];
|
||||
Buffer.BlockCopy(answer, 0, floatArray, 0, answer.Length);
|
||||
|
||||
// Print answer
|
||||
for (int i = 0; i < Amount; i++)
|
||||
{
|
||||
Console.WriteLine(floatArray[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Amount; i++)
|
||||
{
|
||||
SearchArea.IsGood[i] = floatArray[i] > 0.7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
context.GraphicsElements.Add(SearchArea);
|
||||
Result = SearchArea.IsGood.All(x=>x.Value==true);
|
||||
}
|
||||
|
||||
public override void SetParameters(Dictionary<string, object> parameters)
|
||||
{
|
||||
base.SetParameters(parameters);
|
||||
ReloadModelInfo();
|
||||
SearchArea.SetPivot(_workflowList.GetOriginById(ReferenceId).Origin.Location);
|
||||
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(ReferenceId.ToString());
|
||||
bw.Write(Margin);
|
||||
bw.Write(Amount);
|
||||
bw.Write(ModelName);
|
||||
SearchArea.Save(bw);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
ReferenceId = new Guid(br.ReadString());
|
||||
Margin = br.ReadInt32();
|
||||
Amount = br.ReadInt32();
|
||||
ModelName = br.ReadString();
|
||||
SearchArea = new ArrayHorizontalElement();
|
||||
SearchArea.Load(br);
|
||||
ReloadModelInfo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.ArrayHorizontal;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
[IgnoreOperation]
|
||||
public class ArrayModelSamplerOperation:BaseOperation
|
||||
{
|
||||
private Mat _lastImage;
|
||||
|
||||
public RectangleElement SearchArea { get; set; }
|
||||
public string SampleStorage { get; set; } = "samples.storage";
|
||||
public Action Sample { get; set; }
|
||||
|
||||
public ArrayModelSamplerOperation()
|
||||
{
|
||||
SearchArea = new RectangleElement()
|
||||
{
|
||||
Editable = true,
|
||||
Location = Vector2.One * 100,
|
||||
Size = new Vector2(100, 100),
|
||||
|
||||
};
|
||||
Sample = SaveSample;
|
||||
}
|
||||
|
||||
byte[] MatToBytes(Mat mat)
|
||||
{
|
||||
if (mat.Channels() == 3)
|
||||
{
|
||||
mat = mat.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
}
|
||||
|
||||
|
||||
IntPtr dataPtr = mat.Data;
|
||||
int dataSize = mat.Rows * mat.Cols * mat.ElemSize();
|
||||
byte[] byteArray = new byte[dataSize];
|
||||
|
||||
Marshal.Copy(dataPtr, byteArray, 0, dataSize);
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
private void SaveSample()
|
||||
{
|
||||
|
||||
|
||||
|
||||
Mat mask = new Mat(_lastImage.Size(), MatType.CV_8UC1, Scalar.Black);
|
||||
SearchArea.FillMat(mask);
|
||||
var imageBytes = MatToBytes(_lastImage);
|
||||
var maskBytes = MatToBytes(mask);
|
||||
// Append to the end of storage file
|
||||
|
||||
var storagePath=Path.Combine("..\\Data\\AI", SampleStorage);
|
||||
|
||||
|
||||
|
||||
using (var file = new System.IO.FileStream(storagePath, System.IO.FileMode.Append))
|
||||
{
|
||||
// Write size
|
||||
file.Write(BitConverter.GetBytes(_lastImage.Width), 0, 4);
|
||||
file.Write(BitConverter.GetBytes(_lastImage.Height), 0, 4);
|
||||
file.Write(imageBytes, 0, imageBytes.Length);
|
||||
// Write mask
|
||||
file.Write(maskBytes, 0, maskBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
if (!CheckImageExists(context)) return;
|
||||
if(!CheckColorful(context)) return;
|
||||
|
||||
context.GraphicsElements.Add(SearchArea);
|
||||
|
||||
_lastImage=context.ActiveImage.ImageData;
|
||||
Result = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using OpenCvSharp;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Origin;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
[IgnoreOperation]
|
||||
public class BackgroundSeparationModelOperation:BaseOperation,IHaveOrigin
|
||||
{
|
||||
private readonly CSharpDataTransferMQ _transfer;
|
||||
private int _channels;
|
||||
public string ModelName { get; set; } = "background.h5";
|
||||
|
||||
|
||||
public BackgroundSeparationModelOperation()
|
||||
{
|
||||
_transfer = PythonModelProxy.GetInterface();
|
||||
|
||||
}
|
||||
|
||||
byte[] MatToBytes(Mat mat)
|
||||
{
|
||||
if (mat.Channels() == 3)
|
||||
{
|
||||
mat = mat.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
}
|
||||
|
||||
|
||||
IntPtr dataPtr = mat.Data;
|
||||
int dataSize = mat.Rows * mat.Cols * mat.ElemSize();
|
||||
byte[] byteArray = new byte[dataSize];
|
||||
|
||||
Marshal.Copy(dataPtr, byteArray, 0, dataSize);
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
Result = true;
|
||||
if (!CheckImageExists(context)) return;
|
||||
if (!CheckColorful(context)) return;
|
||||
if (!File.Exists(Path.Combine(@"..\Data\AI", ModelName)))
|
||||
{
|
||||
Status= $"Model `{ModelName}` not found";
|
||||
Result = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var img = context.ActiveImage.ImageData;
|
||||
|
||||
var bytes= MatToBytes(img);
|
||||
var responseBytes = _transfer.TransferData(ModelName, 3, bytes);
|
||||
var response = new Mat(img.Rows, img.Cols, MatType.CV_8UC1, responseBytes);
|
||||
|
||||
var contours = response.Threshold(128, 255, ThresholdTypes.Binary).FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
if (contours.Length == 0)
|
||||
{
|
||||
Status = "No object found";
|
||||
Result = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// find biggest contour
|
||||
var max = 0;
|
||||
var maxIndex = 0;
|
||||
for (var i = 0; i < contours.Length; i++)
|
||||
{
|
||||
var area = Cv2.ContourArea(contours[i]);
|
||||
if (area > max)
|
||||
{
|
||||
max = (int)area;
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
PolyElement poly = new PolyElement() { IsGood = true,Editable = false};
|
||||
|
||||
poly.Points = contours[maxIndex].Select(x => new Vector2(x.X, x.Y)).ToArray();
|
||||
|
||||
context.GraphicsElements.Add(poly);
|
||||
|
||||
|
||||
var rect = Cv2.MinAreaRect(contours[maxIndex]);
|
||||
var bound = rect.BoundingRect();
|
||||
Origin=new OriginElement()
|
||||
{
|
||||
Location = new Vector2(bound.Left, bound.Top),
|
||||
};
|
||||
|
||||
Status= $"Object found at {Origin.Location}";
|
||||
Result = true;
|
||||
|
||||
}
|
||||
|
||||
public override void SetParameters(Dictionary<string, object> parameters)
|
||||
{
|
||||
base.SetParameters(parameters);
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(ModelName);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
ModelName = br.ReadString();
|
||||
}
|
||||
|
||||
public OriginElement Origin { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
[IgnoreOperation]
|
||||
public class Color128BinaryOperation:ColorAIOperation
|
||||
{
|
||||
public Color128BinaryOperation() : base("prob128binary")
|
||||
{
|
||||
IsRaw = true;
|
||||
}
|
||||
|
||||
|
||||
public override string FilterClasses { get; set; } = "1";
|
||||
public override FilePath ModelFilePath { get; set; } = new FilePath() { Format = "Model file(*.h5)|*.h5" };
|
||||
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(FilterClasses);
|
||||
bw.Write(ModelFilePath.Path);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
FilterClasses = br.ReadString();
|
||||
ModelFilePath.Path = br.ReadString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using OpenCvSharp;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
[IgnoreOperation]
|
||||
public class Color128HalfOperation:BaseOperation
|
||||
{
|
||||
public Color128HalfOperation()
|
||||
{
|
||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", "segment128half"));
|
||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||
_imageWidth = size[0];
|
||||
_imageHeight = size[1];
|
||||
CanHaveProcessingError = false;
|
||||
}
|
||||
|
||||
public string FilterClasses { get; set; } = "0";
|
||||
public FilePath ModelFilePath { get; set; } = new FilePath() { Format = "Model file(*.h5)|*.h5" };
|
||||
|
||||
|
||||
private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
private readonly int _imageWidth;
|
||||
private readonly int _imageHeight;
|
||||
|
||||
private bool _initialized = false;
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
if(!CheckImageExists(context))return;
|
||||
if(!CheckColorful(context))return;
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", "segment128half"));
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return;
|
||||
}
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
var currentImage = context.ActiveImage;
|
||||
|
||||
var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var resized = rightColor.Resize(new Size(_imageHeight, _imageWidth));
|
||||
int dataSize = resized.Rows * resized.Cols * resized.ElemSize();
|
||||
var byteArray = new byte[dataSize];
|
||||
Marshal.Copy(resized.Data, byteArray, 0, dataSize);
|
||||
|
||||
var allowed = FilterClasses.Split(',',StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray();
|
||||
|
||||
var allowedFlags = new byte[128];
|
||||
foreach (var i in allowed)
|
||||
{
|
||||
allowedFlags[i] = 1;
|
||||
}
|
||||
|
||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("predict", byteArray));
|
||||
|
||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
||||
|
||||
var resultColor = result.Select(x =>
|
||||
allowedFlags[x]==1
|
||||
? (byte)255
|
||||
: (byte)0)
|
||||
.ToArray();
|
||||
|
||||
// black and white mask
|
||||
var mask = new Mat(_imageHeight, _imageWidth, MatType.CV_8UC1, resultColor);
|
||||
|
||||
var maskResized = mask.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));
|
||||
|
||||
context.ActiveImage = new HawkeyeImage() {ImageData = maskResized};
|
||||
|
||||
|
||||
Result = true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void SetParameters(Dictionary<string, object> parameters)
|
||||
{
|
||||
base.SetParameters(parameters);
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(FilterClasses);
|
||||
bw.Write(ModelFilePath.Path);
|
||||
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
FilterClasses = br.ReadString();
|
||||
ModelFilePath.Path = br.ReadString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
[IgnoreOperation]
|
||||
public class Color128SimpleOperation: ColorAIOperation
|
||||
{
|
||||
public Color128SimpleOperation():base("bg128simple")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override string FilterClasses { get; set; } = "1";
|
||||
public override FilePath ModelFilePath { get; set; } = new FilePath(){Format = "Model file(*.h5)|*.h5"};
|
||||
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(FilterClasses);
|
||||
bw.Write(ModelFilePath.Path);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
FilterClasses = br.ReadString();
|
||||
ModelFilePath.Path = br.ReadString();
|
||||
}
|
||||
}
|
||||
114
Hawkeye.VisionBuilder.Workflow/Operations/AI/ColorAIOperation.cs
Normal file
114
Hawkeye.VisionBuilder.Workflow/Operations/AI/ColorAIOperation.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[IgnoreOperation]
|
||||
public abstract class ColorAIOperation:BaseOperation
|
||||
{
|
||||
private readonly string _modelName;
|
||||
|
||||
public ColorAIOperation(string model_name)
|
||||
{
|
||||
_modelName = model_name;
|
||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
||||
CanHaveProcessingError = false;
|
||||
}
|
||||
|
||||
private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
private int _imageWidth;
|
||||
private int _imageHeight;
|
||||
|
||||
private bool _initialized = false;
|
||||
private FilePath _modelFilePath;
|
||||
|
||||
|
||||
public virtual string FilterClasses { get; set; }
|
||||
|
||||
public virtual FilePath ModelFilePath
|
||||
{
|
||||
get => _modelFilePath;
|
||||
set
|
||||
{
|
||||
_modelFilePath = value;
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRaw { get; set; }
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
if (!CheckImageExists(context)) return;
|
||||
if (!CheckColorful(context)) return;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return;
|
||||
}
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
||||
|
||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||
_imageWidth = size[1];
|
||||
_imageHeight = size[2];
|
||||
|
||||
|
||||
var currentImage = context.ActiveImage;
|
||||
|
||||
var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var resized = rightColor.Resize(new Size(_imageHeight, _imageWidth));
|
||||
int dataSize = resized.Rows * resized.Cols * resized.ElemSize();
|
||||
var byteArray = new byte[dataSize];
|
||||
Marshal.Copy(resized.Data, byteArray, 0, dataSize);
|
||||
|
||||
var allowed = FilterClasses.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray();
|
||||
|
||||
var allowedFlags = new byte[128];
|
||||
foreach (var i in allowed)
|
||||
{
|
||||
allowedFlags[i] = 1;
|
||||
}
|
||||
|
||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall(IsRaw?"predict_raw": "predict", byteArray));
|
||||
|
||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
||||
|
||||
byte[] resultColor;
|
||||
if (IsRaw)
|
||||
{
|
||||
resultColor = result.Chunk(25).Select(x =>
|
||||
{
|
||||
return x.Select((x, i) => allowedFlags[i] == 1 ? (byte) (x) : (byte) 0).Max();
|
||||
}).ToArray();
|
||||
//resultColor = result.Select(x => (byte)(x)).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
resultColor = result.Select(x => allowedFlags[x]==1? (byte)(x * 255) : (byte)0).ToArray();
|
||||
}
|
||||
|
||||
|
||||
// black and white mask
|
||||
var mask = new Mat(_imageHeight, _imageWidth, MatType.CV_8UC1, resultColor);
|
||||
|
||||
var maskResized = mask.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));
|
||||
|
||||
context.ActiveImage = new HawkeyeImage() { ImageData = maskResized };
|
||||
|
||||
|
||||
Result = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
public class ColorModelOperation : ColorAIOperation
|
||||
{
|
||||
public ColorModelOperation() : base("office2")
|
||||
{
|
||||
IsRaw = true;
|
||||
}
|
||||
|
||||
|
||||
public override string FilterClasses { get; set; } = "1";
|
||||
public override FilePath ModelFilePath { get; set; } = new FilePath() { Format = "Model file(*.h5)|*.h5" };
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(FilterClasses);
|
||||
// get current directory
|
||||
var dir = Directory.GetCurrentDirectory();
|
||||
// get relative path
|
||||
var relPath = Path.GetRelativePath(dir, ModelFilePath.Path);
|
||||
|
||||
|
||||
bw.Write(relPath);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
FilterClasses = br.ReadString();
|
||||
ModelFilePath.Path = br.ReadString();
|
||||
// get current directory
|
||||
var dir = Directory.GetCurrentDirectory();
|
||||
// get absolute path
|
||||
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
|
||||
ModelFilePath.Path = absPath;
|
||||
}
|
||||
}
|
||||
159
Hawkeye.VisionBuilder.Workflow/Operations/AI/ModelAIOperation.cs
Normal file
159
Hawkeye.VisionBuilder.Workflow/Operations/AI/ModelAIOperation.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
public class ModelAIOperation: BaseOperation
|
||||
{
|
||||
|
||||
|
||||
private readonly string _modelName;
|
||||
|
||||
public ModelAIOperation()
|
||||
{
|
||||
|
||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
||||
CanHaveProcessingError = false;
|
||||
}
|
||||
|
||||
private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
private int _imageWidth;
|
||||
private int _imageHeight;
|
||||
|
||||
|
||||
|
||||
public string FilterClasses { get; set; } = "1";
|
||||
|
||||
private bool _initialized = false;
|
||||
private FilePath _modelFilePath=new FilePath();
|
||||
|
||||
|
||||
public FilePath ModelFilePath
|
||||
{
|
||||
get => _modelFilePath;
|
||||
set
|
||||
{
|
||||
_modelFilePath = value;
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCategorical { get; set; }
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
if (!CheckImageExists(context)) return;
|
||||
if (!CheckColorful(context)) return;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath?.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// is relative path
|
||||
if (ModelFilePath.Path.StartsWith("."))
|
||||
{
|
||||
var dir = Directory.GetCurrentDirectory();
|
||||
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
|
||||
ModelFilePath.Path = absPath;
|
||||
}
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
||||
|
||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||
_imageWidth = size[1];
|
||||
_imageHeight = size[2];
|
||||
|
||||
var outputSize = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_output_size"));
|
||||
var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray();
|
||||
|
||||
int classes;
|
||||
if (outputSizeArray.Length<3)
|
||||
{
|
||||
classes=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
classes = outputSizeArray[2];
|
||||
}
|
||||
|
||||
|
||||
|
||||
var currentImage = context.ActiveImage;
|
||||
|
||||
var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var resized = rightColor.Resize(new Size(_imageHeight, _imageWidth));
|
||||
int dataSize = resized.Rows * resized.Cols * resized.ElemSize();
|
||||
var byteArray = new byte[dataSize];
|
||||
Marshal.Copy(resized.Data, byteArray, 0, dataSize);
|
||||
|
||||
var allowed = FilterClasses.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray();
|
||||
|
||||
var allowedFlags = new byte[128];
|
||||
foreach (var i in allowed)
|
||||
{
|
||||
allowedFlags[i] = 1;
|
||||
}
|
||||
|
||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall(IsCategorical ? "predict_raw" : "predict", byteArray));
|
||||
|
||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
||||
// todo: copy and name RawModelAIOperation
|
||||
byte[] resultColor;
|
||||
if (IsCategorical)
|
||||
{
|
||||
resultColor = result.Chunk(classes).Select(x =>
|
||||
{
|
||||
return x.Select((x, i) => allowedFlags[i] == 1 ? (byte)(x) : (byte)0).Max();
|
||||
}).ToArray();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
resultColor = result.Select(x => allowedFlags[x] == 1 ? (byte)(x * 255) : (byte)0).ToArray();
|
||||
}
|
||||
|
||||
|
||||
// black and white mask
|
||||
var mask = new Mat(outputSizeArray[0], outputSizeArray[1], MatType.CV_8UC1, resultColor);
|
||||
|
||||
var maskResized = mask.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));
|
||||
|
||||
context.ActiveImage = new HawkeyeImage() { ImageData = maskResized };
|
||||
|
||||
|
||||
Result = true;
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(_modelFilePath.Path);
|
||||
bw.Write(_modelFilePath.Format);
|
||||
bw.Write(FilterClasses);
|
||||
bw.Write(IsCategorical);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
_modelFilePath.Path = br.ReadString();
|
||||
_modelFilePath.Format = br.ReadString();
|
||||
FilterClasses = br.ReadString();
|
||||
IsCategorical = br.ReadBoolean();
|
||||
}
|
||||
}
|
||||
148
Hawkeye.VisionBuilder.Workflow/Operations/AI/MultichannelAI.cs
Normal file
148
Hawkeye.VisionBuilder.Workflow/Operations/AI/MultichannelAI.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
public class MultichannelAI: BaseOperation
|
||||
{
|
||||
|
||||
private readonly string _modelName;
|
||||
|
||||
public MultichannelAI()
|
||||
{
|
||||
|
||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
||||
CanHaveProcessingError = false;
|
||||
}
|
||||
|
||||
private readonly CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
private int _imageWidth;
|
||||
private int _imageHeight;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private bool _initialized = false;
|
||||
private FilePath _modelFilePath = new FilePath();
|
||||
|
||||
|
||||
public FilePath ModelFilePath
|
||||
{
|
||||
get => _modelFilePath;
|
||||
set
|
||||
{
|
||||
_modelFilePath = value;
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected unsafe override void InterpretInternal(Context context)
|
||||
{
|
||||
if (!CheckImageExists(context)) return;
|
||||
if (!CheckColorful(context)) return;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath?.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return;
|
||||
}
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
||||
|
||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||
_imageWidth = size[1];
|
||||
_imageHeight = size[2];
|
||||
|
||||
var outputSize = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_output_size"));
|
||||
var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray();
|
||||
|
||||
int classes;
|
||||
if (outputSizeArray.Length < 3)
|
||||
{
|
||||
classes = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
classes = outputSizeArray[2];
|
||||
}
|
||||
|
||||
|
||||
|
||||
var currentImage = context.ActiveImage;
|
||||
|
||||
var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var resized = rightColor.Resize(new Size(_imageHeight, _imageWidth));
|
||||
int dataSize = resized.Rows * resized.Cols * resized.ElemSize();
|
||||
var byteArray = new byte[dataSize];
|
||||
Marshal.Copy(resized.Data, byteArray, 0, dataSize);
|
||||
|
||||
Mat[] channels = new Mat[classes];
|
||||
for (int i = 0; i < classes; i++)
|
||||
{
|
||||
channels[i] = new Mat(_imageHeight, _imageWidth, MatType.CV_8UC1);
|
||||
}
|
||||
|
||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("predict_raw", byteArray));
|
||||
|
||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
||||
|
||||
var chunked = result.Chunk(classes).ToArray();
|
||||
|
||||
int pixel=0;
|
||||
foreach (var chunk in chunked)
|
||||
{
|
||||
for (int i = 0; i < classes; i++)
|
||||
{
|
||||
var data = (byte*)channels[i].Data.ToPointer();
|
||||
var coordX = (int)(pixel / _imageWidth);
|
||||
var coordY = pixel % _imageWidth;
|
||||
data[coordX * _imageWidth + coordY] = chunk[i];
|
||||
}
|
||||
pixel++;
|
||||
}
|
||||
|
||||
//Cv2.ImShow("0", channels[0]);
|
||||
//Cv2.ImShow("1", channels[1]);
|
||||
|
||||
using var res = new Mat();
|
||||
Cv2.Merge(channels,res);
|
||||
|
||||
var resResized = res.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));
|
||||
context.ActiveImage = new HawkeyeImage() {ImageData = resResized };
|
||||
Result = true;
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(_modelFilePath.Path);
|
||||
bw.Write(_modelFilePath.Format);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
_modelFilePath.Path = br.ReadString();
|
||||
_modelFilePath.Format = br.ReadString();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using OpenCvSharp;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using Hawkeye.VisionBuilder.Workflow.DataTransfer;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
public class RawModelAIOperation: BaseOperation
|
||||
{
|
||||
|
||||
|
||||
private string _modelName;
|
||||
|
||||
public RawModelAIOperation()
|
||||
{
|
||||
|
||||
_cSharpDataTransferMQRPC = PythonModelProxyRPC.GetInterface();
|
||||
CanHaveProcessingError = false;
|
||||
}
|
||||
|
||||
private CSharpDataTransferMQRPC _cSharpDataTransferMQRPC;
|
||||
private int _imageWidth;
|
||||
private int _imageHeight;
|
||||
|
||||
|
||||
|
||||
public string FilterClasses { get; set; } = "1";
|
||||
|
||||
private bool _initialized = false;
|
||||
private FilePath _modelFilePath=new FilePath();
|
||||
|
||||
|
||||
public FilePath ModelFilePath
|
||||
{
|
||||
get => _modelFilePath;
|
||||
set
|
||||
{
|
||||
_modelFilePath = value;
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCategorical { get; set; }
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
if (!CheckImageExists(context)) return;
|
||||
if (!CheckColorful(context)) return;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath?.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
_modelName = Path.GetFileNameWithoutExtension(ModelFilePath.Path);
|
||||
|
||||
// is relative path
|
||||
if (ModelFilePath.Path.StartsWith("."))
|
||||
{
|
||||
var dir = Directory.GetCurrentDirectory();
|
||||
var absPath = Path.GetFullPath(Path.Combine(dir, ModelFilePath.Path));
|
||||
ModelFilePath.Path = absPath;
|
||||
}
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("load_model", ModelFilePath.Path, _modelName));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_cSharpDataTransferMQRPC.TransferData<object>(new MethodCall("activate_model", _modelName));
|
||||
|
||||
var sizeEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_accept_size"));
|
||||
var size = sizeEncoded.Select(Convert.ToInt32).ToArray();
|
||||
_imageWidth = size[1];
|
||||
_imageHeight = size[2];
|
||||
|
||||
var outputSize = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("get_output_size"));
|
||||
var outputSizeArray = outputSize.Select(Convert.ToInt32).ToArray();
|
||||
|
||||
int classes;
|
||||
if (outputSizeArray.Length<3)
|
||||
{
|
||||
classes=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
classes = outputSizeArray[2];
|
||||
}
|
||||
|
||||
|
||||
|
||||
var currentImage = context.ActiveImage;
|
||||
|
||||
var rightColor = currentImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
var resized = rightColor.Resize(new Size(_imageHeight, _imageWidth));
|
||||
int dataSize = resized.Rows * resized.Cols * resized.ElemSize();
|
||||
var byteArray = new byte[dataSize];
|
||||
Marshal.Copy(resized.Data, byteArray, 0, dataSize);
|
||||
|
||||
var allowed = FilterClasses.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToInt32(x)).ToArray();
|
||||
|
||||
var allowedFlags = new byte[128];
|
||||
foreach (var i in allowed)
|
||||
{
|
||||
allowedFlags[i] = 1;
|
||||
}
|
||||
|
||||
var resultEncoded = _cSharpDataTransferMQRPC.TransferData<object[]>(new MethodCall("predict_raw", byteArray));
|
||||
|
||||
var result = resultEncoded.Select(Convert.ToByte).ToArray();
|
||||
|
||||
byte[] resultColor;
|
||||
resultColor = result.Select(x => (byte)(x) ).ToArray();
|
||||
|
||||
|
||||
// black and white mask
|
||||
var mask = new Mat(outputSizeArray[0], outputSizeArray[1], MatType.CV_8UC1, resultColor);
|
||||
|
||||
var maskResized = mask.Resize(new Size(currentImage.ImageData.Width, currentImage.ImageData.Height));
|
||||
|
||||
context.ActiveImage = new HawkeyeImage() { ImageData = maskResized };
|
||||
|
||||
|
||||
Result = true;
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(_modelFilePath.Path);
|
||||
bw.Write(_modelFilePath.Format);
|
||||
bw.Write(FilterClasses);
|
||||
bw.Write(IsCategorical);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
_modelFilePath.Path = br.ReadString();
|
||||
_modelFilePath.Format = br.ReadString();
|
||||
FilterClasses = br.ReadString();
|
||||
IsCategorical = br.ReadBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Diagnostics;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
using static IronPython.Runtime.Profiler;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Compunet.YoloV8;
|
||||
using Compunet.YoloV8.Data;
|
||||
using OpenCvSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
[Category("AI")]
|
||||
public class YoloDetectionOperation:BaseOperation
|
||||
{
|
||||
|
||||
private bool _initialized = false;
|
||||
private FilePath _modelFilePath = new FilePath(){ Format = "Onnx format(*.onnx)|*.onnx" };
|
||||
|
||||
|
||||
public FilePath ModelFilePath
|
||||
{
|
||||
get => _modelFilePath;
|
||||
set
|
||||
{
|
||||
_modelFilePath = value;
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EnsureInitialized()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
if (!File.Exists(ModelFilePath?.Path))
|
||||
{
|
||||
this.SetError("Model file not found");
|
||||
return false;
|
||||
}
|
||||
_predictor = YoloV8Predictor.Create(ModelFilePath?.Path);
|
||||
|
||||
_predictor.Configuration.SuppressParallelInference=true;
|
||||
_initialized = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
byte[] _buffer=new byte[0];
|
||||
private YoloV8Predictor _predictor;
|
||||
|
||||
|
||||
void EnsureBuffer(int size)
|
||||
{
|
||||
if (_buffer.Length != size)
|
||||
{
|
||||
_buffer = new byte[size];
|
||||
}
|
||||
}
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
CheckColorful(context);
|
||||
try
|
||||
{
|
||||
if (!EnsureInitialized()) return;
|
||||
var swImagePreparation = Stopwatch.StartNew();
|
||||
|
||||
var image = context.ActiveImage.ImageData.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
EnsureBuffer(image.Cols * image.Rows * image.Channels());
|
||||
Marshal.Copy(image.Data, _buffer, 0, _buffer.Length);
|
||||
Image<Rgb24> img = SixLabors.ImageSharp.Image.LoadPixelData<Rgb24>(_buffer, image.Width, image.Height);
|
||||
// convert to rgb24
|
||||
|
||||
|
||||
swImagePreparation.Stop();
|
||||
|
||||
var swPrediction = Stopwatch.StartNew();
|
||||
|
||||
|
||||
|
||||
var result = _predictor.Detect(img);
|
||||
|
||||
|
||||
|
||||
swPrediction.Stop();
|
||||
|
||||
|
||||
var swPostProcessing = Stopwatch.StartNew();
|
||||
Mat res = new Mat(image.Size(), MatType.CV_8UC1, new Scalar(0));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Dictionary<int, string> classes = new Dictionary<int, string>();
|
||||
|
||||
|
||||
|
||||
foreach (BoundingBox box in result.Boxes.OrderBy(x=>x.Class.Id).ToList())
|
||||
{
|
||||
if (box.Confidence<0.7)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (box.Class.Id + 1 == 10)
|
||||
{
|
||||
|
||||
}
|
||||
classes[box.Class.Id + 1] = box.Class.Name;
|
||||
var rect = box.Bounds;
|
||||
res.Rectangle(new OpenCvSharp.Rect(rect.Left, rect.Top, rect.Width, rect.Height),
|
||||
new Scalar(box.Class.Id + 1), -1);
|
||||
}
|
||||
swPostProcessing.Stop();
|
||||
|
||||
|
||||
foreach (var c in classes)
|
||||
{
|
||||
sb.AppendLine($"{c.Key}={c.Value};");
|
||||
}
|
||||
|
||||
Status = sb.ToString();
|
||||
|
||||
context.ActiveImage = new HawkeyeImage() {ImageData = res};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(ModelFilePath.Path);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
ModelFilePath.Path = br.ReadString();
|
||||
}
|
||||
}
|
||||
115
Hawkeye.VisionBuilder.Workflow/Operations/AI/YoloPickDetected.cs
Normal file
115
Hawkeye.VisionBuilder.Workflow/Operations/AI/YoloPickDetected.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Poly;
|
||||
using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
using OpenCvSharp;
|
||||
using static Hawkeye.VisionBuilder.Workflow.Operations.FindManyBlobsOperation;
|
||||
using Point = OpenCvSharp.Point;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.AI;
|
||||
|
||||
[Category("AI")]
|
||||
public class YoloPickDetected:BaseOperation
|
||||
{
|
||||
public string SlotName { get; set; } = "Image";
|
||||
public int TypeId { get; set; }
|
||||
|
||||
public int MinArea { get; set; } = 0;
|
||||
public int MaxArea { get; set; } = 99999;
|
||||
public int MinWidth { get; set; } = 0;
|
||||
public int MaxWidth { get; set; } = 99999;
|
||||
public int MinHeight { get; set; } = 0;
|
||||
public int MaxHeight { get; set; } = 99999;
|
||||
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
Status = "No blobs found";
|
||||
var image=context.Memory[SlotName];
|
||||
var threshold= image.ImageData.InRange(TypeId, TypeId);
|
||||
var contours = threshold
|
||||
.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
//case: no blobs found
|
||||
if (contours.Length == 0)
|
||||
{
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var validContours = new List<Point[]>();
|
||||
|
||||
foreach (var contour in contours)
|
||||
{
|
||||
var boundingRect = Cv2.BoundingRect(contour);
|
||||
var area = Cv2.ContourArea(contour);
|
||||
if (boundingRect.Width >= MinWidth && boundingRect.Width <= MaxWidth &&
|
||||
boundingRect.Height >= MinHeight && boundingRect.Height <= MaxHeight &&
|
||||
area >= MinArea && area <= MaxArea
|
||||
)
|
||||
{
|
||||
validContours.Add(contour);
|
||||
}
|
||||
}
|
||||
|
||||
int badCount = 0;
|
||||
foreach (var contourMatch in validContours)
|
||||
{
|
||||
PolyElement poly = new PolyElement() { IsGood = false };
|
||||
|
||||
poly.Points = contourMatch.Select(x => new Vector2(x.X, x.Y)).ToArray();
|
||||
|
||||
context.GraphicsElements.Add(poly);
|
||||
badCount++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (badCount > 0)
|
||||
{
|
||||
// find smallest contour size
|
||||
var smallest = validContours.OrderBy(x => Cv2.ContourArea(x)).First();
|
||||
var smallestBoundingRect = Cv2.BoundingRect(smallest);
|
||||
var biggest = validContours.OrderBy(x => Cv2.ContourArea(x)).Last();
|
||||
var biggestBoundingRect = Cv2.BoundingRect(biggest);
|
||||
var smallestArea = Cv2.ContourArea(smallest);
|
||||
var biggestArea = Cv2.ContourArea(biggest);
|
||||
Status = $"Found: {badCount} Min area:{smallestArea} Max area:{biggestArea}";
|
||||
|
||||
|
||||
|
||||
Result = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Result = true;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void Save(BinaryWriter bw)
|
||||
{
|
||||
base.Save(bw);
|
||||
bw.Write(SlotName);
|
||||
bw.Write(TypeId);
|
||||
bw.Write(MinWidth);
|
||||
bw.Write(MaxWidth);
|
||||
bw.Write(MinHeight);
|
||||
bw.Write(MaxHeight);
|
||||
bw.Write(MinArea);
|
||||
bw.Write(MaxArea);
|
||||
}
|
||||
|
||||
public override void Load(BinaryReader br)
|
||||
{
|
||||
base.Load(br);
|
||||
SlotName = br.ReadString();
|
||||
TypeId = br.ReadInt32();
|
||||
MinWidth = br.ReadInt32();
|
||||
MaxWidth = br.ReadInt32();
|
||||
MinHeight = br.ReadInt32();
|
||||
MaxHeight = br.ReadInt32();
|
||||
MinArea = br.ReadInt32();
|
||||
MaxArea = br.ReadInt32();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations;
|
||||
|
||||
public class ActivateImageOperation:BaseOperation
|
||||
{
|
||||
public ScriptValue ImageSource { get; set; } = new ScriptValue();
|
||||
|
||||
protected override void InterpretInternal(Context context)
|
||||
{
|
||||
context.ActiveImage = GetScriptValue<HawkeyeImage>(ImageSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class Category:Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public Category(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class,Inherited = false)]
|
||||
public class IgnoreOperationAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class NotForToolAttribute:Attribute
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class RestrictReferenceAttribute:Attribute
|
||||
{
|
||||
public RestrictReferenceAttribute(Type type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public Type Type { get; set; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user