Compare commits

..

10 commits

Author SHA1 Message Date
f672894c3e Update project metadata and add test data
Some checks failed
Build And Publish AIFotoONLUS.Core / build (push) Failing after 1m16s
Build And Publish AIFotoONLUS.Core / publish (push) Has been skipped
- Changed the repository URL in the AIFotoONLUS.Core project file to point to the new Forgejo instance.
- Removed the inclusion of the XML documentation file in the NuGet package.
- Added a new CSV file containing test data for image processing, including filenames and associated text values.
2026-05-09 12:09:31 +02:00
dc4ebdaf42 Add full XML docs and NuGet IntelliSense support
Comprehensive XML documentation added to all public types, methods, and properties in AIFotoONLUS.Core. Project updated to generate and pack XML docs for NuGet consumers. README rewritten for clarity. Improves developer experience with rich IntelliSense and API docs.
2026-02-15 23:37:08 +01:00
986cc5f8ab Fixed Minversion 2026-02-15 21:39:14 +01:00
1a9491617a Removed gitversion, added minversion 2026-02-15 21:30:01 +01:00
daead29d31 Refactor GitVersion configuration in project file to prioritize safe version composition from MSBuild properties 2026-02-15 19:45:46 +01:00
f6a1aae622 Refactor publish_nuget stage in CI pipeline to streamline dotnet restore and pack commands 2026-02-15 19:35:41 +01:00
5aae5f6486 Add GeneratePackageOnBuild=false to build script in CI pipeline 2026-02-15 19:30:21 +01:00
97eb431b45 Enhance GitVersion integration in CI pipeline with additional diagnostics and JSON output 2026-02-15 19:24:21 +01:00
a90da31e53 Refactor NuGet package versioning logic to use MajorMinorPatch, EscapedBranchName, and PreReleaseNumber 2026-02-15 19:13:00 +01:00
f9a652f5cc Fix GitVersion command to handle errors and improve package versioning logic 2026-02-15 19:02:32 +01:00
13 changed files with 784 additions and 47 deletions

View file

@ -0,0 +1,124 @@
name: Build And Publish AIFotoONLUS.Core
on:
push:
branches:
- master
- develop
tags:
- '*'
workflow_dispatch:
env:
DOTNET_VERSION: 10.0.x
PROJECT_PATH: src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj
PACKAGE_OUTPUT_DIR: artifacts/nuget
PACKAGE_ARTIFACT_NAME: aifotoonlus-core-nuget
NUGET_SOURCE_NAME: forgejo-aifotoonlus
NUGET_SOURCE_URL: ${{ vars.AIFOTOONLUS_NUGET_SOURCE_URL || format('{0}/api/packages/{1}/nuget/index.json', github.server_url, vars.AIFOTOONLUS_PACKAGE_OWNER || github.repository_owner) }}
jobs:
build:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore
run: dotnet restore "${{ env.PROJECT_PATH }}"
- name: Build
run: dotnet build "${{ env.PROJECT_PATH }}" --configuration Release --no-restore /p:GeneratePackageOnBuild=false
- name: Pack
shell: bash
run: |
set -eu
mkdir -p "${{ env.PACKAGE_OUTPUT_DIR }}"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
package_version="${GITHUB_REF_NAME#v}"
echo "Packing tag version ${package_version}"
dotnet pack "${{ env.PROJECT_PATH }}" \
--configuration Release \
--output "${{ env.PACKAGE_OUTPUT_DIR }}" \
--no-build \
/p:PackageVersion="${package_version}"
else
echo "Packing with project version or MinVer-derived version"
dotnet pack "${{ env.PROJECT_PATH }}" \
--configuration Release \
--output "${{ env.PACKAGE_OUTPUT_DIR }}" \
--no-build
fi
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.PACKAGE_ARTIFACT_NAME }}
path: ${{ env.PACKAGE_OUTPUT_DIR }}/*.nupkg
if-no-files-found: error
publish:
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
needs: build
runs-on: docker
env:
FORGEJO_PACKAGE_USERNAME: ${{ secrets.FORGEJO_PACKAGE_USERNAME }}
FORGEJO_PACKAGE_TOKEN: ${{ secrets.FORGEJO_PACKAGE_TOKEN }}
steps:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Download package artifact
uses: actions/download-artifact@v4
with:
name: ${{ env.PACKAGE_ARTIFACT_NAME }}
path: ${{ env.PACKAGE_OUTPUT_DIR }}
- name: Validate publish secrets
shell: bash
run: |
set -eu
if [ -z "${FORGEJO_PACKAGE_USERNAME}" ]; then
echo "secrets.FORGEJO_PACKAGE_USERNAME is required"
exit 1
fi
if [ -z "${FORGEJO_PACKAGE_TOKEN}" ]; then
echo "secrets.FORGEJO_PACKAGE_TOKEN is required"
exit 1
fi
- name: Configure Forgejo NuGet source
run: |
dotnet nuget add source "${{ env.NUGET_SOURCE_URL }}" \
--name "${{ env.NUGET_SOURCE_NAME }}" \
--username "${FORGEJO_PACKAGE_USERNAME}" \
--password "${FORGEJO_PACKAGE_TOKEN}" \
--store-password-in-clear-text
- name: Publish package to Forgejo NuGet
shell: bash
run: |
set -eu
shopt -s nullglob
packages=("${{ env.PACKAGE_OUTPUT_DIR }}"/*.nupkg)
if [ "${#packages[@]}" -eq 0 ]; then
echo "No NuGet packages found in ${{ env.PACKAGE_OUTPUT_DIR }}"
exit 1
fi
dotnet nuget push "${{ env.PACKAGE_OUTPUT_DIR }}"/*.nupkg \
--source "${{ env.NUGET_SOURCE_NAME }}" \
--skip-duplicate

View file

@ -2,6 +2,16 @@ stages:
- build
- publish
# Only create pipelines automatically when a Git tag is pushed.
# Otherwise the pipeline must be started manually (pipeline "Run" / dispatch equivalent).
workflow:
rules:
- if: '$CI_COMMIT_TAG'
when: always
- if: '$CI_PIPELINE_SOURCE == "web"'
when: always
- when: never
variables:
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
DOTNET_NOLOGO: "true"
@ -16,7 +26,7 @@ build:
stage: build
script:
- dotnet restore src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj
- dotnet build src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj --configuration Release --no-restore
- dotnet build src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj --configuration Release --no-restore /p:GeneratePackageOnBuild=false
artifacts:
paths:
- src/AIFotoONLUS.Core/bin/**
@ -25,22 +35,27 @@ build:
publish_nuget:
stage: publish
image: mcr.microsoft.com/dotnet/sdk:10.0
dependencies:
- build
variables:
NUGET_SOURCE: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/nuget/index.json"
script: |
dotnet restore
dotnet restore src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj
git fetch --prune --unshallow || true
git fetch origin +refs/heads/*:refs/remotes/origin/* || true
git branch --show-current || true
- dotnet tool install --global GitVersion.Tool --version 6.5.1 || true
export PATH="$PATH:~/.dotnet/tools"
GITVER=$(gitversion /showvariable NuGetVersionV2)
echo "Raw version: $GITVER"
PACKAGE_VERSION=$(echo "$GITVER" | sed 's/[{}]//g' | sed 's/[^0-9A-Za-z.-]//g')
echo "Package version: $PACKAGE_VERSION"
dotnet pack src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj -c Release -o nuget /p:PackageVersion="$PACKAGE_VERSION"
# If build was triggered by a Git tag, use that tag as the package version (strip leading 'v').
# Otherwise rely on MinVer inside the project to infer a semantic version from git history.
if [ -n "$CI_COMMIT_TAG" ]; then
PACKAGE_VERSION="${CI_COMMIT_TAG#v}"
echo "Using tag version: $PACKAGE_VERSION"
dotnet pack src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj -c Release -o nuget --no-build /p:PackageVersion="$PACKAGE_VERSION" /p:EnableWindowsTargeting=true
else
echo "No tag detected; using MinVer to infer version at pack time"
dotnet pack src/AIFotoONLUS.Core/AIFotoONLUS.Core.csproj -c Release -o nuget --no-build /p:EnableWindowsTargeting=true
fi
dotnet nuget push "nuget/*.nupkg" --source "$NUGET_SOURCE" --api-key "$CI_JOB_TOKEN" --skip-duplicate
only:
- main
- master
- tags
# Pipeline creation is controlled by the top-level `workflow` rules.
# This job will run when the pipeline is created for a tag or when manually started.

View file

@ -1,20 +0,0 @@
mode: ContinuousDelivery
branches:
main:
regex: ^main$
increment: Minor
track-merge-target: false
master:
regex: ^master$
increment: Minor
track-merge-target: false
feature:
regex: ^(?:feat(?:ure)?|feature)[/\\-]
increment: Minor
source-branches: ["main", "master"]
hotfix:
regex: ^hotfix[/\\-]
increment: Patch
ignore:
sha: []
commit-message-incrementing: Enabled

65
README.md Normal file
View file

@ -0,0 +1,65 @@
# AIFotoONLUS Number Recognition Library
This library provides a small, focused engine to detect and recognize numeric
text (digits) in images using Darknet (YOLO) models via OpenCvSharp's DNN API.
It is suitable for batch processing folders of images or individual files.
Features
- Detection network (Darknet/Yolo) to find candidate text regions.
- Recognition network (Darknet/Yolo) to identify digits inside detected crops.
- Single-file and directory-level processing APIs.
- Parallel processing with per-thread network instances for throughput.
- Diagnostic helpers to dump network output shapes and optionally save crop images.
Basic usage
1. Create a `ModelConfiguration` instance that points to your Darknet `.cfg`
and `.weights` files for both detection and recognition networks, configure
confidence and NMS thresholds and provide a list of number class labels.
2. Create an instance of `NumberRecognitionEngine`:
```csharp
using var engine = new NumberRecognitionEngine(modelConfig, logger: null);
```
3. Process a single image:
```csharp
var result = engine.ProcessImage("/path/to/image.jpg");
Console.WriteLine(result.Text);
```
4. Process a directory (parallelized):
```csharp
var results = await engine.ProcessDirectoryAsync("/path/to/images", recursive: false);
foreach (var r in results) Console.WriteLine($"{r.FileName}: {r.Text}");
```
Configuration notes
- `ModelConfiguration` controls model file paths, input sizes, thresholds and
whether to save cropped images for diagnostics. Make sure the paths are
accessible to the process and the model files match the expected network
architectures.
- The engine expects detection network outputs in the YOLO-style layout:
`[cx, cy, w, h, objectness, class1, class2, ...]`.
Threading & diagnostics
- For directory/batch processing the engine creates per-thread Net instances
so OpenCV forward calls can run concurrently. It also contains fallback
logic that will perform processing with shared nets under a lock if needed.
- When `EnableCropSaving` is enabled in configuration, each recognized crop is
saved to `logs/crops` with a timestamp and optional context label to aid
debugging false positives/negatives.
Troubleshooting
- If the engine returns no detections, verify the model files are correct and
compatible with the expected output layout. Use
`ProcessFileWithDiagnostics` to inspect output layer shapes.
License & Notes
This project is provided as-is. See repository for licensing information and
for the model files distribution terms (models are usually not redistributed
with code and must be obtained separately).

27
gitversion.json Normal file
View file

@ -0,0 +1,27 @@
{
"AssemblySemFileVer": "0.1.0.0",
"AssemblySemVer": "0.1.0.0",
"BranchName": "master",
"BuildMetaData": null,
"CommitDate": "2026-02-15",
"CommitsSinceVersionSource": 11,
"EscapedBranchName": "master",
"FullBuildMetaData": "Branch.master.Sha.a90da31e531332a4cf0bafe604f89d0e14f3395a",
"FullSemVer": "0.1.0-{BranchName}.11",
"InformationalVersion": "0.1.0-{BranchName}.11+Branch.master.Sha.a90da31e531332a4cf0bafe604f89d0e14f3395a",
"Major": 0,
"MajorMinorPatch": "0.1.0",
"Minor": 1,
"Patch": 0,
"PreReleaseLabel": "{BranchName}",
"PreReleaseLabelWithDash": "-{BranchName}",
"PreReleaseNumber": 11,
"PreReleaseTag": "{BranchName}.11",
"PreReleaseTagWithDash": "-{BranchName}.11",
"SemVer": "0.1.0-{BranchName}.11",
"Sha": "a90da31e531332a4cf0bafe604f89d0e14f3395a",
"ShortSha": "a90da31",
"UncommittedChanges": 7,
"VersionSourceSha": "",
"WeightedPreReleaseNumber": 11
}

View file

@ -10,8 +10,14 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="6.1.1" />
<!-- MinVer for tag-based semantic versioning -->
<PackageReference Include="MinVer" Version="3.1.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AIFotoONLUS.Core\AIFotoONLUS.Core.csproj" />
</ItemGroup>
<PropertyGroup>
<!-- Fallback version when no tag is present -->
<Version>0.1.0</Version>
</PropertyGroup>
</Project>

View file

@ -3,6 +3,10 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Generate XML documentation file for the public API -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- Ensure the documentation file path is predictable so it can be packed -->
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<!-- NuGet package metadata -->
@ -10,13 +14,10 @@
<Authors>Maddo</Authors>
<Company>Maddo</Company>
<Description>Core library for AIFotoONLUS image processing and recognition.</Description>
<RepositoryUrl>https://gitlab.com/MaddoScientisto/aifotoonlus</RepositoryUrl>
<!-- Use GitVersion MSBuild properties to set package version during CI/build. Provide fallback if variable is not defined.
Sanitize the GitVersion output to remove characters invalid for NuGet (e.g., braces) -->
<GitVersionRaw>$(GitVersion_NuGetVersionV2)</GitVersionRaw>
<GitVersionSanitized>$([System.Text.RegularExpressions.Regex]::Replace('$(GitVersionRaw)','[{}]',''))</GitVersionSanitized>
<Version Condition="'$(GitVersionSanitized)' != ''">$(GitVersionSanitized)</Version>
<Version Condition="'$(GitVersionSanitized)' == ''">0.1.0</Version>
<RepositoryUrl>https://forgejo.maddoscientisto.net/maddo/AIFotoONLUS</RepositoryUrl>
<!-- Versioning: use MinVer to infer semantic versions from Git tags. When no tag is present,
projects will fall back to the default below. -->
<Version>0.1.0</Version>
<PackageReleaseNotes>See Git history for release notes.</PackageReleaseNotes>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>false</IncludeSymbols>
@ -25,8 +26,8 @@
<PackageReference Include="OpenCvSharp4" Version="4.13.0.20260214" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.13.0.20260214" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<!-- GitVersion MSBuild integration for automatic semantic versioning -->
<PackageReference Include="GitVersion.MsBuild" Version="6.5.1" PrivateAssets="all" />
<!-- Use MinVer to infer versions from Git tags (applies at pack/build time) -->
<PackageReference Include="MinVer" Version="3.1.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
</ItemGroup>

View file

@ -0,0 +1,325 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>AIFotoONLUS.Core</name>
</assembly>
<members>
<member name="T:AIFotoONLUS.Core.DetectedRegion">
<summary>
Represents a detected text region produced by the detection network.
</summary>
<param name="BoundingBox">Bounding rectangle of the detection in image coordinates.</param>
<param name="Confidence">Combined confidence score for the detection (objectness * class probability).</param>
<param name="ClassId">Class index predicted by the network (index into <see cref="P:AIFotoONLUS.Core.ModelConfiguration.NumberClasses"/>).</param>
<param name="CenterX">Center X coordinate (in pixels) of the bounding box, used to order detections left-to-right.</param>
</member>
<member name="M:AIFotoONLUS.Core.DetectedRegion.#ctor(OpenCvSharp.Rect,System.Single,System.Int32,System.Double)">
<summary>
Represents a detected text region produced by the detection network.
</summary>
<param name="BoundingBox">Bounding rectangle of the detection in image coordinates.</param>
<param name="Confidence">Combined confidence score for the detection (objectness * class probability).</param>
<param name="ClassId">Class index predicted by the network (index into <see cref="P:AIFotoONLUS.Core.ModelConfiguration.NumberClasses"/>).</param>
<param name="CenterX">Center X coordinate (in pixels) of the bounding box, used to order detections left-to-right.</param>
</member>
<member name="P:AIFotoONLUS.Core.DetectedRegion.BoundingBox">
<summary>Bounding rectangle of the detection in image coordinates.</summary>
</member>
<member name="P:AIFotoONLUS.Core.DetectedRegion.Confidence">
<summary>Combined confidence score for the detection (objectness * class probability).</summary>
</member>
<member name="P:AIFotoONLUS.Core.DetectedRegion.ClassId">
<summary>Class index predicted by the network (index into <see cref="P:AIFotoONLUS.Core.ModelConfiguration.NumberClasses"/>).</summary>
</member>
<member name="P:AIFotoONLUS.Core.DetectedRegion.CenterX">
<summary>Center X coordinate (in pixels) of the bounding box, used to order detections left-to-right.</summary>
</member>
<member name="T:AIFotoONLUS.Core.RecognitionResult">
<summary>
Represents the result of recognizing a single region: recognized text,
its bounding box and confidence.
</summary>
<param name="Text">Recognized text for the region (usually a sequence of digits).</param>
<param name="BoundingBox">Bounding rectangle of the recognition result.</param>
<param name="Confidence">Confidence score associated with the recognition.</param>
</member>
<member name="M:AIFotoONLUS.Core.RecognitionResult.#ctor(System.String,OpenCvSharp.Rect,System.Double)">
<summary>
Represents the result of recognizing a single region: recognized text,
its bounding box and confidence.
</summary>
<param name="Text">Recognized text for the region (usually a sequence of digits).</param>
<param name="BoundingBox">Bounding rectangle of the recognition result.</param>
<param name="Confidence">Confidence score associated with the recognition.</param>
</member>
<member name="P:AIFotoONLUS.Core.RecognitionResult.Text">
<summary>Recognized text for the region (usually a sequence of digits).</summary>
</member>
<member name="P:AIFotoONLUS.Core.RecognitionResult.BoundingBox">
<summary>Bounding rectangle of the recognition result.</summary>
</member>
<member name="P:AIFotoONLUS.Core.RecognitionResult.Confidence">
<summary>Confidence score associated with the recognition.</summary>
</member>
<member name="T:AIFotoONLUS.Core.ImageResult">
<summary>
Aggregated result for a processed image.
</summary>
<param name="FileName">Name of the image file.</param>
<param name="Text">Comma-separated recognized texts found in the image (may be empty).</param>
<param name="FilePath">Full path to the processed image file.</param>
</member>
<member name="M:AIFotoONLUS.Core.ImageResult.#ctor(System.String,System.String,System.String)">
<summary>
Aggregated result for a processed image.
</summary>
<param name="FileName">Name of the image file.</param>
<param name="Text">Comma-separated recognized texts found in the image (may be empty).</param>
<param name="FilePath">Full path to the processed image file.</param>
</member>
<member name="P:AIFotoONLUS.Core.ImageResult.FileName">
<summary>Name of the image file.</summary>
</member>
<member name="P:AIFotoONLUS.Core.ImageResult.Text">
<summary>Comma-separated recognized texts found in the image (may be empty).</summary>
</member>
<member name="P:AIFotoONLUS.Core.ImageResult.FilePath">
<summary>Full path to the processed image file.</summary>
</member>
<member name="T:AIFotoONLUS.Core.ModelConfiguration">
<summary>
Configuration options that control model file locations, input sizes
and runtime thresholds used by <see cref="T:AIFotoONLUS.Core.NumberRecognitionEngine"/>.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.DetectionCfg">
<summary>
Path to the Darknet configuration (.cfg) file for the detection network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.DetectionWeights">
<summary>
Path to the Darknet weights (.weights) file for the detection network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.RecognitionCfg">
<summary>
Path to the Darknet configuration (.cfg) file for the recognition network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.RecognitionWeights">
<summary>
Path to the Darknet weights (.weights) file for the recognition network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.ConfidenceThreshold">
<summary>
Confidence threshold used to filter out low-probability detections.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.NmsThreshold">
<summary>
Non-maximum suppression (NMS) IoU threshold used to remove overlapping
detection boxes.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.DetectionInputSize">
<summary>
Input size used when preparing the blob for the detection network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.RecognitionInputSize">
<summary>
Input size used when preparing the blob for the recognition network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.NumberClasses">
<summary>
Labels representing digit classes in the recognition model. The order
must match the class ordering used by the trained recognition network.
</summary>
</member>
<member name="P:AIFotoONLUS.Core.ModelConfiguration.EnableCropSaving">
<summary>
When enabled, recognition crops will be saved to disk under
"logs/crops" for diagnostic inspection. Disabled by default.
</summary>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.#ctor(AIFotoONLUS.Core.ModelConfiguration)">
<summary>
Create a new instance of <see cref="T:AIFotoONLUS.Core.NumberRecognitionEngine"/> using the
provided <see cref="T:AIFotoONLUS.Core.ModelConfiguration"/>. The constructor loads the
detection and recognition Darknet model files and prepares the OpenCV
DNN nets for CPU inference.
</summary>
<param name="cfg">Model configuration containing file paths, thresholds
and other options. Must not be <c>null</c>.</param>
<remarks>
This constructor will throw <see cref="T:System.IO.FileNotFoundException"/> when
any of the expected model files are missing. For logging purposes an
overload accepting an <see cref="T:Microsoft.Extensions.Logging.ILogger"/> is available.
</remarks>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.#ctor(AIFotoONLUS.Core.ModelConfiguration,Microsoft.Extensions.Logging.ILogger)">
<summary>
Create a new instance of <see cref="T:AIFotoONLUS.Core.NumberRecognitionEngine"/> with an
optional <see cref="T:Microsoft.Extensions.Logging.ILogger"/>. The logger will receive diagnostic
messages and errors produced by the engine during processing.
</summary>
<param name="cfg">Model configuration containing file paths and
runtime thresholds.</param>
<param name="logger">Optional logger for diagnostic messages.
May be <c>null</c>.</param>
<exception cref="T:System.ArgumentNullException">Thrown when <paramref name="cfg"/>
is <c>null</c>.</exception>
<exception cref="T:System.IO.FileNotFoundException">Thrown when one of the model
files referenced by <paramref name="cfg"/> does not exist.</exception>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.DetectTextRegions(OpenCvSharp.Mat)">
<summary>
Detect text regions in the supplied image using the detection network.
</summary>
<param name="image">Input image as an OpenCvSharp <see cref="T:OpenCvSharp.Mat"/>.
Must not be <c>null</c>.</param>
<returns>An enumerable of <see cref="T:AIFotoONLUS.Core.DetectedRegion"/> containing the
bounding boxes, confidence and class information for each detected
region. The results are already filtered with the configured
confidence and NMS thresholds.</returns>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.RecognizeDigits(OpenCvSharp.Mat,System.String)">
<summary>
Recognize digits inside a cropped image region using the recognition
network. The method runs the recognition network and returns the
concatenated sequence of recognized digit labels ordered left-to-right.
</summary>
<param name="croppedImage">Cropped image containing digits as
<see cref="T:OpenCvSharp.Mat"/>. Must not be <c>null</c>.</param>
<param name="context">Optional context string used for diagnostics
(e.g. when saving crop image files).</param>
<returns>A string containing recognized digits in left-to-right order.
Returns an empty string when no digits are recognized above the
configured confidence threshold.</returns>
</member>
<member name="T:AIFotoONLUS.Core.NumberRecognitionEngine.DetectionOutput">
<summary>
Small DTO that describes the name and shape of a detection network
forward output used for diagnostics.
</summary>
<param name="Name">Layer/output name.</param>
<param name="Rows">Number of rows in the output Mat.</param>
<param name="Cols">Number of columns in the output Mat.</param>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.DetectionOutput.#ctor(System.String,System.Int32,System.Int32)">
<summary>
Small DTO that describes the name and shape of a detection network
forward output used for diagnostics.
</summary>
<param name="Name">Layer/output name.</param>
<param name="Rows">Number of rows in the output Mat.</param>
<param name="Cols">Number of columns in the output Mat.</param>
</member>
<member name="P:AIFotoONLUS.Core.NumberRecognitionEngine.DetectionOutput.Name">
<summary>Layer/output name.</summary>
</member>
<member name="P:AIFotoONLUS.Core.NumberRecognitionEngine.DetectionOutput.Rows">
<summary>Number of rows in the output Mat.</summary>
</member>
<member name="P:AIFotoONLUS.Core.NumberRecognitionEngine.DetectionOutput.Cols">
<summary>Number of columns in the output Mat.</summary>
</member>
<member name="T:AIFotoONLUS.Core.NumberRecognitionEngine.DiagnosticResult">
<summary>
Result returned by <see cref="M:AIFotoONLUS.Core.NumberRecognitionEngine.ProcessFileWithDiagnostics(System.String)"/>, contains
the recognized text result and an array describing detection network
forward outputs (shapes and names) which are useful for debugging
model output layout mismatches.
</summary>
<param name="Result">Recognition result for the processed image.</param>
<param name="DetectionOutputs">Array describing detection net outputs.</param>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.DiagnosticResult.#ctor(AIFotoONLUS.Core.ImageResult,AIFotoONLUS.Core.NumberRecognitionEngine.DetectionOutput[])">
<summary>
Result returned by <see cref="M:AIFotoONLUS.Core.NumberRecognitionEngine.ProcessFileWithDiagnostics(System.String)"/>, contains
the recognized text result and an array describing detection network
forward outputs (shapes and names) which are useful for debugging
model output layout mismatches.
</summary>
<param name="Result">Recognition result for the processed image.</param>
<param name="DetectionOutputs">Array describing detection net outputs.</param>
</member>
<member name="P:AIFotoONLUS.Core.NumberRecognitionEngine.DiagnosticResult.Result">
<summary>Recognition result for the processed image.</summary>
</member>
<member name="P:AIFotoONLUS.Core.NumberRecognitionEngine.DiagnosticResult.DetectionOutputs">
<summary>Array describing detection net outputs.</summary>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.ProcessFileWithDiagnostics(System.String)">
<summary>
Process a single image file and return the recognition result together
with detection network forward output shapes for diagnostics. This
method reads the image from disk, runs a forward pass over the
detection network to capture the raw output Mat shapes and then calls
the normal processing pipeline to return the recognized text.
</summary>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.ProcessImage(System.String)">
<summary>
Process a single image file and return the recognized text as an
<see cref="T:AIFotoONLUS.Core.ImageResult"/>. The method detects candidate text regions
and runs recognition on each crop. Multiple recognized digit sequences
are joined with a comma in the returned <see cref="P:AIFotoONLUS.Core.ImageResult.Text"/>.
</summary>
<param name="filePath">Path to an image file on disk. Supported
formats depend on OpenCV (typically JPEG, PNG, ...).</param>
<returns>An <see cref="T:AIFotoONLUS.Core.ImageResult"/> containing the file name and
recognized text (possibly empty).</returns>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.ProcessDirectory(System.String,System.Boolean)">
<summary>
Process all JPEG images in a directory and return the recognition
results. This is a blocking wrapper over <see cref="M:AIFotoONLUS.Core.NumberRecognitionEngine.ProcessDirectoryAsync(System.String,System.Boolean,System.Boolean,System.IProgress{AIFotoONLUS.Core.ProcessingStats},System.IProgress{AIFotoONLUS.Core.ImageResult},System.Threading.CancellationToken)"/>.
</summary>
<param name="directoryPath">Path to a directory containing images.</param>
<param name="skipTextNegative">If true, files whose names start with
"tn_" will be skipped (convention used to mark text-negative images).</param>
<returns>Collection of <see cref="T:AIFotoONLUS.Core.ImageResult"/> ordered by file name.</returns>
</member>
<member name="M:AIFotoONLUS.Core.NumberRecognitionEngine.RecognizeDigits(OpenCvSharp.Mat,OpenCvSharp.Dnn.Net,System.String)">
<summary>
Worker overload of <see cref="M:AIFotoONLUS.Core.NumberRecognitionEngine.RecognizeDigits(OpenCvSharp.Mat,System.String)"/> that
accepts a <see cref="T:OpenCvSharp.Dnn.Net"/> instance. This is used by the parallel
processing pipeline where each worker owns its own Net instance.
</summary>
<param name="croppedImage">Cropped region to recognize.</param>
<param name="recognitionNet">Recognition <see cref="T:OpenCvSharp.Dnn.Net"/> to execute
the forward pass with.</param>
<param name="context">Optional context string for diagnostics.</param>
<returns>Recognized digit sequence or empty string.</returns>
</member>
<member name="T:AIFotoONLUS.Core.ProcessingStats">
<summary>
Progress statistics reported during directory processing.
</summary>
<param name="TotalFiles">Total number of image files to process.</param>
<param name="ProcessedFiles">Number of files processed so far.</param>
<param name="ImagesPerSecond">Current processing throughput in images/second.</param>
</member>
<member name="M:AIFotoONLUS.Core.ProcessingStats.#ctor(System.Int32,System.Int32,System.Double)">
<summary>
Progress statistics reported during directory processing.
</summary>
<param name="TotalFiles">Total number of image files to process.</param>
<param name="ProcessedFiles">Number of files processed so far.</param>
<param name="ImagesPerSecond">Current processing throughput in images/second.</param>
</member>
<member name="P:AIFotoONLUS.Core.ProcessingStats.TotalFiles">
<summary>Total number of image files to process.</summary>
</member>
<member name="P:AIFotoONLUS.Core.ProcessingStats.ProcessedFiles">
<summary>Number of files processed so far.</summary>
</member>
<member name="P:AIFotoONLUS.Core.ProcessingStats.ImagesPerSecond">
<summary>Current processing throughput in images/second.</summary>
</member>
</members>
</doc>

View file

@ -2,7 +2,29 @@ using OpenCvSharp;
namespace AIFotoONLUS.Core
{
/// <summary>
/// Represents a detected text region produced by the detection network.
/// </summary>
/// <param name="BoundingBox">Bounding rectangle of the detection in image coordinates.</param>
/// <param name="Confidence">Combined confidence score for the detection (objectness * class probability).</param>
/// <param name="ClassId">Class index predicted by the network (index into <see cref="ModelConfiguration.NumberClasses"/>).</param>
/// <param name="CenterX">Center X coordinate (in pixels) of the bounding box, used to order detections left-to-right.</param>
public record DetectedRegion(Rect BoundingBox, float Confidence, int ClassId, double CenterX);
/// <summary>
/// Represents the result of recognizing a single region: recognized text,
/// its bounding box and confidence.
/// </summary>
/// <param name="Text">Recognized text for the region (usually a sequence of digits).</param>
/// <param name="BoundingBox">Bounding rectangle of the recognition result.</param>
/// <param name="Confidence">Confidence score associated with the recognition.</param>
public record RecognitionResult(string Text, Rect BoundingBox, double Confidence);
/// <summary>
/// Aggregated result for a processed image.
/// </summary>
/// <param name="FileName">Name of the image file.</param>
/// <param name="Text">Comma-separated recognized texts found in the image (may be empty).</param>
/// <param name="FilePath">Full path to the processed image file.</param>
public record ImageResult(string FileName, string Text, string FilePath);
}

View file

@ -2,21 +2,63 @@ using OpenCvSharp;
namespace AIFotoONLUS.Core
{
/// <summary>
/// Configuration options that control model file locations, input sizes
/// and runtime thresholds used by <see cref="NumberRecognitionEngine"/>.
/// </summary>
public class ModelConfiguration
{
/// <summary>
/// Path to the Darknet configuration (.cfg) file for the detection network.
/// </summary>
public string DetectionCfg { get; set; } = "models/detection.cfg";
/// <summary>
/// Path to the Darknet weights (.weights) file for the detection network.
/// </summary>
public string DetectionWeights { get; set; } = "models/detection.weights";
/// <summary>
/// Path to the Darknet configuration (.cfg) file for the recognition network.
/// </summary>
public string RecognitionCfg { get; set; } = "models/recognition.cfg";
/// <summary>
/// Path to the Darknet weights (.weights) file for the recognition network.
/// </summary>
public string RecognitionWeights { get; set; } = "models/recognition.weights";
/// <summary>
/// Confidence threshold used to filter out low-probability detections.
/// </summary>
public double ConfidenceThreshold { get; set; } = 0.5;
/// <summary>
/// Non-maximum suppression (NMS) IoU threshold used to remove overlapping
/// detection boxes.
/// </summary>
public double NmsThreshold { get; set; } = 0.4;
/// <summary>
/// Input size used when preparing the blob for the detection network.
/// </summary>
public Size DetectionInputSize { get; set; } = new Size(416, 416);
/// <summary>
/// Input size used when preparing the blob for the recognition network.
/// </summary>
public Size RecognitionInputSize { get; set; } = new Size(140, 120);
/// <summary>
/// Labels representing digit classes in the recognition model. The order
/// must match the class ordering used by the trained recognition network.
/// </summary>
public string[] NumberClasses { get; set; } = new[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
// When true, recognition crops will be saved to disk for diagnostics. Disabled by default.
/// <summary>
/// When enabled, recognition crops will be saved to disk under
/// "logs/crops" for diagnostic inspection. Disabled by default.
/// </summary>
public bool EnableCropSaving { get; set; } = false;
}
}

View file

@ -12,8 +12,32 @@ using System.Threading.Tasks;
namespace AIFotoONLUS.Core
{
/// <summary>
/// NumberRecognitionEngine: loads Darknet models via OpenCvSharp and
/// provides methods to detect text regions and recognize digits.
/// NumberRecognitionEngine is a high-level wrapper that loads Darknet (YOLO)
/// models through OpenCvSharp's DNN API and exposes simple synchronous and
/// asynchronous methods to detect numeric text regions in images and recognize
/// the digits contained within those regions.
///
/// Overview
/// - Loads two Darknet networks: a detection network (finds text regions)
/// and a recognition network (recognizes digits inside a cropped region).
/// - Uses OpenCvSharp (CvDnn) to create input blobs, run forward passes and
/// perform nonmaximum suppression (NMS) on detection candidates.
/// - Provides single-image and directory-level processing APIs. Directory
/// processing supports parallel workers where each worker uses its own
/// per-thread Net instances to allow concurrent forward calls.
///
/// Threading and performance notes
/// - The class constructs and owns two shared Net instances used by the
/// simple (single-threaded) APIs. When doing parallel processing the
/// implementation creates per-thread Net instances to avoid concurrent
/// calls into the same Net object. A small fallback path exists that will
/// call into the shared nets under a lock when needed.
/// - OpenCV internal threading is enabled (Cv2.SetNumThreads) when supported.
///
/// Diagnostics
/// - When enabled via the configuration, crops may be saved to disk for
/// debugging. The <see cref="ModelConfiguration"/> contains thresholds and
/// paths used by the engine.
/// </summary>
using Microsoft.Extensions.Logging;
@ -27,11 +51,37 @@ namespace AIFotoONLUS.Core
private readonly ILogger? _logger;
private bool _disposed;
/// <summary>
/// Create a new instance of <see cref="NumberRecognitionEngine"/> using the
/// provided <see cref="ModelConfiguration"/>. The constructor loads the
/// detection and recognition Darknet model files and prepares the OpenCV
/// DNN nets for CPU inference.
/// </summary>
/// <param name="cfg">Model configuration containing file paths, thresholds
/// and other options. Must not be <c>null</c>.</param>
/// <remarks>
/// This constructor will throw <see cref="FileNotFoundException"/> when
/// any of the expected model files are missing. For logging purposes an
/// overload accepting an <see cref="ILogger"/> is available.
/// </remarks>
public NumberRecognitionEngine(ModelConfiguration cfg)
: this(cfg, logger: null)
{
}
/// <summary>
/// Create a new instance of <see cref="NumberRecognitionEngine"/> with an
/// optional <see cref="ILogger"/>. The logger will receive diagnostic
/// messages and errors produced by the engine during processing.
/// </summary>
/// <param name="cfg">Model configuration containing file paths and
/// runtime thresholds.</param>
/// <param name="logger">Optional logger for diagnostic messages.
/// May be <c>null</c>.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="cfg"/>
/// is <c>null</c>.</exception>
/// <exception cref="FileNotFoundException">Thrown when one of the model
/// files referenced by <paramref name="cfg"/> does not exist.</exception>
public NumberRecognitionEngine(ModelConfiguration cfg, ILogger? logger)
{
_logger = logger;
@ -77,6 +127,15 @@ namespace AIFotoONLUS.Core
private string[] GetOutputLayerNames(Net net) => net.GetUnconnectedOutLayersNames();
/// <summary>
/// Detect text regions in the supplied image using the detection network.
/// </summary>
/// <param name="image">Input image as an OpenCvSharp <see cref="Mat"/>.
/// Must not be <c>null</c>.</param>
/// <returns>An enumerable of <see cref="DetectedRegion"/> containing the
/// bounding boxes, confidence and class information for each detected
/// region. The results are already filtered with the configured
/// confidence and NMS thresholds.</returns>
public IEnumerable<DetectedRegion> DetectTextRegions(Mat image)
{
if (image is null) throw new ArgumentNullException(nameof(image));
@ -190,6 +249,18 @@ namespace AIFotoONLUS.Core
return results;
}
/// <summary>
/// Recognize digits inside a cropped image region using the recognition
/// network. The method runs the recognition network and returns the
/// concatenated sequence of recognized digit labels ordered left-to-right.
/// </summary>
/// <param name="croppedImage">Cropped image containing digits as
/// <see cref="Mat"/>. Must not be <c>null</c>.</param>
/// <param name="context">Optional context string used for diagnostics
/// (e.g. when saving crop image files).</param>
/// <returns>A string containing recognized digits in left-to-right order.
/// Returns an empty string when no digits are recognized above the
/// configured confidence threshold.</returns>
public string RecognizeDigits(Mat croppedImage, string? context = null)
{
if (croppedImage is null) throw new ArgumentNullException(nameof(croppedImage));
@ -287,12 +358,31 @@ namespace AIFotoONLUS.Core
return string.Concat(ordered);
}
/// <summary>
/// Small DTO that describes the name and shape of a detection network
/// forward output used for diagnostics.
/// </summary>
/// <param name="Name">Layer/output name.</param>
/// <param name="Rows">Number of rows in the output Mat.</param>
/// <param name="Cols">Number of columns in the output Mat.</param>
public record DetectionOutput(string Name, int Rows, int Cols);
/// <summary>
/// Result returned by <see cref="ProcessFileWithDiagnostics"/>, contains
/// the recognized text result and an array describing detection network
/// forward outputs (shapes and names) which are useful for debugging
/// model output layout mismatches.
/// </summary>
/// <param name="Result">Recognition result for the processed image.</param>
/// <param name="DetectionOutputs">Array describing detection net outputs.</param>
public record DiagnosticResult(ImageResult Result, DetectionOutput[] DetectionOutputs);
/// <summary>
/// Process a single image file and return the recognition result together with
/// detection network forward output shapes for diagnostics.
/// Process a single image file and return the recognition result together
/// with detection network forward output shapes for diagnostics. This
/// method reads the image from disk, runs a forward pass over the
/// detection network to capture the raw output Mat shapes and then calls
/// the normal processing pipeline to return the recognized text.
/// </summary>
public DiagnosticResult ProcessFileWithDiagnostics(string filePath)
{
@ -330,6 +420,16 @@ namespace AIFotoONLUS.Core
return new DiagnosticResult(imgRes, outputs);
}
/// <summary>
/// Process a single image file and return the recognized text as an
/// <see cref="ImageResult"/>. The method detects candidate text regions
/// and runs recognition on each crop. Multiple recognized digit sequences
/// are joined with a comma in the returned <see cref="ImageResult.Text"/>.
/// </summary>
/// <param name="filePath">Path to an image file on disk. Supported
/// formats depend on OpenCV (typically JPEG, PNG, ...).</param>
/// <returns>An <see cref="ImageResult"/> containing the file name and
/// recognized text (possibly empty).</returns>
public ImageResult ProcessImage(string filePath)
{
if (!File.Exists(filePath)) throw new FileNotFoundException("Image not found", filePath);
@ -351,6 +451,14 @@ namespace AIFotoONLUS.Core
return result;
}
/// <summary>
/// Process all JPEG images in a directory and return the recognition
/// results. This is a blocking wrapper over <see cref="ProcessDirectoryAsync"/>.
/// </summary>
/// <param name="directoryPath">Path to a directory containing images.</param>
/// <param name="skipTextNegative">If true, files whose names start with
/// "tn_" will be skipped (convention used to mark text-negative images).</param>
/// <returns>Collection of <see cref="ImageResult"/> ordered by file name.</returns>
public IEnumerable<ImageResult> ProcessDirectory(string directoryPath, bool skipTextNegative = false)
{
// Simple wrapper over async implementation
@ -504,6 +612,16 @@ namespace AIFotoONLUS.Core
}
// Overload RecognizeDigits that accepts a Net for worker threads
/// <summary>
/// Worker overload of <see cref="RecognizeDigits(Mat,string?)"/> that
/// accepts a <see cref="Net"/> instance. This is used by the parallel
/// processing pipeline where each worker owns its own Net instance.
/// </summary>
/// <param name="croppedImage">Cropped region to recognize.</param>
/// <param name="recognitionNet">Recognition <see cref="Net"/> to execute
/// the forward pass with.</param>
/// <param name="context">Optional context string for diagnostics.</param>
/// <returns>Recognized digit sequence or empty string.</returns>
private string RecognizeDigits(Mat croppedImage, Net recognitionNet, string? context = null)
{
if (croppedImage is null) throw new ArgumentNullException(nameof(croppedImage));

View file

@ -1,4 +1,10 @@
namespace AIFotoONLUS.Core
{
/// <summary>
/// Progress statistics reported during directory processing.
/// </summary>
/// <param name="TotalFiles">Total number of image files to process.</param>
/// <param name="ProcessedFiles">Number of files processed so far.</param>
/// <param name="ImagesPerSecond">Current processing throughput in images/second.</param>
public record ProcessingStats(int TotalFiles, int ProcessedFiles, double ImagesPerSecond);
}

View file

@ -12,7 +12,13 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="6.1.1" />
<!-- MinVer for tag-based semantic versioning -->
<PackageReference Include="MinVer" Version="3.1.0" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<!-- Fallback version when no tag is present -->
<Version>0.1.0</Version>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AIFotoONLUS.Core\AIFotoONLUS.Core.csproj" />
</ItemGroup>