Initial implementation
@@ -0,0 +1,521 @@
|
|||||||
|
# Do not check in tesseract training data
|
||||||
|
*.traineddata
|
||||||
|
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/visualstudio,intellij
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,intellij
|
||||||
|
|
||||||
|
### Intellij ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Intellij Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# SonarQube Plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Markdown Navigator plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
# Cache file creation bug
|
||||||
|
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
|
# CodeStream plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
|
.idea/codestream.xml
|
||||||
|
|
||||||
|
# Azure Toolkit for IntelliJ plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||||
|
.idea/**/azureSettings.xml
|
||||||
|
|
||||||
|
### VisualStudio ###
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# 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 Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
### VisualStudio Patch ###
|
||||||
|
# Additional files built by Visual Studio
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,intellij
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Processors\Ocr.Processors.csproj" />
|
||||||
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="img\command-processing_screentypes_controlgroup_005.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\editor_startpage_project-exist_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\editor_windows_position_006.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\historian_assistent_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_MetadataEditor_variables_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_REPORTS_EfficencyClass_009.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_ZAMS_3rd-connector_014.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_ZAMS_filter-alarmgroup_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Common;
|
||||||
|
using Common.Extensions;
|
||||||
|
using ImageMagick;
|
||||||
|
|
||||||
|
var scanner = new ScreenshotScanner();
|
||||||
|
|
||||||
|
Console.WriteLine($"# Scanning: {string.Join(',', args)}...");
|
||||||
|
scanner.Process(GetImages(args));
|
||||||
|
|
||||||
|
Console.WriteLine($"# Results ({scanner.Lookup.Keys.Count}):");
|
||||||
|
Console.WriteLine(string.Join(' ', scanner.Lookup.Keys));
|
||||||
|
|
||||||
|
static IEnumerable<MagickImage> GetImages(IEnumerable<string> paths) => paths
|
||||||
|
.SelectMany(p => p.ExpandPath())
|
||||||
|
.Select(p => new MagickImage(p))
|
||||||
|
.ToArray();
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"WSL": {
|
||||||
|
"commandName": "WSL2",
|
||||||
|
"distributionName": ""
|
||||||
|
},
|
||||||
|
"Test all img": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "\"img/*\""
|
||||||
|
},
|
||||||
|
"Test single img": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "\"img/historian_assistent_001.png\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Lookup\Lookup.Memory\Lookup.Memory.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Processors\Ocr.Processors.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Ocr\Ocr.Tesseract\Ocr.Tesseract.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Process\Process.Interface\Process.Interface.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="tessdata\deu.traineddata">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="tessdata\eng.traineddata">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="tessdata\osd.traineddata">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Common.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for the string object type
|
||||||
|
/// </summary>
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
private static readonly Regex patternRegex = new Regex(@"^\*$");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this string contains the specified string. Not case sensitive.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source"> The source.</param>
|
||||||
|
/// <param name="contained">The contained.</param>
|
||||||
|
public static bool ContainsIgnoreCase(this string source, string contained)
|
||||||
|
{
|
||||||
|
return source?.IndexOf(contained, StringComparison.InvariantCultureIgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expands a path containing a wildcard pattern
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="self"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ICollection<string> ExpandPath(this string self)
|
||||||
|
{
|
||||||
|
string pattern = Path.GetFileName(self);
|
||||||
|
if (patternRegex.IsMatch(pattern))
|
||||||
|
{
|
||||||
|
return Directory.GetFiles(
|
||||||
|
self.Substring(0, self.Length - pattern.Length),
|
||||||
|
pattern,
|
||||||
|
SearchOption.TopDirectoryOnly
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[] { self };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using Lookup.Memory;
|
||||||
|
using Ocr.Tesseract;
|
||||||
|
using Ocr.Tesseract.Configuration;
|
||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Ocr.Tesseract.Screenshots;
|
||||||
|
using Ocr.Tesseract.Screenshots.Configuration;
|
||||||
|
using Process.Abstract.Configuration;
|
||||||
|
using Process.Interface;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Scanner class, scanning <see cref="MagickImage"/>s for <see cref="Word"/>s
|
||||||
|
/// via optical character recognition. Optimized for digital Screenshots.
|
||||||
|
/// </summary>
|
||||||
|
public class ScreenshotScanner
|
||||||
|
{
|
||||||
|
private readonly IProcessor<MagickImage, ScanResult> _processor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Regex"/> expression for extracting whole words from scan results
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Regex wordRegex = new(
|
||||||
|
@"[\w'\-]{2,}",
|
||||||
|
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data storage
|
||||||
|
/// </summary>
|
||||||
|
public Lookup.Interface.ILookup<Word, MagickImage> Lookup { get; } =
|
||||||
|
new MemoryLookup<Word, MagickImage>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration of the <see cref="ImageProcessor"/>
|
||||||
|
/// </summary>
|
||||||
|
public ScreenshotProcessorConfiguration ImageProcessorConfiguration { get; set; } = new();
|
||||||
|
|
||||||
|
public ITesseractConfiguration TesseractConfiguration { get; set; } =
|
||||||
|
new TesseractScreenshotConfiguration();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public ScreenshotScanner()
|
||||||
|
{
|
||||||
|
_processor = MakeProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process the provided <paramref name="images"/> and add the results to
|
||||||
|
/// the <see cref="Lookup"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="images">The <see cref="MagickImage"/>s to process</param>
|
||||||
|
public void Process(IEnumerable<MagickImage> images)
|
||||||
|
{
|
||||||
|
foreach (var kv in _processor.Process(images))
|
||||||
|
{
|
||||||
|
Lookup.Add(kv.Word, kv.Image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IProcessor<MagickImage, ScanResult> MakeProcessor()
|
||||||
|
{
|
||||||
|
return new ProcessorChainConfiguration<MagickImage, ScanResult>()
|
||||||
|
.Use(new ScreenshotProcessor(ImageProcessorConfiguration)) // Preprocess input data
|
||||||
|
.Use(new ProcessingEvent<MagickImage>(OnProcessing)) // Scan
|
||||||
|
.Use(new TesseractProcessor(TesseractConfiguration)) // Scan
|
||||||
|
.Use(new ProcessingEvent<ScanResult>(OnProcessed)) // Scan
|
||||||
|
.Use(new ConfidenceFilter(50)) // Process output data
|
||||||
|
.Use(new DuplicateFilter())
|
||||||
|
.Use(new ToLowerProcessor())
|
||||||
|
.Complete(new RegexFilter(wordRegex));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.33424.131
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Process", "Process", "{E55C5AE2-39DF-4AC6-B7AC-3100B0ACFD77}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Interface", "..\Process\Process.Interface\Process.Interface.csproj", "{249ECD4B-B160-4DFF-B4A5-888D702182B1}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Abstract", "..\Process\Process.Abstract\Process.Abstract.csproj", "{369DB407-CF9A-4DC4-83BF-391894B2D25E}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lookup", "Lookup", "{25AA2201-7BB4-4D00-A979-74FA04EB225B}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Memory", "..\Lookup\Lookup.Memory\Lookup.Memory.csproj", "{061EDDA9-0A15-452C-8AEA-C15B5532AD90}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Interface", "..\Lookup\Lookup.Interface\Lookup.Interface.csproj", "{D6281061-E3CB-4F32-ABBB-4B41CE6189CE}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.IO", "..\Lookup\Lookup.File\Lookup.IO.csproj", "{3944B59F-CB56-4A43-A2CE-7CE9C00BBB6C}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Database", "..\Lookup\Lookup.Database\Lookup.Database.csproj", "{EF39CBC7-E961-4CE8-ABC0-4FC1F3F8C91B}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Abstract", "..\Lookup\Lookup.Abstract\Lookup.Abstract.csproj", "{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CLI", "CLI\CLI.csproj", "{2856493F-EF1C-42A1-8EE5-6C0387D08F95}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GUI", "GUI\GUI.csproj", "{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "Common\Common.csproj", "{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Ocr", "Ocr", "{E55F78E4-09F1-4D79-A9A2-460562C96DAB}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract", "..\Ocr\Ocr.Tesseract\Ocr.Tesseract.csproj", "{D9B70035-0159-4D75-8ED6-2461F060F683}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract.Screenshots", "..\Ocr\Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj", "{251F9AC9-3765-498C-83FD-DB3539A19CB3}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{249ECD4B-B160-4DFF-B4A5-888D702182B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{249ECD4B-B160-4DFF-B4A5-888D702182B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{249ECD4B-B160-4DFF-B4A5-888D702182B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{249ECD4B-B160-4DFF-B4A5-888D702182B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{369DB407-CF9A-4DC4-83BF-391894B2D25E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{369DB407-CF9A-4DC4-83BF-391894B2D25E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{369DB407-CF9A-4DC4-83BF-391894B2D25E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{369DB407-CF9A-4DC4-83BF-391894B2D25E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{061EDDA9-0A15-452C-8AEA-C15B5532AD90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{061EDDA9-0A15-452C-8AEA-C15B5532AD90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{061EDDA9-0A15-452C-8AEA-C15B5532AD90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{061EDDA9-0A15-452C-8AEA-C15B5532AD90}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D6281061-E3CB-4F32-ABBB-4B41CE6189CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D6281061-E3CB-4F32-ABBB-4B41CE6189CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D6281061-E3CB-4F32-ABBB-4B41CE6189CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D6281061-E3CB-4F32-ABBB-4B41CE6189CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3944B59F-CB56-4A43-A2CE-7CE9C00BBB6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3944B59F-CB56-4A43-A2CE-7CE9C00BBB6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3944B59F-CB56-4A43-A2CE-7CE9C00BBB6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3944B59F-CB56-4A43-A2CE-7CE9C00BBB6C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EF39CBC7-E961-4CE8-ABC0-4FC1F3F8C91B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EF39CBC7-E961-4CE8-ABC0-4FC1F3F8C91B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EF39CBC7-E961-4CE8-ABC0-4FC1F3F8C91B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EF39CBC7-E961-4CE8-ABC0-4FC1F3F8C91B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2856493F-EF1C-42A1-8EE5-6C0387D08F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2856493F-EF1C-42A1-8EE5-6C0387D08F95}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2856493F-EF1C-42A1-8EE5-6C0387D08F95}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2856493F-EF1C-42A1-8EE5-6C0387D08F95}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DA447F14-1B1D-4733-99F3-6EF8225DCBAB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A6C738AC-DCD7-4024-A92D-3FC3CDCD7229}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D9B70035-0159-4D75-8ED6-2461F060F683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9B70035-0159-4D75-8ED6-2461F060F683}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9B70035-0159-4D75-8ED6-2461F060F683}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D9B70035-0159-4D75-8ED6-2461F060F683}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{251F9AC9-3765-498C-83FD-DB3539A19CB3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{E55C5AE2-39DF-4AC6-B7AC-3100B0ACFD77} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
||||||
|
{249ECD4B-B160-4DFF-B4A5-888D702182B1} = {E55C5AE2-39DF-4AC6-B7AC-3100B0ACFD77}
|
||||||
|
{369DB407-CF9A-4DC4-83BF-391894B2D25E} = {E55C5AE2-39DF-4AC6-B7AC-3100B0ACFD77}
|
||||||
|
{25AA2201-7BB4-4D00-A979-74FA04EB225B} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
||||||
|
{061EDDA9-0A15-452C-8AEA-C15B5532AD90} = {25AA2201-7BB4-4D00-A979-74FA04EB225B}
|
||||||
|
{D6281061-E3CB-4F32-ABBB-4B41CE6189CE} = {25AA2201-7BB4-4D00-A979-74FA04EB225B}
|
||||||
|
{3944B59F-CB56-4A43-A2CE-7CE9C00BBB6C} = {25AA2201-7BB4-4D00-A979-74FA04EB225B}
|
||||||
|
{EF39CBC7-E961-4CE8-ABC0-4FC1F3F8C91B} = {25AA2201-7BB4-4D00-A979-74FA04EB225B}
|
||||||
|
{D14DA0B8-5EAE-4C77-992E-3527DC84CE6D} = {25AA2201-7BB4-4D00-A979-74FA04EB225B}
|
||||||
|
{E55F78E4-09F1-4D79-A9A2-460562C96DAB} = {CF65AA6A-2F25-4FEE-BDC1-AD96E1FFFA49}
|
||||||
|
{D9B70035-0159-4D75-8ED6-2461F060F683} = {E55F78E4-09F1-4D79-A9A2-460562C96DAB}
|
||||||
|
{251F9AC9-3765-498C-83FD-DB3539A19CB3} = {E55F78E4-09F1-4D79-A9A2-460562C96DAB}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {DFA659EE-FE78-4BD9-888B-78984354093E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<Application x:Class="GUI.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Application.Resources/>
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using GUI.ViewModels;
|
||||||
|
using GUI.Views;
|
||||||
|
using Serilog;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace GUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
var loggingCollection = new LoggingCollection(100);
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.WriteTo.Sink(loggingCollection)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
new LogView(loggingCollection).Show();
|
||||||
|
new ImageView().Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="GUI.Controls.ImageControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:converters="clr-namespace:GUI.Converters"
|
||||||
|
xmlns:controls="clr-namespace:GUI.Controls"
|
||||||
|
d:DataContext="{d:DesignInstance controls:ImageControl}"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:ImageConverter x:Key="Converter.CImage" />
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="ControlTemplate.ImageControl.Default" TargetType="controls:ImageControl">
|
||||||
|
<Border DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
|
||||||
|
Padding="4"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<Image RenderOptions.BitmapScalingMode="NearestNeighbor"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Source="{Binding Image, Converter={StaticResource Converter.CImage}}" />
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<Style x:Key="Style.ImageControl.Default" TargetType="controls:ImageControl">
|
||||||
|
<Setter Property="Template" Value="{StaticResource ControlTemplate.ImageControl.Default}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style BasedOn="{StaticResource Style.ImageControl.Default}" TargetType="controls:ImageControl" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid />
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace GUI.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for ImageControl.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ImageControl : UserControl
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="Image"/>
|
||||||
|
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
|
||||||
|
nameof(Image), typeof(MagickImage), typeof(ImageControl), new PropertyMetadata(default(MagickImage)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="MagickImage"/> displayed in this <see cref="ImageControl"/>
|
||||||
|
/// </summary>
|
||||||
|
public MagickImage Image
|
||||||
|
{
|
||||||
|
get => (MagickImage)GetValue(ImageProperty);
|
||||||
|
set => SetValue(ImageProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ImageName"/>
|
||||||
|
public static readonly DependencyProperty ImageNameProperty = DependencyProperty.Register(
|
||||||
|
nameof(ImageName), typeof(object), typeof(ImageControl),
|
||||||
|
new PropertyMetadata(default(object)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the loaded image
|
||||||
|
/// </summary>
|
||||||
|
public object ImageName
|
||||||
|
{
|
||||||
|
get => (object)GetValue(ImageNameProperty);
|
||||||
|
set => SetValue(ImageNameProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ImageControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace GUI.Converters
|
||||||
|
{
|
||||||
|
internal class ImageConverter : IValueConverter
|
||||||
|
{
|
||||||
|
#region Implementation of IValueConverter
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is not MagickImage image)
|
||||||
|
{
|
||||||
|
return Binding.DoNothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
|
||||||
|
// Save image to stream
|
||||||
|
image.Write(stream, MagickFormat.Png);
|
||||||
|
|
||||||
|
// Build Bitmap from stream
|
||||||
|
var imageSource = new BitmapImage();
|
||||||
|
imageSource.BeginInit();
|
||||||
|
imageSource.StreamSource = stream;
|
||||||
|
imageSource.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
imageSource.EndInit();
|
||||||
|
|
||||||
|
return imageSource;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error($"{e.Message}");
|
||||||
|
return Binding.DoNothing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="img\command-processing_screentypes_controlgroup_005.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\editor_startpage_project-exist_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\editor_windows_position_006.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\historian_assistent_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_MetadataEditor_variables_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_REPORTS_EfficencyClass_009.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_ZAMS_3rd-connector_014.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="img\zrs_ZAMS_filter-alarmgroup_001.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace GUI.Model;
|
||||||
|
|
||||||
|
public class Command : ICommand
|
||||||
|
{
|
||||||
|
public Action Action { get; set; }
|
||||||
|
|
||||||
|
public Command(Action action)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of ICommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool CanExecute(object? parameter)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute(object? parameter)
|
||||||
|
{
|
||||||
|
Action?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event EventHandler? CanExecuteChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
using Common;
|
||||||
|
using GUI.Model;
|
||||||
|
using ImageMagick;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Process.Interface;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace GUI.ViewModels;
|
||||||
|
|
||||||
|
internal class ImageViewModel : ScreenshotScanner, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public ImageViewModel()
|
||||||
|
{
|
||||||
|
// Scanner.ImageOperationSettings.PropertyChanged += (sender, args) => Task.Run(UpdateImage);
|
||||||
|
|
||||||
|
OpenFileCommand = new Command(OpenFile);
|
||||||
|
SaveEditedImageCommand = new Command(SaveEditedImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Scanner
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
foreach (var image in inputs)
|
||||||
|
{
|
||||||
|
Edited.Add(image);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
|
||||||
|
{
|
||||||
|
ScannedText = $"[{inputs.Count} words] " + string.Join(' ', inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public ImageViewModel(MagickImage image) : this()
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Clear()
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
ScannedText = string.Empty;
|
||||||
|
Words.Clear();
|
||||||
|
Lookup.Clear();
|
||||||
|
Edited.Clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenFile()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog()
|
||||||
|
{
|
||||||
|
InitialDirectory = Directory.GetCurrentDirectory()
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
Image = new MagickImage(dialog.FileName);
|
||||||
|
UpdateImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveEditedImage()
|
||||||
|
{
|
||||||
|
var basePath = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
|
||||||
|
for (var i = 0; i < Edited.Count; i++)
|
||||||
|
{
|
||||||
|
Edited[i].Write(Path.Combine(basePath, $"edited_{i}.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Information($"Saved image to '{basePath}'");
|
||||||
|
System.Diagnostics.Process.Start(
|
||||||
|
"explorer.exe",
|
||||||
|
Path.GetDirectoryName(basePath) ?? string.Empty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateConfidence()
|
||||||
|
{
|
||||||
|
Confidence = Lookup.Keys.Any()
|
||||||
|
? Lookup.Keys.Sum(key => key.Confidence) / Lookup.Keys.Count
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateImage()
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
IsIdle = false;
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
if (Image != null)
|
||||||
|
{
|
||||||
|
Process(new[] { Image });
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateWords();
|
||||||
|
UpdateConfidence();
|
||||||
|
|
||||||
|
IsIdle = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWords()
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
foreach (var word in Lookup.Keys)
|
||||||
|
{
|
||||||
|
Words.Add(word);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
private float _confidence;
|
||||||
|
private MagickImage? _image;
|
||||||
|
private bool _isIdle;
|
||||||
|
|
||||||
|
private string _scannedText = string.Empty;
|
||||||
|
|
||||||
|
public string ScannedText
|
||||||
|
{
|
||||||
|
get => _scannedText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _scannedText)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scannedText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsIdle
|
||||||
|
{
|
||||||
|
get => _isIdle;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _isIdle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isIdle = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Confidence
|
||||||
|
{
|
||||||
|
get => _confidence;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_confidence = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<MagickImage> Edited { get; } = new();
|
||||||
|
|
||||||
|
public MagickImage? Image
|
||||||
|
{
|
||||||
|
get => _image;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_image = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<Word> Words { get; } = new();
|
||||||
|
|
||||||
|
#endregion Properties
|
||||||
|
|
||||||
|
#region Commands
|
||||||
|
|
||||||
|
public ICommand OpenFileCommand { get; private set; }
|
||||||
|
public ICommand SaveEditedImageCommand { get; private set; }
|
||||||
|
|
||||||
|
#endregion Commands
|
||||||
|
|
||||||
|
#region INotifyPropertyChanged
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion INotifyPropertyChanged
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GUI.ViewModels;
|
||||||
|
|
||||||
|
public class LogMessage
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace GUI.ViewModels
|
||||||
|
{
|
||||||
|
public class LogViewModel
|
||||||
|
{
|
||||||
|
public LoggingCollection LoggingCollection { get; }
|
||||||
|
|
||||||
|
public LogViewModel(LoggingCollection loggingCollection)
|
||||||
|
{
|
||||||
|
LoggingCollection = loggingCollection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace GUI.ViewModels;
|
||||||
|
|
||||||
|
public class LoggingCollection : ILogEventSink
|
||||||
|
{
|
||||||
|
public int Capacity { get; }
|
||||||
|
|
||||||
|
public ObservableCollection<LogMessage> Items { get; }
|
||||||
|
|
||||||
|
public LoggingCollection(int capacity)
|
||||||
|
{
|
||||||
|
Capacity = capacity;
|
||||||
|
Items = new ObservableCollection<LogMessage>(new List<LogMessage>(capacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Trim(int offset = 0)
|
||||||
|
{
|
||||||
|
for (int i = Items.Count - Capacity - offset; i >= 0; i--)
|
||||||
|
{
|
||||||
|
Items.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of ILogEventSink
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Emit(LogEvent logEvent)
|
||||||
|
{
|
||||||
|
Trim(1);
|
||||||
|
|
||||||
|
Items.Add(new LogMessage
|
||||||
|
{
|
||||||
|
Timestamp = logEvent.Timestamp.DateTime,
|
||||||
|
Message = logEvent.RenderMessage()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="GUI.Views.ImageView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:GUI.Controls"
|
||||||
|
xmlns:viewModels="clr-namespace:GUI.ViewModels"
|
||||||
|
Title="OcrView"
|
||||||
|
Width="800"
|
||||||
|
Height="450"
|
||||||
|
d:DataContext="{d:DesignInstance viewModels:ImageViewModel}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Window.Resources>
|
||||||
|
<CollectionViewSource
|
||||||
|
x:Key="View.Words"
|
||||||
|
Source="{Binding Words}">
|
||||||
|
<CollectionViewSource.SortDescriptions>
|
||||||
|
<componentModel:SortDescription
|
||||||
|
Direction="Descending"
|
||||||
|
PropertyName="Confidence" />
|
||||||
|
</CollectionViewSource.SortDescriptions>
|
||||||
|
</CollectionViewSource>
|
||||||
|
</Window.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition
|
||||||
|
Height="*"
|
||||||
|
MinHeight="300" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Menu Grid.Row="0">
|
||||||
|
<MenuItem Header="File">
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding OpenFileCommand}"
|
||||||
|
Header="Open image..." />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding SaveEditedImageCommand}"
|
||||||
|
Header="Save edited image" />
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition
|
||||||
|
Height="*"
|
||||||
|
MinHeight="120" />
|
||||||
|
<RowDefinition Height="4" />
|
||||||
|
<RowDefinition
|
||||||
|
Height="2*"
|
||||||
|
MinHeight="120" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Grid.Row="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition
|
||||||
|
Width="*"
|
||||||
|
MinWidth="120" />
|
||||||
|
<ColumnDefinition Width="4" />
|
||||||
|
<ColumnDefinition
|
||||||
|
Width="2*"
|
||||||
|
MinWidth="120" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Column="0"
|
||||||
|
HorizontalScrollBarVisibility="Visible"
|
||||||
|
VerticalScrollBarVisibility="Disabled">
|
||||||
|
<controls:ImageControl Image="{Binding Image}" />
|
||||||
|
</ScrollViewer>
|
||||||
|
<GridSplitter
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
ResizeBehavior="PreviousAndNext"
|
||||||
|
ResizeDirection="Columns" />
|
||||||
|
<ListBox
|
||||||
|
Grid.Column="2"
|
||||||
|
ItemsSource="{Binding Edited}"
|
||||||
|
ScrollViewer.CanContentScroll="False"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Visible"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Disabled">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel
|
||||||
|
IsItemsHost="True"
|
||||||
|
Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border
|
||||||
|
BorderBrush="CornflowerBlue"
|
||||||
|
BorderThickness="2">
|
||||||
|
<controls:ImageControl
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Image="{Binding NotifyOnSourceUpdated=True}" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
<GridSplitter
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
ResizeBehavior="PreviousAndNext"
|
||||||
|
ResizeDirection="Rows" />
|
||||||
|
<Grid Grid.Row="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="2*" MinWidth="60"></ColumnDefinition>
|
||||||
|
<ColumnDefinition Width="4"></ColumnDefinition>
|
||||||
|
<ColumnDefinition Width="*" MinWidth="60"></ColumnDefinition>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox Grid.Column="2" VerticalContentAlignment="Top" HorizontalContentAlignment="Left"
|
||||||
|
TextWrapping="Wrap" Text="{Binding ScannedText}">
|
||||||
|
</TextBox>
|
||||||
|
<GridSplitter Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ResizeBehavior="PreviousAndNext"
|
||||||
|
ResizeDirection="Columns">
|
||||||
|
</GridSplitter>
|
||||||
|
<ListBox Grid.Column="0"
|
||||||
|
d:ItemsSource="{d:SampleData ItemCount=25}"
|
||||||
|
ItemsSource="{Binding Source={StaticResource View.Words}}"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
IsItemsHost="True" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid ToolTip="{Binding Text}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="36" />
|
||||||
|
<ColumnDefinition Width="80" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="{Binding Confidence,
|
||||||
|
StringFormat=0.00}" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Text="{Binding Text}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<UniformGrid
|
||||||
|
IsEnabled="{Binding IsIdle}"
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="16"
|
||||||
|
Columns="3">
|
||||||
|
<CheckBox
|
||||||
|
x:Name="EnableThreshold"
|
||||||
|
Content="Apply Threshold"
|
||||||
|
IsChecked="{Binding ImageProcessorConfiguration.EnableThresholding}" />
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
Margin="4"
|
||||||
|
IsEnabled="{Binding ElementName=EnableThreshold,
|
||||||
|
Path=IsChecked}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0">Block Width:</TextBlock>
|
||||||
|
<Slider
|
||||||
|
x:Name="SldThreshold1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="4,0"
|
||||||
|
Maximum="100"
|
||||||
|
Minimum="0"
|
||||||
|
Thumb.DragCompleted="SldThreshold1_OnDragCompleted"
|
||||||
|
Value="15" />
|
||||||
|
<TextBlock Grid.Column="2">
|
||||||
|
<Run Text="{Binding Value, ElementName=SldThreshold1, FallbackValue=0, StringFormat=0.00}" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
Margin="4"
|
||||||
|
IsEnabled="{Binding ElementName=EnableThreshold,
|
||||||
|
Path=IsChecked}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0">Block Height:</TextBlock>
|
||||||
|
<Slider
|
||||||
|
x:Name="SldThreshold2"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="4,0"
|
||||||
|
Maximum="100"
|
||||||
|
Minimum="0"
|
||||||
|
Thumb.DragCompleted="SldThreshold2_OnDragCompleted"
|
||||||
|
Value="17" />
|
||||||
|
<TextBlock Grid.Column="2">
|
||||||
|
<Run Text="{Binding Value, ElementName=SldThreshold2, FallbackValue=0, StringFormat=0.00}" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
Content="Resize"
|
||||||
|
IsChecked="{Binding ImageProcessorConfiguration.EnableResizing}" />
|
||||||
|
<Grid Margin="4">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0">Border:</TextBlock>
|
||||||
|
<Slider
|
||||||
|
x:Name="SldBorder"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="4,0"
|
||||||
|
Maximum="25"
|
||||||
|
Minimum="0"
|
||||||
|
Thumb.DragCompleted="SldBorder_OnDragCompleted"
|
||||||
|
Value="10" />
|
||||||
|
<TextBlock Grid.Column="2">
|
||||||
|
<Run Text="{Binding Value, ElementName=SldBorder, FallbackValue=0, StringFormat=0.00}" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Rectangle />
|
||||||
|
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
Content="Filter connected components"
|
||||||
|
IsChecked="{Binding ImageProcessorConfiguration.FilterConnectedComponents}" />
|
||||||
|
</UniformGrid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using GUI.ViewModels;
|
||||||
|
using ImageMagick;
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
|
||||||
|
namespace GUI.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class ImageView : Window
|
||||||
|
{
|
||||||
|
public ImageView()
|
||||||
|
{
|
||||||
|
DataContext = new ImageViewModel();
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageView(MagickImage image)
|
||||||
|
{
|
||||||
|
DataContext = new ImageViewModel(image);
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SldThreshold1_OnDragCompleted(object sender, DragCompletedEventArgs args)
|
||||||
|
{
|
||||||
|
((ImageViewModel)DataContext).ImageProcessorConfiguration.ThresholdWidth = (int)Math.Round(((Slider)sender).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SldThreshold2_OnDragCompleted(object sender, DragCompletedEventArgs args)
|
||||||
|
{
|
||||||
|
((ImageViewModel)DataContext).ImageProcessorConfiguration.ThresholdHeight = (int)Math.Round(((Slider)sender).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void SldBorder_OnDragCompleted(object sender, DragCompletedEventArgs e)
|
||||||
|
{
|
||||||
|
((ImageViewModel)DataContext).ImageProcessorConfiguration.Border = (int)Math.Round(((Slider)sender).Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="GUI.Views.LogView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:viewModels="clr-namespace:GUI.ViewModels"
|
||||||
|
Title="LogView"
|
||||||
|
Width="800"
|
||||||
|
Height="450"
|
||||||
|
d:DataContext="{d:DesignInstance viewModels:LogViewModel}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<DataGrid
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
ItemsSource="{Binding LoggingCollection.Items}"
|
||||||
|
VirtualizingPanel.ScrollUnit="Pixel">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="Auto"
|
||||||
|
MinWidth="120"
|
||||||
|
Binding="{Binding Timestamp}"
|
||||||
|
Header="Timestamp" />
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="*"
|
||||||
|
MinWidth="120"
|
||||||
|
MaxWidth="300"
|
||||||
|
Binding="{Binding Message}"
|
||||||
|
Header="Message">
|
||||||
|
<DataGridTextColumn.ElementStyle>
|
||||||
|
<Style>
|
||||||
|
<Setter Property="TextBlock.TextAlignment" Value="Left" />
|
||||||
|
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.ElementStyle>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using GUI.ViewModels;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace GUI.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for LogView.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class LogView : Window
|
||||||
|
{
|
||||||
|
public LogView(LoggingCollection loggingCollection)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = new LogViewModel(loggingCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lookup.Interface\Lookup.Interface.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
using Lookup.Interface;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Lookup.Abstract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Basic <see cref="ILookup{TKey,TValue}"/> implementation
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">Type of the key referencing the <typeparamref name="TValue"/>s</typeparam>
|
||||||
|
/// <typeparam name="TValue">Type of the stored values, referenced by <typeparamref name="TKey"/></typeparam>
|
||||||
|
public abstract class Lookup<TKey, TValue>
|
||||||
|
: Dictionary<TKey, ICollection<TValue>>,
|
||||||
|
ILookup<TKey, TValue>
|
||||||
|
where TKey : notnull
|
||||||
|
{
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(IDictionary<TKey, ICollection<TValue>> dictionary) : base(dictionary)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(
|
||||||
|
IDictionary<TKey, ICollection<TValue>> dictionary, IEqualityComparer<TKey>? comparer
|
||||||
|
) : base(dictionary, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(IEnumerable<KeyValuePair<TKey, ICollection<TValue>>> collection) :
|
||||||
|
base(collection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(
|
||||||
|
IEnumerable<KeyValuePair<TKey, ICollection<TValue>>> collection,
|
||||||
|
IEqualityComparer<TKey>? comparer
|
||||||
|
) : base(collection, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(IEqualityComparer<TKey>? comparer) : base(comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(int capacity) : base(capacity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(int capacity, IEqualityComparer<TKey>? comparer) : base(capacity, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected Lookup(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IDictionary<TKey,ICollection<TValue>>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Add(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
var collection = TryGetValue(key, out var values) ? values : Add(key);
|
||||||
|
collection.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void AddRange(TKey key, IEnumerable<TValue> values)
|
||||||
|
{
|
||||||
|
var collection = TryGetValue(key, out var existingCollection)
|
||||||
|
? existingCollection
|
||||||
|
: Add(key);
|
||||||
|
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
collection.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Remove(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
if (!TryGetValue(key, out var values))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
values.Remove(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICollection<TValue> GetOrAdd(TKey key)
|
||||||
|
{
|
||||||
|
return TryGetValue(key, out var values)
|
||||||
|
? values
|
||||||
|
: Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of ILookup<TKey,TValue>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract ICollection<TValue> Add(TKey key);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IDisposable
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Dispose()"/>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Ocr.Lookup
|
||||||
|
|
||||||
|
Base project for looking up data with different storage backends
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace Lookup.Database.Configurations;
|
||||||
|
|
||||||
|
internal class LookupConfiguration<TKey, TValue>
|
||||||
|
: IEntityTypeConfiguration<IKeyEntity<TKey, TValue>>
|
||||||
|
where TKey : class, IKeyEntity<TKey, TValue>
|
||||||
|
where TValue : class, IValueEntity<TKey, TValue>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Configure(EntityTypeBuilder<IKeyEntity<TKey, TValue>> builder)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.HasMany(e => e.Values)
|
||||||
|
.WithMany(e => e.Keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Lookup.Database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for database entities
|
||||||
|
/// </summary>
|
||||||
|
public interface IEntity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id of the database entity
|
||||||
|
/// </summary>
|
||||||
|
int Id { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Lookup.Database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for <see cref="IEntity"/> variants
|
||||||
|
/// which reference <see cref="IValueEntity{TKey,TValue}"/> instances
|
||||||
|
/// </summary>
|
||||||
|
public interface IKeyEntity<TKey, TValue> : IEntity
|
||||||
|
where TKey : class, IKeyEntity<TKey, TValue>
|
||||||
|
where TValue : class, IValueEntity<TKey, TValue>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The referenced <see cref="IValueEntity{TKey,TValue}"/> instances
|
||||||
|
/// </summary>
|
||||||
|
DbSet<IValueEntity<TKey, TValue>> Values { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Lookup.Database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for <see cref="IEntity"/> variants
|
||||||
|
/// which are referenced by <see cref="IKeyEntity{TKey,TValue}"/> instances
|
||||||
|
/// </summary>
|
||||||
|
public interface IValueEntity<TKey, TValue> : IEntity
|
||||||
|
where TKey : class, IKeyEntity<TKey, TValue>
|
||||||
|
where TValue : class, IValueEntity<TKey, TValue>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The referencing <see cref="IKeyEntity{TKey,TValue}"/> instances
|
||||||
|
/// </summary>
|
||||||
|
DbSet<IKeyEntity<TKey, TValue>> Keys { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lookup.Interface\Lookup.Interface.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using Lookup.Database.Configurations;
|
||||||
|
using Lookup.Interface;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Lookup.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Basic <see cref="DbContext"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey"></typeparam>
|
||||||
|
/// <typeparam name="TValue"></typeparam>
|
||||||
|
public class LookupDbContext<TKey, TValue> : DbContext
|
||||||
|
where TKey : class, IKeyEntity<TKey, TValue>
|
||||||
|
where TValue : class, IRelated<TKey>, IValueEntity<TKey, TValue>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stored <see cref="IKeyEntity{TKey,TValue}"/> instances
|
||||||
|
/// </summary>
|
||||||
|
public virtual DbSet<IKeyEntity<TKey, TValue>> Keys { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stored <see cref="IValueEntity{TKey,TValue}"/> instances
|
||||||
|
/// </summary>
|
||||||
|
public virtual DbSet<IValueEntity<TKey, TValue>> Values { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.ApplyConfiguration(new LookupConfiguration<TKey, TValue>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Ocr.Lookup.Database
|
||||||
|
|
||||||
|
Database backend for lookup data storage
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using Lookup.Memory;
|
||||||
|
|
||||||
|
namespace Lookup.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Interface.ILookup{TKey,TValue}"/>
|
||||||
|
/// implementation, which stores data in <see cref="ICollection{T}"/>s.
|
||||||
|
/// Supports writing the stored data to a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">Type of the key referencing the <typeparamref name="TValue"/>s</typeparam>
|
||||||
|
/// <typeparam name="TValue">Type of the stored values, referenced by <typeparamref name="TKey"/></typeparam>
|
||||||
|
public class FileLookup<TKey, TValue> : MemoryLookup<TKey, TValue>
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public FileLookup(string path)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
using var writer = new StreamWriter(System.IO.File.Create(Path));
|
||||||
|
foreach (var kv in this)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"{kv.Key};{string.Join(',', kv.Value)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Clear()
|
||||||
|
{
|
||||||
|
base.Clear();
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(Path))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lookup.Memory\Lookup.Memory.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Lookup.Interface;
|
||||||
|
|
||||||
|
public interface IKey<TKey>
|
||||||
|
{
|
||||||
|
public TKey Key { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Lookup.Interface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for <see cref="ILookup"/>s,
|
||||||
|
/// storing data as key-value pairs
|
||||||
|
/// </summary>
|
||||||
|
public interface ILookup
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for <see cref="ILookup{TKey, TValue}"/>s,
|
||||||
|
/// storing data as <typeparamref name="TKey"/>-<typeparamref name="TValue"/> pairs
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">Type of the key referencing the <typeparamref name="TValue"/>s</typeparam>
|
||||||
|
/// <typeparam name="TValue">Type of the stored values, referenced by <typeparamref name="TKey"/></typeparam>
|
||||||
|
public interface ILookup<TKey, TValue>
|
||||||
|
: ILookup,
|
||||||
|
IDictionary<TKey, ICollection<TValue>>,
|
||||||
|
IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a key to the <see cref="ILookup"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">
|
||||||
|
/// The <typeparamref name="TKey"/> to add
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The new <see cref="ICollection{TValue}"/>
|
||||||
|
/// to store <typeparamref name="TValue"/>s in
|
||||||
|
/// </returns>
|
||||||
|
ICollection<TValue> Add(TKey key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <typeparamref name="TValue"/> to an existing collection
|
||||||
|
/// referenced by <typeparamref name="TKey"/> in the <see cref="ILookup"/>
|
||||||
|
/// </summary>
|
||||||
|
public void Add(TKey key, TValue value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple <typeparamref name="TValue"/>s to
|
||||||
|
/// an existing <see cref="ICollection{TValue}"/>
|
||||||
|
/// referenced by <typeparamref name="TKey"/> in
|
||||||
|
/// the <see cref="ILookup"/>
|
||||||
|
/// </summary>
|
||||||
|
public void AddRange(TKey key, IEnumerable<TValue> values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a <typeparamref name="TValue"/> from
|
||||||
|
/// an existing <see cref="ICollection{TValue}"/>
|
||||||
|
/// referenced by <typeparamref name="TKey"/> in
|
||||||
|
/// the <see cref="ILookup"/>
|
||||||
|
/// </summary>
|
||||||
|
public bool Remove(TKey key, TValue value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an existing <see cref="ICollection{TValue}"/> referenced by
|
||||||
|
/// <typeparamref name="TKey"/> or creates it, if it does not already exist
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key">
|
||||||
|
/// The <typeparamref name="TKey"/> to add
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The <see cref="ICollection{TValue}"/> to
|
||||||
|
/// store <typeparamref name="TValue"/>s in
|
||||||
|
/// </returns>
|
||||||
|
public ICollection<TValue> GetOrAdd(TKey key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Lookup.Interface;
|
||||||
|
|
||||||
|
public interface IRelated<TKey>
|
||||||
|
{
|
||||||
|
public ICollection<TKey> Keys { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Lookup.Interface;
|
||||||
|
|
||||||
|
public interface IValues<TValue>
|
||||||
|
{
|
||||||
|
public TValue Values { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Ocr.Lookup
|
||||||
|
|
||||||
|
Base project for looking up data with different storage backends
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lookup.Abstract\Lookup.Abstract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using Lookup.Interface;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Lookup.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ILookup{TKey,TValue}"/>
|
||||||
|
/// implementation, storing data in <see cref="ICollection{T}"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">Type of the key referencing the <typeparamref name="TValue"/>s</typeparam>
|
||||||
|
/// <typeparam name="TValue">Type of the stored values, referenced by <typeparamref name="TKey"/></typeparam>
|
||||||
|
public class MemoryLookup<TKey, TValue> : Abstract.Lookup<TKey, TValue>
|
||||||
|
where TKey : notnull
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(IDictionary<TKey, ICollection<TValue>> dictionary) : base(dictionary)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(
|
||||||
|
IDictionary<TKey, ICollection<TValue>> dictionary, IEqualityComparer<TKey>? comparer
|
||||||
|
) : base(dictionary, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(
|
||||||
|
IEnumerable<KeyValuePair<TKey, ICollection<TValue>>> collection
|
||||||
|
) : base(collection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(
|
||||||
|
IEnumerable<KeyValuePair<TKey, ICollection<TValue>>> collection,
|
||||||
|
IEqualityComparer<TKey>? comparer
|
||||||
|
) : base(collection, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(IEqualityComparer<TKey>? comparer) : base(comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(int capacity) : base(capacity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(int capacity, IEqualityComparer<TKey>? comparer) : base(capacity, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MemoryLookup(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Overrides of Lookup<TKey,ICollection<TValue>>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ICollection<TValue> Add(TKey key)
|
||||||
|
{
|
||||||
|
base.Add(key, new List<TValue>());
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Add(TKey)" />
|
||||||
|
public ICollection<TValue> Add(TKey key, int collectionCapacity)
|
||||||
|
{
|
||||||
|
base.Add(key, new List<TValue>(collectionCapacity));
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using Lookup.Interface;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Lookup.Memory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="ILookup{TKey,TValue}"/>
|
||||||
|
/// implementation, storing weakly-referenced <typeparamref name="TValue"/>s in <see cref="ICollection{T}"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">Type of the key referencing the <typeparamref name="TValue"/>s</typeparam>
|
||||||
|
/// <typeparam name="TValue">Type of the stored values, referenced by <typeparamref name="TKey"/></typeparam>
|
||||||
|
public class WeakReferenceMemoryLookup<TKey, TValue>
|
||||||
|
: MemoryLookup<TKey, WeakReference<TValue>>
|
||||||
|
where TKey : notnull
|
||||||
|
where TValue : class
|
||||||
|
{
|
||||||
|
#region Constructors
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(
|
||||||
|
IDictionary<TKey, ICollection<WeakReference<TValue>>> dictionary
|
||||||
|
) : base(dictionary)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(
|
||||||
|
IDictionary<TKey, ICollection<WeakReference<TValue>>> dictionary,
|
||||||
|
IEqualityComparer<TKey>? comparer
|
||||||
|
) : base(dictionary, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(
|
||||||
|
IEnumerable<KeyValuePair<TKey, ICollection<WeakReference<TValue>>>> collection
|
||||||
|
) : base(collection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(
|
||||||
|
IEnumerable<KeyValuePair<TKey, ICollection<WeakReference<TValue>>>> collection,
|
||||||
|
IEqualityComparer<TKey>? comparer
|
||||||
|
) : base(collection, comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(IEqualityComparer<TKey>? comparer) : base(comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(int capacity) : base(capacity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(int capacity, IEqualityComparer<TKey>? comparer) : base(capacity,
|
||||||
|
comparer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WeakReferenceMemoryLookup(SerializationInfo info, StreamingContext context) : base(info,
|
||||||
|
context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.33424.131
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Memory", "Lookup.Memory\Lookup.Memory.csproj", "{330927B1-6BE3-4C89-9680-3FF6B16D26AB}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.IO", "Lookup.File\Lookup.IO.csproj", "{1E08545D-6878-435E-86CE-04EE9C952860}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Database", "Lookup.Database\Lookup.Database.csproj", "{F1AA0A94-DB98-4B7D-A823-9487A396E2DD}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Interface", "Lookup.Interface\Lookup.Interface.csproj", "{93B14D5B-27A9-4CB6-934E-0630CB5FE337}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Abstract", "Lookup.Abstract\Lookup.Abstract.csproj", "{49187C7B-26A4-4C02-901B-48190FC566AF}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{330927B1-6BE3-4C89-9680-3FF6B16D26AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{330927B1-6BE3-4C89-9680-3FF6B16D26AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{330927B1-6BE3-4C89-9680-3FF6B16D26AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{330927B1-6BE3-4C89-9680-3FF6B16D26AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1E08545D-6878-435E-86CE-04EE9C952860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1E08545D-6878-435E-86CE-04EE9C952860}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1E08545D-6878-435E-86CE-04EE9C952860}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1E08545D-6878-435E-86CE-04EE9C952860}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F1AA0A94-DB98-4B7D-A823-9487A396E2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F1AA0A94-DB98-4B7D-A823-9487A396E2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F1AA0A94-DB98-4B7D-A823-9487A396E2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F1AA0A94-DB98-4B7D-A823-9487A396E2DD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{93B14D5B-27A9-4CB6-934E-0630CB5FE337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{93B14D5B-27A9-4CB6-934E-0630CB5FE337}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{93B14D5B-27A9-4CB6-934E-0630CB5FE337}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{93B14D5B-27A9-4CB6-934E-0630CB5FE337}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{49187C7B-26A4-4C02-901B-48190FC566AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{49187C7B-26A4-4C02-901B-48190FC566AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{49187C7B-26A4-4C02-901B-48190FC566AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{49187C7B-26A4-4C02-901B-48190FC566AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {DFA659EE-FE78-4BD9-888B-78984354093E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using Ocr.Tesseract.Interface;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Screenshots.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for the <see cref="ScreenshotProcessor"/>
|
||||||
|
/// </summary>
|
||||||
|
public class ScreenshotProcessorConfiguration : IImageProcessorConfiguration, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
#region Implementation of IMagickImageValueProcessorSettings
|
||||||
|
|
||||||
|
private int _border = 10;
|
||||||
|
private bool _enableResizing = true;
|
||||||
|
private bool _enableThresholding = true;
|
||||||
|
private bool _filterConnectedComponents;
|
||||||
|
private int _thresholdWidth = 15, _thresholdHeight = 17;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Border
|
||||||
|
{
|
||||||
|
get => _border;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _border)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_border = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool EnableResizing
|
||||||
|
{
|
||||||
|
get => _enableResizing;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _enableResizing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_enableResizing = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool EnableThresholding
|
||||||
|
{
|
||||||
|
get => _enableThresholding;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _enableThresholding)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_enableThresholding = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool FilterConnectedComponents
|
||||||
|
{
|
||||||
|
get => _filterConnectedComponents;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _filterConnectedComponents)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_filterConnectedComponents = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int ThresholdHeight
|
||||||
|
{
|
||||||
|
get => _thresholdHeight;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _thresholdHeight)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_thresholdHeight = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int ThresholdWidth
|
||||||
|
{
|
||||||
|
get => _thresholdWidth;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _thresholdWidth)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_thresholdWidth = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Implementation of IMagickImageValueProcessorSettings
|
||||||
|
|
||||||
|
#region Implementation of INotifyPropertyChanged
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Implementation of INotifyPropertyChanged
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Ocr.Tesseract.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Screenshots.Configuration;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class TesseractScreenshotConfiguration : ITesseractConfiguration
|
||||||
|
{
|
||||||
|
#region Implementation of ITesseractConfiguration
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DataPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string[] Languages { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDictionary<string, object> Variables { get; set; } = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "load_system_dawg", false },
|
||||||
|
{ "language_model_penalty_non_freq_dict_word", 1 },
|
||||||
|
{ "language_model_penalty_non_dict_word", 1 },
|
||||||
|
// { "user_words_suffix", "fra.user-words" },
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||||
|
<PackageReference Include="Magick.NET.Core" Version="13.2.0" />
|
||||||
|
<PackageReference Include="Magick.NET.SystemDrawing" Version="7.0.6" />
|
||||||
|
<PackageReference Include="Tesseract" Version="5.2.0" />
|
||||||
|
<PackageReference Include="Tesseract.Drawing" Version="5.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Lookup\Lookup.Interface\Lookup.Interface.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Process\Process.Abstract\Process.Abstract.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Process\Process.Interface\Process.Interface.csproj" />
|
||||||
|
<ProjectReference Include="..\Ocr.Tesseract\Ocr.Tesseract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using Ocr.Tesseract.Extensions;
|
||||||
|
using Ocr.Tesseract.Screenshots.Configuration;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Screenshots;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class ScreenshotProcessor : ImageProcessor
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ScreenshotProcessor(ScreenshotProcessorConfiguration configuration)
|
||||||
|
: base(configuration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="System.Diagnostics.Process"/>
|
||||||
|
public override IEnumerable<MagickImage> Process(MagickImage image)
|
||||||
|
{
|
||||||
|
var tImage = image.CloneImage();
|
||||||
|
|
||||||
|
if (Configuration.EnableResizing)
|
||||||
|
{
|
||||||
|
tImage = tImage
|
||||||
|
.ResizeImage(
|
||||||
|
2f,
|
||||||
|
FilterType.Lanczos2Sharp,
|
||||||
|
PixelInterpolateMethod.Mesh
|
||||||
|
)
|
||||||
|
.Resample(300, DensityUnit.PixelsPerInch);
|
||||||
|
|
||||||
|
yield return tImage.CloneImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Configuration.EnableThresholding)
|
||||||
|
{
|
||||||
|
tImage = tImage
|
||||||
|
.NormalizeImage()
|
||||||
|
.RemoveAlpha(MagickColors.White)
|
||||||
|
.ToGrayscale()
|
||||||
|
.ThresholdAdaptive(Configuration.ThresholdWidth, Configuration.ThresholdHeight)
|
||||||
|
.ToBinary();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Configuration.Border > 0)
|
||||||
|
{
|
||||||
|
tImage = tImage.AddBorder(Configuration.Border, MagickColors.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return tImage;
|
||||||
|
yield return tImage.CloneImage().NegateColors();
|
||||||
|
|
||||||
|
// todo filter large connected components
|
||||||
|
|
||||||
|
// var resized = fluent .CloneObject() .Resize( 0.25f, FilterType.Point,
|
||||||
|
// PixelInterpolateMethod.Integer ); yield return resized.ToImage();
|
||||||
|
//
|
||||||
|
// var hlines = resized .GetHLines() .Select(c => new MagickGeometry(c.Item1, c.Item2, c.Item1 +
|
||||||
|
// 3, c.Item2 + 3)) .ToArray(); Log.Information($"Matched {hlines.Length} geometries");
|
||||||
|
//
|
||||||
|
// var rgbImage = resized.CloneObject().ToRgb(); rgbImage.Fill(MagickColors.Red, hlines); yield
|
||||||
|
// return rgbImage.ToImage();
|
||||||
|
|
||||||
|
// if (Settings.FilterConnectedComponents) { var colorImage = fluent .CloneObject()
|
||||||
|
// .GetConnectedComponents( _connectedComponentsSettings, out var components ) .ToRgb();
|
||||||
|
//
|
||||||
|
// var pixels = colorImage.GetPixels(); colorImage.Fill( MagickColors.Red, components .Where(c
|
||||||
|
// => c.IsFilled(pixels)) .Select(c => c.ToGeometry()) );
|
||||||
|
|
||||||
|
// yield return colorImage.ToImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Processor<MagickImage,IMagickImageValueProcessorSettings>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<MagickImage> Process(IEnumerable<MagickImage> inputs)
|
||||||
|
{
|
||||||
|
return inputs.SelectMany(Process);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Process.Abstract;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters <see cref="Word"/>s by confidence
|
||||||
|
/// </summary>
|
||||||
|
public class ConfidenceFilter : Processor<ScanResult, ScanResult>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum confidence
|
||||||
|
/// </summary>
|
||||||
|
public int Min { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum confidence
|
||||||
|
/// </summary>
|
||||||
|
public int Max { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ConfidenceFilter(int min, int max)
|
||||||
|
{
|
||||||
|
Min = min;
|
||||||
|
Max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ConfidenceFilter(int min)
|
||||||
|
: this(min, 100)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Processor<Word,Word>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<ScanResult> Process(IEnumerable<ScanResult> inputs)
|
||||||
|
{
|
||||||
|
return inputs.Where(s => s.Word.Confidence >= Min && s.Word.Confidence <= Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="TesseractProcessor"/> configuration
|
||||||
|
/// </summary>
|
||||||
|
public interface ITesseractConfiguration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tesseract data directory path
|
||||||
|
/// </summary>
|
||||||
|
public string DataPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Supported languages
|
||||||
|
/// </summary>
|
||||||
|
public string[] Languages { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tesseract environment variables
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<string, object> Variables { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Process.Abstract;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters duplicate <see cref="Word"/>s
|
||||||
|
/// </summary>
|
||||||
|
public class DuplicateFilter
|
||||||
|
: Processor<ScanResult, ScanResult>
|
||||||
|
{
|
||||||
|
#region Overrides of Processor<KeyValuePair<Word,MagickImage>,KeyValuePair<Word,MagickImage>>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<ScanResult> Process(
|
||||||
|
IEnumerable<ScanResult> inputs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return inputs
|
||||||
|
.GroupBy(sr => sr.Word.Text)
|
||||||
|
.Select(DuplicateSelector)
|
||||||
|
.OrderByDescending(w => w.Word.Confidence);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects the instance to keep, if duplicates are detected in the input data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="g">
|
||||||
|
/// <see cref="IGrouping{TKey,TElement}"/>
|
||||||
|
/// containing the duplicate instances
|
||||||
|
/// </param>
|
||||||
|
/// <returns>One single instance to add to the output data</returns>
|
||||||
|
protected virtual ScanResult DuplicateSelector(IGrouping<string, ScanResult> g)
|
||||||
|
{
|
||||||
|
// Default: Return instance with the highest confidence
|
||||||
|
return g.MaxBy(sr => sr.Word.Confidence)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for the <see cref="IConnectedComponent{TQuantumType}"/> type
|
||||||
|
/// </summary>
|
||||||
|
public static class ConnectedComponentExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determine whether this <see cref="IConnectedComponent{TQuantumType}"/> is filled out
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsFilled(
|
||||||
|
this IConnectedComponent<ushort> component,
|
||||||
|
IPixelCollection<ushort> pixels
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var initial = pixels[component.X, component.Y]!.GetChannel(0);
|
||||||
|
return pixels
|
||||||
|
.GetArea(component.X, component.Y, component.Width, component.Height)!
|
||||||
|
.All(p => p == initial);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for the <see cref="MagickImage"/> type
|
||||||
|
/// </summary>
|
||||||
|
public static class MagickImageExtensions
|
||||||
|
{
|
||||||
|
public static double GetIntensity(this MagickImage image)
|
||||||
|
{
|
||||||
|
using var pixels = image.GetPixels();
|
||||||
|
double totalIntensity = pixels.Sum(pixel =>
|
||||||
|
(pixel.GetChannel(0) + pixel.GetChannel(1) + pixel.GetChannel(2)) / 3.0);
|
||||||
|
return totalIntensity / pixels.Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage ThresholdAdaptive(
|
||||||
|
this MagickImage image,
|
||||||
|
int width, int height, Channels channel = Channels.Gray
|
||||||
|
)
|
||||||
|
{
|
||||||
|
image.AdaptiveThreshold(width, height, channel);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage NegateColors(
|
||||||
|
this MagickImage image
|
||||||
|
)
|
||||||
|
{
|
||||||
|
image.Negate();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage NormalizeImage(this MagickImage image)
|
||||||
|
{
|
||||||
|
image.Normalize();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage ToBinary(this MagickImage image)
|
||||||
|
{
|
||||||
|
image.Depth = 1;
|
||||||
|
image.SetBitDepth(1);
|
||||||
|
image.ColorType = ColorType.Bilevel;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage AddBorder(
|
||||||
|
this MagickImage image,
|
||||||
|
int size, IMagickColor<ushort> color
|
||||||
|
)
|
||||||
|
{
|
||||||
|
image.BorderColor = color;
|
||||||
|
image.Border(size);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage RemoveAlpha(this MagickImage image, IMagickColor<ushort> color)
|
||||||
|
{
|
||||||
|
image.BackgroundColor = color;
|
||||||
|
image.Alpha(AlphaOption.Remove);
|
||||||
|
image.Alpha(AlphaOption.Off);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage Resample(this MagickImage image, double density, DensityUnit unit)
|
||||||
|
{
|
||||||
|
image.Density = new Density(density, unit);
|
||||||
|
image.Resample(new PointD(density));
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] hLineKernel =
|
||||||
|
{
|
||||||
|
2, 2, 2,
|
||||||
|
-1, -1, -1,
|
||||||
|
2, 2, 2,
|
||||||
|
// -1, -1, -1
|
||||||
|
};
|
||||||
|
|
||||||
|
private static int[] vLineKernel =
|
||||||
|
{
|
||||||
|
-1, 2, -1,
|
||||||
|
-1, 2, -1,
|
||||||
|
-1, 2, -1
|
||||||
|
};
|
||||||
|
|
||||||
|
public static MagickImage GetHLines(this MagickImage image, out IEnumerable<(int, int)> coords)
|
||||||
|
{
|
||||||
|
var pixels = image.GetPixels();
|
||||||
|
coords = Filter(image, hLineKernel, pixels);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage GetVLines(this MagickImage image, out IEnumerable<(int, int)> coords)
|
||||||
|
{
|
||||||
|
var pixels = image.GetPixels();
|
||||||
|
coords = Filter(image, vLineKernel, pixels);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the provided <paramref name="kernel"/> and returns a list of coordinates where the filter matches
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="kernel"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
private static IEnumerable<(int, int)> Filter(
|
||||||
|
IMagickImage image, int[] kernel, IPixelCollection<ushort> pixels
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const int kernelSize = 3;
|
||||||
|
|
||||||
|
if (kernel.Length != (kernelSize * kernelSize))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Filter kernels must be a {kernelSize}x{kernelSize} matrix",
|
||||||
|
nameof(kernel));
|
||||||
|
}
|
||||||
|
|
||||||
|
(int, int)? current = null;
|
||||||
|
for (int x = 0; x < image.Width; x += kernelSize)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < image.Height; y += kernelSize)
|
||||||
|
{
|
||||||
|
if (IsMatch(x, y))
|
||||||
|
{
|
||||||
|
current ??= (x, y);
|
||||||
|
}
|
||||||
|
else if (current.HasValue)
|
||||||
|
{
|
||||||
|
yield return current.Value;
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsMatch(int x, int y)
|
||||||
|
{
|
||||||
|
for (int kernelX = 0; kernelX < 3; kernelX++)
|
||||||
|
{
|
||||||
|
for (int kernelY = 0; kernelY < 3; kernelY++)
|
||||||
|
{
|
||||||
|
var k = kernel[kernelX + (kernelSize * kernelY)];
|
||||||
|
var v = pixels![x + kernelX, y + kernelY]!.GetChannel((int)PixelChannel.Gray);
|
||||||
|
|
||||||
|
switch (k)
|
||||||
|
{
|
||||||
|
case > 0 when v <= 0:
|
||||||
|
case < 0 when v > 0:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage ResizeImage(
|
||||||
|
this MagickImage image,
|
||||||
|
float factor,
|
||||||
|
FilterType filterType,
|
||||||
|
PixelInterpolateMethod interpolateMethod
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return image.ResizeImage(
|
||||||
|
(int)Math.Round(image.Width * factor),
|
||||||
|
(int)Math.Round(image.Height * factor),
|
||||||
|
filterType,
|
||||||
|
interpolateMethod
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage ResizeImage(
|
||||||
|
this MagickImage image,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
FilterType filterType,
|
||||||
|
PixelInterpolateMethod interpolateMethod
|
||||||
|
)
|
||||||
|
{
|
||||||
|
image.FilterType = filterType;
|
||||||
|
image.Interpolate = interpolateMethod;
|
||||||
|
image.Resize(new MagickGeometry(width, height));
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage ToRgb(
|
||||||
|
this MagickImage image
|
||||||
|
)
|
||||||
|
{
|
||||||
|
image.ColorSpace = ColorSpace.sRGB;
|
||||||
|
image.ColorType = ColorType.TrueColorAlpha;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage ToGrayscale(
|
||||||
|
this MagickImage image
|
||||||
|
)
|
||||||
|
{
|
||||||
|
image.Grayscale();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage Negate(this MagickImage image)
|
||||||
|
{
|
||||||
|
image.Negate();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage GrayscaleBonW(this MagickImage image)
|
||||||
|
{
|
||||||
|
var intensity = image.GetIntensity();
|
||||||
|
var maxDepth = Math.Pow(2, image.DetermineBitDepth());
|
||||||
|
|
||||||
|
image.Grayscale();
|
||||||
|
if (intensity < maxDepth / 2f)
|
||||||
|
{
|
||||||
|
image.NegateGrayscale();
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static MagickImage Fill(
|
||||||
|
this MagickImage image,
|
||||||
|
IMagickColor<ushort> color,
|
||||||
|
IEnumerable<IMagickGeometry> geometries
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var drawables = new Drawables().FillColor(color);
|
||||||
|
|
||||||
|
foreach (var component in geometries)
|
||||||
|
{
|
||||||
|
drawables.Rectangle(
|
||||||
|
component.X,
|
||||||
|
component.Y,
|
||||||
|
component.X + component.Width,
|
||||||
|
component.Height + component.Y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawables.Draw(image);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MagickImage GetConnectedComponents(
|
||||||
|
this MagickImage image,
|
||||||
|
IConnectedComponentsSettings settings,
|
||||||
|
out IReadOnlyCollection<IConnectedComponent<ushort>> components
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var tImage = image.CloneImage();
|
||||||
|
components = tImage.ConnectedComponents(settings);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="MagickImage.Clone()" />
|
||||||
|
public static MagickImage CloneImage(this MagickImage image)
|
||||||
|
{
|
||||||
|
return (MagickImage)image.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for the <see cref="Page"/> type
|
||||||
|
/// </summary>
|
||||||
|
public static class PageExtensions
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the <see cref="Word"/>s in the given <paramref name="page"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">The <see cref="Page"/> to extract words from</param>
|
||||||
|
/// <returns>The extracted <see cref="Word"/>s in the given <paramref name="page"/></returns>
|
||||||
|
public static IEnumerable<Word> GetWords(this Page page)
|
||||||
|
{
|
||||||
|
using var iterator = page.GetIterator();
|
||||||
|
iterator.Begin();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var word = Word.Parse(iterator);
|
||||||
|
if (string.IsNullOrEmpty(word.Text))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return word;
|
||||||
|
} while (iterator.Next(PageIteratorLevel.TextLine, PageIteratorLevel.Word));
|
||||||
|
} while (iterator.Next(PageIteratorLevel.Para, PageIteratorLevel.TextLine));
|
||||||
|
} while (iterator.Next(PageIteratorLevel.Block, PageIteratorLevel.Para));
|
||||||
|
} while (iterator.Next(PageIteratorLevel.Block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using Ocr.Tesseract.Interface;
|
||||||
|
using Process.Abstract;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies various transformation the the input images
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ImageProcessor : Processor<MagickImage, MagickImage>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Image processing configuration
|
||||||
|
/// </summary>
|
||||||
|
public IImageProcessorConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected ImageProcessor(IImageProcessorConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Process(IEnumerable{MagickImage})" />
|
||||||
|
public abstract IEnumerable<MagickImage> Process(MagickImage value);
|
||||||
|
|
||||||
|
#region Overrides of Processor<MagickImage,IMagickImageValueProcessorSettings>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<MagickImage> Process(IEnumerable<MagickImage> inputs)
|
||||||
|
{
|
||||||
|
return inputs.SelectMany(Process);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Ocr.Tesseract.Interface;
|
||||||
|
|
||||||
|
public interface IImageProcessorConfiguration
|
||||||
|
{
|
||||||
|
public int ThresholdWidth { get; set; }
|
||||||
|
public int ThresholdHeight { get; set; }
|
||||||
|
public int Border { get; set; }
|
||||||
|
public bool EnableThresholding { get; set; }
|
||||||
|
public bool EnableResizing { get; set; }
|
||||||
|
public bool FilterConnectedComponents { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Model for <see cref="Word"/> choices
|
||||||
|
/// </summary>
|
||||||
|
public class Choice : IEquatable<Choice>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <param name="confidence"></param>
|
||||||
|
public Choice(string text, float confidence)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Confidence = confidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scanned text
|
||||||
|
/// </summary>
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Confidence that this <see cref="Choice"/> is the correct one
|
||||||
|
/// </summary>
|
||||||
|
public float Confidence { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="Choice"/> object from a <see cref="ChoiceIterator"/>
|
||||||
|
/// </summary>
|
||||||
|
public static Choice Parse(ChoiceIterator iterator)
|
||||||
|
{
|
||||||
|
return new Choice
|
||||||
|
(
|
||||||
|
iterator.GetText(),
|
||||||
|
iterator.GetConfidence()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Object
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Equality members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(Choice? other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, other))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text == other.Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, obj))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, obj))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.GetType() != this.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Equals((Choice)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Choice? left, Choice? right)
|
||||||
|
{
|
||||||
|
return Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Choice? left, Choice? right)
|
||||||
|
{
|
||||||
|
return !Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Struct for storing <see cref="TesseractProcessor"/> results
|
||||||
|
/// </summary>
|
||||||
|
public struct ScanResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="word"></param>
|
||||||
|
/// <param name="image"></param>
|
||||||
|
public ScanResult(Word word, MagickImage image)
|
||||||
|
{
|
||||||
|
Word = word;
|
||||||
|
Image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key referencing <see cref="Image"/>
|
||||||
|
/// </summary>
|
||||||
|
public Word Word { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value referenced by <see cref="Word"/>
|
||||||
|
/// </summary>
|
||||||
|
public MagickImage Image { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Word.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Model for storing scanned words
|
||||||
|
/// </summary>
|
||||||
|
public class Word : Choice, IEquatable<Word>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the <see cref="Word"/> was found in the dictionary
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFromDictionary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the <see cref="Word"/> is numeric
|
||||||
|
/// </summary>
|
||||||
|
public bool Numeric { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Other available <see cref="Choice"/>s for this <see cref="Word"/>
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<Choice> Choices { get; set; } = Array.Empty<Choice>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Word(
|
||||||
|
string text,
|
||||||
|
float confidence,
|
||||||
|
bool isFromDictionary,
|
||||||
|
bool isNumeric
|
||||||
|
)
|
||||||
|
: base(text, confidence)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Confidence = confidence;
|
||||||
|
IsFromDictionary = isFromDictionary;
|
||||||
|
Numeric = isNumeric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Word Parse(ResultIterator iterator)
|
||||||
|
{
|
||||||
|
var word = new Word
|
||||||
|
(
|
||||||
|
iterator.GetText(PageIteratorLevel.Word),
|
||||||
|
iterator.GetConfidence(PageIteratorLevel.Word),
|
||||||
|
iterator.GetWordIsFromDictionary(),
|
||||||
|
iterator.GetWordIsNumeric()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(word.Text))
|
||||||
|
{
|
||||||
|
// No choices available
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
var choices = new List<Choice>();
|
||||||
|
using var choiceIterator = iterator.GetChoiceIterator();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
choices.Add(Parse(choiceIterator));
|
||||||
|
} while (choiceIterator.Next());
|
||||||
|
|
||||||
|
word.Choices = choices;
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Equality members
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Equals(Word? other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, other))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(other)
|
||||||
|
&& IsFromDictionary == other.IsFromDictionary
|
||||||
|
&& Numeric == other.Numeric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, obj))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, obj))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.GetType() != this.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Equals((Word)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(base.GetHashCode(), IsFromDictionary, Numeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Word? left, Word? right)
|
||||||
|
{
|
||||||
|
return Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Word? left, Word? right)
|
||||||
|
{
|
||||||
|
return !Equals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.2.0" />
|
||||||
|
<PackageReference Include="Magick.NET.Core" Version="13.2.0" />
|
||||||
|
<PackageReference Include="Magick.NET.SystemDrawing" Version="7.0.6" />
|
||||||
|
<PackageReference Include="Tesseract" Version="5.2.0" />
|
||||||
|
<PackageReference Include="Tesseract.Drawing" Version="5.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Lookup\Lookup.Interface\Lookup.Interface.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Process\Process.Abstract\Process.Abstract.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Process\Process.Interface\Process.Interface.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Process.Abstract;
|
||||||
|
using Process.Interface;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes a <see cref="ProcessingEventDelegate{T}"/>
|
||||||
|
/// once <see cref="IProcessor{TInput,TOutput}.Process(IEnumerable{TInput})"/>
|
||||||
|
/// is called
|
||||||
|
/// </summary>
|
||||||
|
public class ProcessingEvent<T> : Processor<T, T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called once this <see cref="IProcessor"/> is executed
|
||||||
|
/// </summary>
|
||||||
|
public event ProcessingEventDelegate<T>? OnProcessing;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ProcessingEvent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ProcessingEvent(ProcessingEventDelegate<T> onProcessing)
|
||||||
|
{
|
||||||
|
OnProcessing += onProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Processor<T,T>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<T> Process(IEnumerable<T> inputs)
|
||||||
|
{
|
||||||
|
var values = inputs.ToArray();
|
||||||
|
OnProcessing?.Invoke(this, values);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event created by <see cref="ProcessingEvent{T}"/>
|
||||||
|
/// </summary>
|
||||||
|
public delegate void ProcessingEventDelegate<T>(
|
||||||
|
IProcessor sender, ICollection<T> inputs
|
||||||
|
);
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Process.Abstract;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters words using regular expressions
|
||||||
|
/// </summary>
|
||||||
|
public class RegexFilter : Processor<ScanResult, ScanResult>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Regex"/> to apply. If the expression
|
||||||
|
/// does not match the input, it will be removed from
|
||||||
|
/// the output data of this processor
|
||||||
|
/// </summary>
|
||||||
|
public Regex Regex { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public RegexFilter(Regex regex)
|
||||||
|
{
|
||||||
|
Regex = regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Processor<Word,Word>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<ScanResult> Process(
|
||||||
|
IEnumerable<ScanResult> inputs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var matches = inputs
|
||||||
|
.Select(kv => (Kv: kv, Match: Regex.Match(kv.Word.Text)))
|
||||||
|
.Where(m => m.Match.Success)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var tuple in matches)
|
||||||
|
{
|
||||||
|
tuple.Kv.Word.Text = tuple.Match.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches.Select(t => t.Kv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using ImageMagick;
|
||||||
|
using Ocr.Tesseract.Configuration;
|
||||||
|
using Ocr.Tesseract.Extensions;
|
||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Process.Abstract;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Tesseract;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Scans <see cref="MagickImage"/>s for <see cref="Word"/>s
|
||||||
|
/// and maps the results to a <see cref="ScanResult"/>
|
||||||
|
/// </summary>
|
||||||
|
public class TesseractProcessor : Processor<MagickImage, ScanResult>
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="ITesseractConfiguration"/>
|
||||||
|
public ITesseractConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public TesseractProcessor(ITesseractConfiguration config)
|
||||||
|
{
|
||||||
|
Configuration = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scans the provided <paramref name="image"/> for <see cref="Word"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="image">The <see cref="MagickImage"/> to scan</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A list of <see cref="Word"/>s found
|
||||||
|
/// in the provided <paramref name="image"/>
|
||||||
|
/// </returns>
|
||||||
|
private IEnumerable<Word> Scan(MagickImage image)
|
||||||
|
{
|
||||||
|
// Convert image
|
||||||
|
using var pix = PixConverter.ToPix(image.ToBitmapWithDensity());
|
||||||
|
using var engine = new TesseractEngine(
|
||||||
|
Configuration.DataPath,
|
||||||
|
string.Join('+', Configuration.Languages),
|
||||||
|
EngineMode.Default,
|
||||||
|
Enumerable.Empty<string>(),
|
||||||
|
Configuration.Variables,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DefaultPageSegMode = PageSegMode.AutoOsd
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scan
|
||||||
|
return engine
|
||||||
|
.Process(pix)
|
||||||
|
.GetWords()
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<ScanResult> Process(
|
||||||
|
IEnumerable<MagickImage> inputs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return inputs
|
||||||
|
.SelectMany(Scan, (input, word) => new ScanResult(word, input));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Ocr.Tesseract.Models;
|
||||||
|
using Process.Abstract;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ocr.Tesseract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Word.Text"/> strings to lowercase
|
||||||
|
/// </summary>
|
||||||
|
public class ToLowerProcessor
|
||||||
|
: Processor<ScanResult, ScanResult>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<ScanResult> Process(
|
||||||
|
IEnumerable<ScanResult> inputs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
foreach (var kv in inputs)
|
||||||
|
{
|
||||||
|
kv.Word.Text = kv.Word.Text.ToLower();
|
||||||
|
yield return kv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.33424.131
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Process", "Process", "{C68DA0CF-A004-4E4F-9A6A-C37E9F38193A}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Interface", "..\Process\Process.Interface\Process.Interface.csproj", "{D3695210-38E3-44E7-A3A5-0FB356B1B57E}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Abstract", "..\Process\Process.Abstract\Process.Abstract.csproj", "{8BC5293F-780B-425E-B90F-9695E917CADA}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lookup", "Lookup", "{CFE60455-2706-4A43-B724-B3D6F4CFB003}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Memory", "..\Lookup\Lookup.Memory\Lookup.Memory.csproj", "{3E08171E-04D1-49BC-9DD3-480E2E89B768}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Interface", "..\Lookup\Lookup.Interface\Lookup.Interface.csproj", "{A0CE3D20-DC5C-4449-9562-CDE3339D50E4}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.IO", "..\Lookup\Lookup.File\Lookup.IO.csproj", "{BA13C7E2-FAA6-4E79-A92B-803D0E0464BD}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Database", "..\Lookup\Lookup.Database\Lookup.Database.csproj", "{BF18A079-9E23-454C-BA9D-B339B854A101}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lookup.Abstract", "..\Lookup\Lookup.Abstract\Lookup.Abstract.csproj", "{C16F3302-2E53-4CEF-958E-37EC3308D5EF}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract", "Ocr.Tesseract\Ocr.Tesseract.csproj", "{4D62EA05-B3E1-4EA9-A253-56CB76C2BE78}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocr.Tesseract.Screenshots", "Ocr.Tesseract.Screenshots\Ocr.Tesseract.Screenshots.csproj", "{46028E7B-3CA3-4E5A-BD28-17D04216068F}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D3695210-38E3-44E7-A3A5-0FB356B1B57E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D3695210-38E3-44E7-A3A5-0FB356B1B57E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D3695210-38E3-44E7-A3A5-0FB356B1B57E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D3695210-38E3-44E7-A3A5-0FB356B1B57E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8BC5293F-780B-425E-B90F-9695E917CADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8BC5293F-780B-425E-B90F-9695E917CADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8BC5293F-780B-425E-B90F-9695E917CADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8BC5293F-780B-425E-B90F-9695E917CADA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3E08171E-04D1-49BC-9DD3-480E2E89B768}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3E08171E-04D1-49BC-9DD3-480E2E89B768}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3E08171E-04D1-49BC-9DD3-480E2E89B768}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3E08171E-04D1-49BC-9DD3-480E2E89B768}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A0CE3D20-DC5C-4449-9562-CDE3339D50E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0CE3D20-DC5C-4449-9562-CDE3339D50E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A0CE3D20-DC5C-4449-9562-CDE3339D50E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A0CE3D20-DC5C-4449-9562-CDE3339D50E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BA13C7E2-FAA6-4E79-A92B-803D0E0464BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BA13C7E2-FAA6-4E79-A92B-803D0E0464BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BA13C7E2-FAA6-4E79-A92B-803D0E0464BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BA13C7E2-FAA6-4E79-A92B-803D0E0464BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BF18A079-9E23-454C-BA9D-B339B854A101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BF18A079-9E23-454C-BA9D-B339B854A101}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BF18A079-9E23-454C-BA9D-B339B854A101}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BF18A079-9E23-454C-BA9D-B339B854A101}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C16F3302-2E53-4CEF-958E-37EC3308D5EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C16F3302-2E53-4CEF-958E-37EC3308D5EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C16F3302-2E53-4CEF-958E-37EC3308D5EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C16F3302-2E53-4CEF-958E-37EC3308D5EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4D62EA05-B3E1-4EA9-A253-56CB76C2BE78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4D62EA05-B3E1-4EA9-A253-56CB76C2BE78}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4D62EA05-B3E1-4EA9-A253-56CB76C2BE78}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4D62EA05-B3E1-4EA9-A253-56CB76C2BE78}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{46028E7B-3CA3-4E5A-BD28-17D04216068F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{46028E7B-3CA3-4E5A-BD28-17D04216068F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{46028E7B-3CA3-4E5A-BD28-17D04216068F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{46028E7B-3CA3-4E5A-BD28-17D04216068F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{D3695210-38E3-44E7-A3A5-0FB356B1B57E} = {C68DA0CF-A004-4E4F-9A6A-C37E9F38193A}
|
||||||
|
{8BC5293F-780B-425E-B90F-9695E917CADA} = {C68DA0CF-A004-4E4F-9A6A-C37E9F38193A}
|
||||||
|
{3E08171E-04D1-49BC-9DD3-480E2E89B768} = {CFE60455-2706-4A43-B724-B3D6F4CFB003}
|
||||||
|
{A0CE3D20-DC5C-4449-9562-CDE3339D50E4} = {CFE60455-2706-4A43-B724-B3D6F4CFB003}
|
||||||
|
{BA13C7E2-FAA6-4E79-A92B-803D0E0464BD} = {CFE60455-2706-4A43-B724-B3D6F4CFB003}
|
||||||
|
{BF18A079-9E23-454C-BA9D-B339B854A101} = {CFE60455-2706-4A43-B724-B3D6F4CFB003}
|
||||||
|
{C16F3302-2E53-4CEF-958E-37EC3308D5EF} = {CFE60455-2706-4A43-B724-B3D6F4CFB003}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {DFA659EE-FE78-4BD9-888B-78984354093E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Magick/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tesseract/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using Process.Interface;
|
||||||
|
using Process.Interface.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Process.Abstract.Configuration;
|
||||||
|
|
||||||
|
internal class AnonymousProcessor<TInput, TOutput> : Processor<TInput, TOutput>
|
||||||
|
{
|
||||||
|
private readonly Func<IEnumerable<TInput>, IEnumerable<TOutput>> _func;
|
||||||
|
|
||||||
|
public AnonymousProcessor(Func<IEnumerable<TInput>, IEnumerable<TOutput>> func)
|
||||||
|
{
|
||||||
|
_func = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Overrides of Processor<TInput,TOutput>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<TOutput> Process(IEnumerable<TInput> inputs)
|
||||||
|
{
|
||||||
|
return _func.Invoke(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Basic implementation for <see cref="ProcessorChainConfiguration{TInput, TOutput}"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInput"><see cref="IProcessor{TInput,TOutput}"/> input type</typeparam>
|
||||||
|
/// <typeparam name="TOutput"><see cref="IProcessor{TInput,TOutput}"/> output type</typeparam>
|
||||||
|
public class ProcessorChainConfiguration<TInput, TOutput>
|
||||||
|
: ProcessorChain<TInput, TOutput>, IProcessorChainConfiguration<TInput, TOutput>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||||
|
IProcessor<TInput, T> processor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Processors.Add(processor);
|
||||||
|
return new ProcessorChainConfiguration<T, TOutput, TInput, TOutput>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Use<T>(new AnonymousProcessor<TInput, T>(processorFunc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||||
|
IProcessor<TInput, TOutput> processor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Processors.Add(processor);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Complete(new AnonymousProcessor<TInput, TOutput>(processorFunc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Basic implementation for <see cref="ProcessorChainConfiguration{TInput, TOutput}"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInput"><see cref="IProcessor{TInput,TOutput}"/> input type</typeparam>
|
||||||
|
/// <typeparam name="TOutput"><see cref="IProcessor{TInput,TOutput}"/> output type</typeparam>
|
||||||
|
/// <typeparam name="TInChain">Input type of the base <see cref="IProcessorChainConfiguration{TInput,TOutput}"/></typeparam>
|
||||||
|
/// <typeparam name="TOutChain">Output type of the base <see cref="IProcessorChainConfiguration{TInput,TOutput}"/></typeparam>
|
||||||
|
public class ProcessorChainConfiguration<TInput, TOutput, TInChain, TOutChain>
|
||||||
|
: IProcessorChainConfiguration<TInput, TOutput, TInChain, TOutChain>
|
||||||
|
{
|
||||||
|
private readonly IProcessorChain<TInChain, TOutChain> _chain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chain">Parent <see cref="IProcessorChain{TInput,TOutput}"/></param>
|
||||||
|
internal ProcessorChainConfiguration(IProcessorChain<TInChain, TOutChain> chain)
|
||||||
|
{
|
||||||
|
_chain = chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IProcessorStep<out TInput,TOutput>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||||
|
IProcessor<TInput, T> processor
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_chain.Processors.Add(processor);
|
||||||
|
return new ProcessorChainConfiguration<T, TOutput, TInChain, TOutChain>(_chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Use(new AnonymousProcessor<TInput, T>(processorFunc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChain<TInChain, TOutChain> Complete(IProcessor<TInput, TOutput> processor)
|
||||||
|
{
|
||||||
|
_chain.Processors.Add(processor);
|
||||||
|
return _chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IProcessorChain<TInChain, TOutChain> Complete(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Complete(new AnonymousProcessor<TInput, TOutput>(processorFunc));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Process.Interface\Process.Interface.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Process.Interface;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Process.Abstract;
|
||||||
|
|
||||||
|
public abstract class Processor<TInput, TOutput> : IProcessor<TInput, TOutput>
|
||||||
|
{
|
||||||
|
#region Implementation of IProcessor<in TValue,out TValue>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract IEnumerable<TOutput> Process(IEnumerable<TInput> inputs);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IProcessor
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IProcessor.Process" />
|
||||||
|
public IEnumerable Process(IEnumerable inputs)
|
||||||
|
{
|
||||||
|
return Process(inputs.Cast<TInput>());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Process.Interface;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Process.Abstract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Basic implementation for <see cref="IProcessorChain{TInput, TOutput}"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInput"><see cref="IProcessor{TInput,TOutput}"/> input type</typeparam>
|
||||||
|
/// <typeparam name="TOutput"><see cref="IProcessor{TInput,TOutput}"/> output type</typeparam>
|
||||||
|
public class ProcessorChain<TInput, TOutput>
|
||||||
|
: Processor<TInput, TOutput>, IProcessorChain<TInput, TOutput>
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICollection<IProcessor> Processors { get; } = new List<IProcessor>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override IEnumerable<TOutput> Process(IEnumerable<TInput> inputs)
|
||||||
|
{
|
||||||
|
return Processors
|
||||||
|
.Aggregate(
|
||||||
|
(IEnumerable)inputs,
|
||||||
|
(current, processor) => processor.Process(current)
|
||||||
|
)
|
||||||
|
.Cast<TOutput>()
|
||||||
|
.Where(k => k is not null)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Ocr.Process
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Process.Interface.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration interface for <see cref="IProcessorChain"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInput"><see cref="IProcessor{TInput,TOutput}"/> input type</typeparam>
|
||||||
|
/// <typeparam name="TOutput"><see cref="IProcessor{TInput,TOutput}"/> output type</typeparam>
|
||||||
|
public interface IProcessorChainConfiguration<TInput, TOutput> : IProcessorChain<TInput, TOutput>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add the specified <paramref name="processor"/> to <see cref="IProcessor{TInput,TOutput}"/> chain
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="processor">The <see cref="IProcessor{TInput,TOutput}"/> to add to the chain</param>
|
||||||
|
/// <returns>The <see cref="IProcessorChainConfiguration{TInput,TOutput}"/> for fluent chaining of method calls</returns>
|
||||||
|
IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||||
|
IProcessor<TInput, T> processor
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Use{T}(IProcessor{TInput,T})"/>
|
||||||
|
IProcessorChainConfiguration<T, TOutput, TInput, TOutput> Use<T>(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the configuration process and converts the
|
||||||
|
/// <see cref="IProcessorChainConfiguration{TInput,TOutput}"/>
|
||||||
|
/// to a <see cref="IProcessorChain"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="processor">The <see cref="IProcessor{TInput,TOutput}"/> to add to the chain</param>
|
||||||
|
/// <returns>The configured <see cref="IProcessorChain{TInput,TOutput}"/></returns>
|
||||||
|
IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||||
|
IProcessor<TInput, TOutput> processor
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Complete(IProcessor{TInput,TOutput})"/>
|
||||||
|
IProcessorChainConfiguration<TInput, TOutput> Complete(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for configuring sub-steps of <see cref="IProcessorChain{TInput,TOutput}"/>s
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInput"><see cref="IProcessor{TInput,TOutput}"/> input type</typeparam>
|
||||||
|
/// <typeparam name="TOutput"><see cref="IProcessor{TInput,TOutput}"/> output type</typeparam>
|
||||||
|
/// <typeparam name="TInChain">Input type of the base <see cref="IProcessorChainConfiguration{TInput,TOutput}"/></typeparam>
|
||||||
|
/// <typeparam name="TOutChain">Output type of the base <see cref="IProcessorChainConfiguration{TInput,TOutput}"/></typeparam>
|
||||||
|
public interface IProcessorChainConfiguration<TInput, TOutput, TInChain, TOutChain>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add the specified <paramref name="processor"/> to <see cref="IProcessor{TInput,TOutput}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="processor">The <see cref="IProcessor{TInput,TOutput}"/> to add to the chain</param>
|
||||||
|
/// <returns>The <see cref="IProcessorChainConfiguration{TInput,TOutput, TInChain, TOutChain}"/> for fluent chaining of method calls</returns>
|
||||||
|
IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||||
|
IProcessor<TInput, T> processor
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Use{T}(IProcessor{TInput,T})"/>
|
||||||
|
IProcessorChainConfiguration<T, TOutput, TInChain, TOutChain> Use<T>(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<T>> processorFunc
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ends the configuration process and returns the base <see cref="IProcessorChainConfiguration{TInput,TOutput}"/>
|
||||||
|
/// to a <see cref="IProcessorChain"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="processor">The <see cref="IProcessor{TInput,TOutput}"/> to add to the chain</param>
|
||||||
|
/// <returns>The configured <see cref="IProcessorChain{TInput,TOutput}"/></returns>
|
||||||
|
IProcessorChain<TInChain, TOutChain> Complete(
|
||||||
|
IProcessor<TInput, TOutput> processor
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Complete(IProcessor{TInput,TOutput})"/>
|
||||||
|
IProcessorChain<TInChain, TOutChain> Complete(
|
||||||
|
Func<IEnumerable<TInput>, IEnumerable<TOutput>> processorFunc
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Process.Interface;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for <see cref="IProcessor"/>s, which
|
||||||
|
/// are able to manipulate input data and generate an output
|
||||||
|
/// </summary>
|
||||||
|
public interface IProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an <seealso cref="IEnumerable"/>,
|
||||||
|
/// containing the manipulated data after this processing step
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The processed data</returns>
|
||||||
|
IEnumerable Process(IEnumerable inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for <see cref="IProcessor"/>s, which
|
||||||
|
/// are able to manipulate <typeparamref name="TInput"/> data and generate an <typeparamref name="TOutput"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TInput">The data type to input</typeparam>
|
||||||
|
/// <typeparam name="TOutput">The resulting data type</typeparam>
|
||||||
|
public interface IProcessor<in TInput, out TOutput> : IProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an <seealso cref="IEnumerable{T}"/>,
|
||||||
|
/// containing the manipulated <typeparamref name="TInput"/> data after this processing step
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The processed <typeparamref name="TOutput"/> data</returns>
|
||||||
|
IEnumerable<TOutput> Process(IEnumerable<TInput> inputs);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IProcessor.Process(IEnumerable)"/>
|
||||||
|
new IEnumerable Process(IEnumerable inputs)
|
||||||
|
{
|
||||||
|
return Process((IEnumerable<TInput>)inputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Process.Interface;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="IProcessor"/>, allows to process values through many sub-<see cref="IProcessor"/>s
|
||||||
|
/// </summary>
|
||||||
|
public interface IProcessorChain : IProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Collection containing the applicable <see cref="IProcessor"/>s for this <see cref="IProcessorChain"/>
|
||||||
|
/// </summary>
|
||||||
|
ICollection<IProcessor> Processors { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IProcessorChain" />
|
||||||
|
public interface IProcessorChain<in TInput, out TOutput>
|
||||||
|
: IProcessorChain, IProcessor<TInput, TOutput>
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<IncludeSymbols>True</IncludeSymbols>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Ocr.Process
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.33424.131
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Interface", "Process.Interface\Process.Interface.csproj", "{5A0B5F3D-23CA-4C02-8802-401363556875}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.Abstract", "Process.Abstract\Process.Abstract.csproj", "{6D07940F-C2B0-4B8B-99FF-F20E190D408B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{5A0B5F3D-23CA-4C02-8802-401363556875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5A0B5F3D-23CA-4C02-8802-401363556875}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5A0B5F3D-23CA-4C02-8802-401363556875}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5A0B5F3D-23CA-4C02-8802-401363556875}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6D07940F-C2B0-4B8B-99FF-F20E190D408B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6D07940F-C2B0-4B8B-99FF-F20E190D408B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6D07940F-C2B0-4B8B-99FF-F20E190D408B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6D07940F-C2B0-4B8B-99FF-F20E190D408B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {DFA659EE-FE78-4BD9-888B-78984354093E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||