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.
This commit is contained in:
MaddoScientisto 2026-02-15 23:37:08 +01:00
commit dc4ebdaf42
7 changed files with 596 additions and 5 deletions

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));