Comment implémenter l'OCR en C# en utilisant des bibliothèques open source
Ce guide s'adresse aux développeurs .NET qui ont intégré l'API REST de Klippa et qui migrent vers IronOCR pour le traitement de documents sur site. Il décrit les étapes pratiques pour supprimer l'infrastructure client HTTP, éliminer la désérialisation JSON et remplacer les téléchargements de documents dépendants du cloud par des appels OCR locaux qui ne passent jamais par le réseau.
Pourquoi migrer depuis Klippa OCR?
Klippa est un service d'analyse documentaire exclusivement basé sur le cloud et ne nécessitant pas de kit de développement .NET . Chaque intégration est un client REST développé sur mesure. Cette réalité architecturale a des conséquences en aval qui s'accumulent tout au long du cycle de vie d'un système de production.
L'absence de package NuGet signifie que vous êtes propriétaire de la couche d'intégration. Il n'y a rien à installer. Le coût d'entrée est l'écriture d'un HttpClient wrapper, la configuration des en-têtes d'authentification X-Auth-Key, la création des corps de requête MultipartFormDataContent, la désérialisation du schéma de réponse JSON de Klippa et la mise en place de la logique de nouvelle tentative pour les échecs transitoires. Il faut donc compter 2 à 4 jours de mise en place avant que le premier document ne soit traité de manière fiable en production. Lorsque Klippa met à jour son schéma d'API, votre code de désérialisation devient incompatible et nécessite une maintenance manuelle.
Chaque téléchargement de document nécessite une connexion réseau. Klippa traite les documents exclusivement sur des serveurs hébergés dans l'UE. Les pannes de production du côté de Klippa, une latence élevée ou toute interruption de l'accès Internet sortant de votre serveur d'application interrompent complètement le traitement des documents. Il n'existe aucune solution de repli, aucun mode local, ni aucune nouvelle tentative pour résoudre le problème d'indisponibilité d'un service cloud.
Les documents sensibles quittent votre infrastructure. Les documents financiers (reçus avec les détails de paiement, factures avec les numéros de TVA et les montants, pièces d'identité avec les données du passeport) sont transmis à un serveur tiers à chaque appel d'API. Les dispositions du RGPD relatives au transfert de données répondent en partie à ces problématiques pour les traitements effectués dans l'UE, mais le périmètre de l'audit s'étend toujours à l'infrastructure de Klippa, à ses politiques de conservation des données et à ses sous-traitants. Pour les équipes ayant des contrats dans les secteurs de la santé, des services juridiques, financiers ou gouvernementaux, l'expression " hébergé dans l'UE " ne satisfait pas à l'exigence selon laquelle les données ne doivent pas quitter l'organisation.
Tarification par document sans plafond. Klippa ne publie pas ses tarifs. Pour tout volume de documents significatif (10 000 reçus par mois dans un système de gestion des dépenses, 500 factures par jour dans un flux de travail d'automatisation des comptes fournisseurs), le modèle de facturation par document engendre des coûts qu'une licence perpétuelle n'entraînerait jamais. L'évolution des coûts est directement liée à la croissance des entreprises, ce qui est l'inverse de ce que devraient faire les dépenses d'infrastructure.
Le périmètre d'intervention du spécialiste s'élargit lorsque les besoins évoluent. Klippa est formée à la gestion des reçus, des factures et des pièces d'identité. Une application conçue initialement pour la gestion des dépenses reste rarement cantonnée à ce rôle. La première fois qu'un type de document ne faisant pas partie de ces trois catégories apparaît (un contrat de travail scanné, un formulaire médical, un dessin technique, un bon de commande avec une mise en page non standard), Klippa ne renvoie rien d'utile. IronOCR traite tout document contenant du texte, sans restriction de catégorie.
Les appels REST asynchrones ajoutent de la latence dans les contextes synchrones. Chaque appel Klippa est une opération HTTP asynchrone. L'envoi aller-retour d'un seul document prend entre 500 ms et 2000 ms sur le réseau. IronOCR traite le même document localement en 100 à 400 ms sans la surcharge asynchrone dans les scénarios où le traitement synchrone convient mieux à l'architecture.
Le problème fondamental
Klippa ne possède pas de SDK. L'OCR consiste à construire et à envoyer une requête HTTP, puis à désérialiser le JSON :
// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks
var response = await _client.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost
var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks
var response = await _client.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost
var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
Imports System.Net.Http
Imports System.IO
Imports System.Text.Json
Imports System.Threading.Tasks
' Klippa: 15+ lines of HTTP plumbing before you read a single character
Dim content As New MultipartFormDataContent()
content.Add(New ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg")
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey) ' auth header — rotates, breaks, leaks
Dim response As HttpResponseMessage = Await _client.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", content)
response.EnsureSuccessStatusCode() ' throws on 4xx/5xx — no retry, document lost
Dim json As String = Await response.Content.ReadAsStringAsync()
Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json) ' your schema, your maintenance
Dim text As String = parsed?.Data?.ParsedDocument?.Text ' nullable chain — breaks when schema changes
IronOCR remplace tout cela :
// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
' IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text = New IronTesseract().Read(imagePath).Text
Comparaison des fonctionnalités IronOCR et de Klippa OCR
Le tableau ci-dessous compare les deux bibliothèques selon les dimensions les plus importantes pour une décision de migration en production.
| Fonction | Klippa OCR | IronOCR |
|---|---|---|
| Modèle de déploiement | Serveurs exclusivement en nuage (serveurs européens) | Sur place, entièrement local |
| Kit de développement .NET / package NuGet | None | IronOcr NuGet package |
| Internet requis | Oui, à chaque appel | Jamais |
| Les données du document quittent le réseau | Toujours | Jamais |
| OCR à usage général | Non (reçus, factures, pièces d'identité uniquement) | Oui (tout type de document) |
| Configuration de l'authentification | X-Auth-Key HTTP header |
IronOcr.License.LicenseKey string |
| Client HTTP requis | Oui | Non |
| Désérialisation de la réponse | Analyse manuelle JSON | Objet typé OcrResult |
| logique de nouvelle tentative/délai d'attente | roulé à la main | Pas nécessaire (appel local) |
| Assistance hors ligne / isolée du réseau | Non | Oui |
| Entrée PDF | Oui (cloud) | Oui (natif, local) |
| Entrée TIFF multipage | Inconnu | Oui |
| Formats d'entrée d'image | JPG, PNG (nuage) | JPG, PNG, BMP, TIFF, GIF et plus encore |
| Entrée de flux et de tableau d'octets | Pas de SDK | Oui |
| prétraitement automatique des images | Côté nuageux (opaque) | Oui (Correction de l'inclinaison, réduction du bruit, contraste, binarisation, netteté) |
| Sortie structurée : coordonnées des mots | Non | Oui |
| Scores de confiance par mot | Non | Oui |
| Sortie PDF consultable | Non | Oui |
| Lecture de codes-barres lors de la reconnaissance optique de caractères (OCR) | Non | Oui |
| Assistance multilingue | Limité aux types de documents formés | plus de 125 langues |
| Sécurité du fil | N/A (appels HTTP) | Oui (un IronTesseract par thread) |
| Déploiement multiplateforme | REST-agnostique | Windows, Linux, macOS, Docker, Azure, AWS |
| Conformité HIPAA/ITAR/environnement isolé (sans contact) | Non | Oui |
| Modèle de tarification | SaaS par document (tarifs non publiés) | Licence perpétuelle de $999 |
| Coût par page à l'échelle | Oui, sans limites | None |
Démarrage rapide : Migration de Klippa OCRvers IronOCR
Étape 1 : Remplacer le package NuGet
Klippa ne possède pas de package NuGet officiel. Supprimez les dépendances du client HTTP qui existent uniquement pour prendre en charge l'intégration de Klippa :
# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
Installez IronOCR depuis NuGet :
dotnet add package IronOcr
Étape 2 : Mise à jour des espaces de noms
Supprimez les espaces de noms HTTP et JSON requis par l'intégration de Klippa. Ajouter l'espace de noms IronOCR unique :
// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
// After (IronOCR)
using IronOcr;
// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
// After (IronOCR)
using IronOcr;
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports IronOcr
Étape 3 : initialisation de la licence
Ajoutez l'initialisation de la licence une fois au démarrage de l'application — dans Program.cs, Startup.cs, ou avant le premier appel OCR :
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Exemples de migration de code
Remplacement de la classe de service client HTTP
L'intégration de Klippa nécessite une classe de service complète encapsulant l'infrastructure HTTP. Il n'y a aucun moyen d'éviter cela car il n'existe pas de SDK.
Approche Klippa :
// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";
public KlippaOcrService(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
_httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
}
public async Task<string> ReadDocumentTextAsync(string filePath)
{
using var form = new MultipartFormDataContent();
var fileBytes = await File.ReadAllBytesAsync(filePath);
form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));
var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
// navigate Klippa's nested JSON schema
return doc.RootElement
.GetProperty("data")
.GetProperty("parsed_document")
.GetProperty("text")
.GetString() ?? string.Empty;
}
public void Dispose() => _httpClient.Dispose();
}
// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";
public KlippaOcrService(string apiKey)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
_httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
}
public async Task<string> ReadDocumentTextAsync(string filePath)
{
using var form = new MultipartFormDataContent();
var fileBytes = await File.ReadAllBytesAsync(filePath);
form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));
var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
// navigate Klippa's nested JSON schema
return doc.RootElement
.GetProperty("data")
.GetProperty("parsed_document")
.GetProperty("text")
.GetString() ?? string.Empty;
}
public void Dispose() => _httpClient.Dispose();
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Text.Json
' Klippa: entire service class just to send one HTTP request
Public Class KlippaOcrService
Implements IDisposable
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _baseUrl As String = "https://custom-ocr.klippa.com/api/v1"
Public Sub New(apiKey As String)
_httpClient = New HttpClient()
_httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey)
_httpClient.Timeout = TimeSpan.FromSeconds(30) ' network timeout required
End Sub
Public Async Function ReadDocumentTextAsync(filePath As String) As Task(Of String)
Using form As New MultipartFormDataContent()
Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
form.Add(New ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath))
Dim response = Await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form)
response.EnsureSuccessStatusCode()
Dim json = Await response.Content.ReadAsStringAsync()
Using doc = JsonDocument.Parse(json)
' navigate Klippa's nested JSON schema
Return doc.RootElement _
.GetProperty("data") _
.GetProperty("parsed_document") _
.GetProperty("text") _
.GetString() OrElse String.Empty
End Using
End Using
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_httpClient.Dispose()
End Sub
End Class
Approche IronOCR :
// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
public string ReadDocumentText(string filePath)
{
return _ocr.Read(filePath).Text;
}
}
// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
private readonly IronTesseract _ocr = new IronTesseract();
public string ReadDocumentText(string filePath)
{
return _ocr.Read(filePath).Text;
}
}
// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
Imports IronOcr
Public Class OcrService
Private ReadOnly _ocr As New IronTesseract()
Public Function ReadDocumentText(filePath As String) As String
Return _ocr.Read(filePath).Text
End Function
End Class
' At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' Usage — identical call site, different internals:
Dim service As New OcrService()
Dim text As String = service.ReadDocumentText("invoice.jpg") ' local, synchronous, zero network
La classe de service Klippa existe uniquement parce que l'API nécessite une infrastructure HTTP. L'équivalent d'IronOCR se réduit à un seul appel Read(). Les délais d'attente, les en-têtes d'authentification et les modèles de suppression disparaissent tous en l'absence de réseau. Consultez le guide d'installation d'IronTesseract pour les options d'initialisation et l' exemple de code OCR de base pour un code fonctionnel.
Suppression du téléchargement de formulaires en plusieurs parties
Klippa reçoit les documents sous forme de formulaires multiparties téléchargés. Le code de téléchargement est mécanique mais fragile : lectures de fichiers, en-têtes de type de contenu, construction des limites et gestion de la taille du téléchargement.
Approche Klippa :
// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
string filePath, string documentType = "financial")
{
using var form = new MultipartFormDataContent();
// read file into memory — entire document in RAM before upload
var fileBytes = await File.ReadAllBytesAsync(filePath);
var byteContent = new ByteArrayContent(fileBytes);
byteContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
form.Add(byteContent, "document", Path.GetFileName(filePath));
form.Add(new StringContent(documentType), "DocumentType");
// document leaves your server here
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
}
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<KlippaResult>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
string filePath, string documentType = "financial")
{
using var form = new MultipartFormDataContent();
// read file into memory — entire document in RAM before upload
var fileBytes = await File.ReadAllBytesAsync(filePath);
var byteContent = new ByteArrayContent(fileBytes);
byteContent.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
form.Add(byteContent, "document", Path.GetFileName(filePath));
form.Add(new StringContent(documentType), "DocumentType");
// document leaves your server here
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
}
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<KlippaResult>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class KlippaUploader
Private ReadOnly _httpClient As HttpClient
Public Sub New(httpClient As HttpClient)
_httpClient = httpClient
End Sub
Public Async Function UploadAndParseAsync(filePath As String, Optional documentType As String = "financial") As Task(Of KlippaResult)
Using form As New MultipartFormDataContent()
' read file into memory — entire document in RAM before upload
Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
Dim byteContent = New ByteArrayContent(fileBytes)
byteContent.Headers.ContentType = New System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg")
form.Add(byteContent, "document", Path.GetFileName(filePath))
form.Add(New StringContent(documentType), "DocumentType")
' document leaves your server here
Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)
If Not response.IsSuccessStatusCode Then
Dim error = Await response.Content.ReadAsStringAsync()
Throw New InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}")
End If
Dim json = Await response.Content.ReadAsStringAsync()
Return JsonSerializer.Deserialize(Of KlippaResult)(json, New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True})
End Using
End Function
End Class
Approche IronOCR :
// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);
// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);
Console.WriteLine(result.Text);
// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);
// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);
Console.WriteLine(result.Text);
Imports IronOcr
Imports System.IO
' IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' From file path
Using input As New OcrInput()
input.LoadImage("invoice.jpg")
Dim result = New IronTesseract().Read(input)
' From byte array (same bytes Klippa was uploading)
Dim fileBytes As Byte() = Await File.ReadAllBytesAsync("invoice.jpg")
Using inputFromBytes As New OcrInput()
inputFromBytes.LoadImage(fileBytes)
Dim resultFromBytes = New IronTesseract().Read(inputFromBytes)
Console.WriteLine(result.Text)
End Using
End Using
La construction MultipartFormDataContent, les en-têtes de type de contenu et le téléchargement lui-même ont tous disparu. IronOCR lit directement à partir du chemin du fichier, d'un tableau d'octets ou d'un Stream — les mêmes données que Klippa transmettait au cloud restent locales. Le guide d'entrée d'images couvre tous les formats d'entrée pris en charge, et le guide d'entrée de flux couvre le chemin de flux mémoire pour les documents qui arrivent sous forme de tableaux d'octets provenant de processus en amont.
Remplacement de la désérialisation de la réponse JSON
Klippa renvoie une structure JSON imbriquée. Naviguer dans cette structure nécessite soit un modèle C# correspondant, soit un parcours inline JsonDocument — qui se cassent tous les deux lorsque Klippa modifie leur schéma de réponse.
Approche Klippa :
// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
[JsonPropertyName("data")]
public KlippaData Data { get; set; }
}
public class KlippaData
{
[JsonPropertyName("parsed_document")]
public KlippaParsedDocument ParsedDocument { get; set; }
}
public class KlippaParsedDocument
{
[JsonPropertyName("text")]
public string Text { get; set; }
[JsonPropertyName("amount")]
public decimal? Amount { get; set; }
[JsonPropertyName("merchant")]
public string Merchant { get; set; }
[JsonPropertyName("date")]
public string Date { get; set; }
}
// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
var klippaResult = await UploadAndParseAsync(imagePath);
// every property access is nullable — schema drift breaks this silently
return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
[JsonPropertyName("data")]
public KlippaData Data { get; set; }
}
public class KlippaData
{
[JsonPropertyName("parsed_document")]
public KlippaParsedDocument ParsedDocument { get; set; }
}
public class KlippaParsedDocument
{
[JsonPropertyName("text")]
public string Text { get; set; }
[JsonPropertyName("amount")]
public decimal? Amount { get; set; }
[JsonPropertyName("merchant")]
public string Merchant { get; set; }
[JsonPropertyName("date")]
public string Date { get; set; }
}
// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
var klippaResult = await UploadAndParseAsync(imagePath);
// every property access is nullable — schema drift breaks this silently
return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
Imports System.Text.Json.Serialization
' Klippa: deserialization model — breaks when API schema changes
Public Class KlippaResponse
<JsonPropertyName("data")>
Public Property Data As KlippaData
End Class
Public Class KlippaData
<JsonPropertyName("parsed_document")>
Public Property ParsedDocument As KlippaParsedDocument
End Class
Public Class KlippaParsedDocument
<JsonPropertyName("text")>
Public Property Text As String
<JsonPropertyName("amount")>
Public Property Amount As Decimal?
<JsonPropertyName("merchant")>
Public Property Merchant As String
<JsonPropertyName("date")>
Public Property Date As String
End Class
' Usage: navigate the nullable chain every time
Public Async Function GetExtractedTextAsync(imagePath As String) As Task(Of String)
Dim klippaResult = Await UploadAndParseAsync(imagePath)
' every property access is nullable — schema drift breaks this silently
Return If(klippaResult?.Data?.ParsedDocument?.Text, String.Empty)
End Function
Approche IronOCR :
// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");
// Direct property access — no deserialization, no nullable navigation
string fullText = result.Text;
double confidence = result.Confidence;
int pageCount = result.Pages.Count();
// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
}
}
// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");
// Direct property access — no deserialization, no nullable navigation
string fullText = result.Text;
double confidence = result.Confidence;
int pageCount = result.Pages.Count();
// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
foreach (var line in page.Lines)
{
Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
}
}
Imports IronOcr
' IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr As New IronTesseract()
Dim result = ocr.Read("invoice.jpg")
' Direct property access — no deserialization, no nullable navigation
Dim fullText As String = result.Text
Dim confidence As Double = result.Confidence
Dim pageCount As Integer = result.Pages.Count()
' Structured data: lines and words with coordinates
For Each page In result.Pages
For Each line In page.Lines
Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}")
Next
Next
OcrResult est un objet .NET typé. Il n'y a pas de JSON à analyser, pas de classe de modèle à maintenir et aucun risque de dérive de schéma interrompant la désérialisation en production. Le guide de lecture des résultats documente le modèle d'objet OcrResult complet, y compris les coordonnées de mot, les scores de confiance et la hiérarchie de page structurée. Pour des modèles d'extraction de champs spécifiques aux factures construits sur OcrResult, le tutoriel sur la reconnaissance des factures OCR couvre la logique d'extraction de bout en bout.
Suppression de l'infrastructure de gestion des erreurs et de nouvelle tentative
L'intégration de Klippa via HTTP nécessite une gestion des erreurs pour chaque mode d'échec qu'un appel réseau peut produire : délais d'attente, réponses 4xx, réponses 5xx, limites de débit et JSON partiel. Les équipes qui gèrent les intégrations en production ajoutent des politiques de nouvelle tentative à l'aide de Polly ou d'une logique personnalisée. Cette infrastructure disparaît lorsque l'appel réseau disparaît.
Approche Klippa :
// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
var delay = TimeSpan.FromSeconds(1);
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
"document",
Path.GetFileName(filePath));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// rate limited — back off and retry
await Task.Delay(delay * attempt);
continue;
}
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(cts.Token);
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
}
catch (HttpRequestException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // exponential backoff
}
catch (TaskCanceledException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // timeout — retry
}
}
throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
var delay = TimeSpan.FromSeconds(1);
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
"document",
Path.GetFileName(filePath));
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
// rate limited — back off and retry
await Task.Delay(delay * attempt);
continue;
}
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(cts.Token);
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
}
catch (HttpRequestException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // exponential backoff
}
catch (TaskCanceledException) when (attempt < maxRetries)
{
await Task.Delay(delay * attempt); // timeout — retry
}
}
throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks
Public Class KlippaService
Private ReadOnly _httpClient As HttpClient
Public Sub New(httpClient As HttpClient)
_httpClient = httpClient
End Sub
' Klippa: retry policy required — cloud calls fail unpredictably
Public Async Function ReadWithRetryAsync(filePath As String, Optional maxRetries As Integer = 3) As Task(Of String)
Dim delay As TimeSpan = TimeSpan.FromSeconds(1)
For attempt As Integer = 1 To maxRetries
Try
Using form As New MultipartFormDataContent()
form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(filePath)), "document", Path.GetFileName(filePath))
Using cts As New CancellationTokenSource(TimeSpan.FromSeconds(30))
Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token)
If response.StatusCode = System.Net.HttpStatusCode.TooManyRequests Then
' rate limited — back off and retry
Await Task.Delay(delay * attempt)
Continue For
End If
response.EnsureSuccessStatusCode()
Dim json As String = Await response.Content.ReadAsStringAsync(cts.Token)
Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json)
Return If(parsed?.Data?.ParsedDocument?.Text, String.Empty)
End Using
End Using
Catch ex As HttpRequestException When attempt < maxRetries
Await Task.Delay(delay * attempt) ' exponential backoff
Catch ex As TaskCanceledException When attempt < maxRetries
Await Task.Delay(delay * attempt) ' timeout — retry
End Try
Next
Throw New InvalidOperationException($"Klippa API failed after {maxRetries} attempts")
End Function
End Class
Public Class KlippaResponse
Public Property Data As KlippaData
End Class
Public Class KlippaData
Public Property ParsedDocument As ParsedDocument
End Class
Public Class ParsedDocument
Public Property Text As String
End Class
Approche IronOCR :
// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
public string ReadDocument(string filePath)
{
// Non retry loop. Non CancellationTokenSource. Non HTTP status checks.
// Non rate limit handling. Non partial-JSON guards.
var result = new IronTesseract().Read(filePath);
return result.Text;
}
// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
public string ReadDocument(string filePath)
{
// Non retry loop. Non CancellationTokenSource. Non HTTP status checks.
// Non rate limit handling. Non partial-JSON guards.
var result = new IronTesseract().Read(filePath);
return result.Text;
}
Imports IronOcr
' IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Public Function ReadDocument(filePath As String) As String
' Non retry loop. Non CancellationTokenSource. Non HTTP status checks.
' Non rate limit handling. Non partial-JSON guards.
Dim result = New IronTesseract().Read(filePath)
Return result.Text
End Function
L'ensemble de l'infrastructure de nouvelle tentative — la boucle, le calcul de retard, le CancellationTokenSource, le branchement du code d'état HTTP, le bloc de capture TaskCanceledException — existe uniquement à cause du réseau. Supprimez l'appel réseau et tout disparaît. Un appel OCR local échoue rapidement avec une exception typée si le fichier d'entrée est manquant ou illisible, et réussit dans le cas contraire. Le guide d'optimisation de la vitesse explique comment optimiser les performances IronOCR si le débit est un point important après la migration.
Traitement des PDF multipages sans téléchargement sur le cloud
Klippa accepte les téléchargements de PDF via le même point de terminaison parseDocument. Les fichiers PDF multipages quittent toujours votre réseau. IronOCR lit les fichiers PDF nativement, en temps réel, avec un accès aux résultats page par page.
Approche Klippa :
// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
var pages = new List<string>();
// Klippa parses the entire PDF server-side and returns combined results
// You cannot control per-page processing or access raw page text
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
"document",
Path.GetFileName(pdfPath));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<KlippaResponse>(json);
// Klippa returns the combined parsed text — no per-page breakdown in basic API
pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);
return pages;
}
// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
var pages = new List<string>();
// Klippa parses the entire PDF server-side and returns combined results
// You cannot control per-page processing or access raw page text
using var form = new MultipartFormDataContent();
form.Add(
new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
"document",
Path.GetFileName(pdfPath));
var response = await _httpClient.PostAsync(
"https://custom-ocr.klippa.com/api/v1/parseDocument", form);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<KlippaResponse>(json);
// Klippa returns the combined parsed text — no per-page breakdown in basic API
pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);
return pages;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Public Class PdfExtractor
Private ReadOnly _httpClient As HttpClient
Public Sub New(httpClient As HttpClient)
_httpClient = httpClient
End Sub
' Klippa: PDF upload — entire document transmitted, results depend on cloud availability
Public Async Function ExtractPdfPagesAsync(pdfPath As String) As Task(Of List(Of String))
Dim pages As New List(Of String)()
' Klippa parses the entire PDF server-side and returns combined results
' You cannot control per-page processing or access raw page text
Using form As New MultipartFormDataContent()
form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(pdfPath)), "document", Path.GetFileName(pdfPath))
Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)
response.EnsureSuccessStatusCode()
Dim json = Await response.Content.ReadAsStringAsync()
Dim result = JsonSerializer.Deserialize(Of KlippaResponse)(json)
' Klippa returns the combined parsed text — no per-page breakdown in basic API
pages.Add(If(result?.Data?.ParsedDocument?.Text, String.Empty))
End Using
Return pages
End Function
End Class
Public Class KlippaResponse
Public Property Data As KlippaData
End Class
Public Class KlippaData
Public Property ParsedDocument As ParsedDocument
End Class
Public Class ParsedDocument
Public Property Text As String
End Class
Approche IronOCR :
// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP
var ocr = new IronTesseract();
var result = ocr.Read(input);
// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
Console.WriteLine(page.Text);
}
// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP
var ocr = new IronTesseract();
var result = ocr.Read(input);
// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
Console.WriteLine(page.Text);
}
// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
Imports IronOcr
' IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Using input As New OcrInput()
input.LoadPdf("multi-page-invoice.pdf") ' reads locally — no HTTP
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
' Per-page access — not available from Klippa's combined response
For Each page In result.Pages
Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines")
Console.WriteLine(page.Text)
Next
' Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf")
End Using
IronOCR lit les fichiers PDF nativement, sans aucune conversion. Chaque page est accessible individuellement, avec sa hiérarchie complète de lignes, de mots et de caractères. L'appel SaveAsSearchablePdf() produit un PDF avec couche de texte à partir d'un document numérisé — une capacité que Klippa n'offre pas. Le guide d'importation PDF décrit les options de chargement, et le guide PDF consultable décrit les options de sortie, notamment le format PDF/A pour la conformité aux exigences d'archivage.
Référence de mappage de l'API Klippa OCRvers IronOCR
Klippa est une API REST, et non un SDK typé. Le tableau ci-dessous traduit la surface d'intégration de Klippa en équivalents IronOCR .
| Concept Klippa | Équivalent d'IronOCR |
|---|---|
HttpClient avec en-tête X-Auth-Key |
Instance IronTesseract — pas de configuration d'authentification |
MultipartFormDataContent |
OcrInput.LoadImage(path) ou OcrInput.LoadPdf(path) |
POST /api/v1/parseDocument |
IronTesseract.Read(input) |
await _client.PostAsync(...) |
ocr.Read(input) — synchrone, pas de await nécessaire |
response.EnsureSuccessStatusCode() |
Inutile — aucune réponse HTTP |
JsonSerializer.Deserialize<KlippaResponse>(json) |
Objet typé OcrResult — pas de désérialisation |
KlippaResponse.Data.ParsedDocument.Text |
OcrResult.Text |
KlippaResponse.Data.ParsedDocument.Amount |
Regex personnalisé sur OcrResult.Text ou OcrResult.Lines |
KlippaResponse.Data.ParsedDocument.Merchant |
OcrResult.Pages[0].Lines[0].Text |
Boucle de nouvelle tentative avec Task.Delay |
Inutile — aucun mode de défaillance réseau |
CancellationTokenSource(TimeSpan.FromSeconds(30)) |
Inutile — exécution locale |
| Gestion des limites de débit (HTTP 429) | Inutile — aucune limite de débit |
| Routage de documents cloud vers les serveurs de l'UE | Exécution locale en cours |
KlippaService.Dispose() / HttpClient.Dispose() |
Élimination de OcrInput via l'instruction using |
| Champs de réponse JSON structurés | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| Abonnement à l'API SaaS | Objet string IronOcr.License.LicenseKey — perpétuel |
Problèmes de migration courants et solutions
Problème 1 : Sites d'appels asynchrones uniquement après la suppression du protocole HTTP
Klippa : Toute l'intégration de Klippa est asynchrone car les appels HTTP l'exigent. Les contrôleurs, services et travailleurs en arrière-plan dans l'ensemble de votre base de code appellent await ProcessDocumentAsync(...). Supprimer l'appel HTTP signifie que le await n'est plus nécessaire, mais les signatures de méthode async restent.
Solution : IronOCR fournit des API synchrones et asynchrones. Pour les sites d'appel qui doivent rester asynchrones (contrôleurs ASP.NET Core, services en arrière-plan avec CancellationToken), utilisez ReadAsync :
// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
string filePath, CancellationToken cancellationToken = default)
{
// Previously: await _httpClient.PostAsync(...)
// Now: local call, same awaitable pattern
var ocr = new IronTesseract();
var result = await ocr.ReadAsync(filePath);
return result.Text;
}
// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
string filePath, CancellationToken cancellationToken = default)
{
// Previously: await _httpClient.PostAsync(...)
// Now: local call, same awaitable pattern
var ocr = new IronTesseract();
var result = await ocr.ReadAsync(filePath);
return result.Text;
}
Imports System.Threading
Imports System.Threading.Tasks
Public Class DocumentProcessor
Public Async Function ProcessDocumentAsync(filePath As String, Optional cancellationToken As CancellationToken = Nothing) As Task(Of String)
Dim ocr As New IronTesseract()
Dim result = Await ocr.ReadAsync(filePath)
Return result.Text
End Function
End Class
Le guide OCR asynchrone couvre l'intégration ReadAsync et CancellationToken pour ASP.NET Core et les modèles de service hébergés.
Problème 2 : Enregistrement de l'injection de dépendances
Klippa : La classe KlippaService est enregistrée dans DI comme service singleton ou scoped et encapsule HttpClient. Sa suppression implique la mise à jour de l'enregistrement DI et de tous les points d'injection.
Solution : Enregistrez IronTesseract comme singleton (il est sûr pour le thread) et injectez-le directement, ou créez un wrapper mince qui reflète votre interface de service existante :
// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();
// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public string ReadDocument(string path) => _ocr.Read(path).Text;
}
// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();
// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();
public class IronOcrService : IOcrService
{
private readonly IronTesseract _ocr;
public IronOcrService(IronTesseract ocr) => _ocr = ocr;
public string ReadDocument(string path) => _ocr.Read(path).Text;
}
Imports Microsoft.Extensions.DependencyInjection
' In Program.vb or Startup.vb
builder.Services.AddSingleton(Of IronTesseract)()
' Or wrap for interface compatibility
builder.Services.AddSingleton(Of IOcrService, IronOcrService)()
Public Class IronOcrService
Implements IOcrService
Private ReadOnly _ocr As IronTesseract
Public Sub New(ocr As IronTesseract)
_ocr = ocr
End Sub
Public Function ReadDocument(path As String) As String Implements IOcrService.ReadDocument
Return _ocr.Read(path).Text
End Function
End Class
Une instance IronTesseract enregistrée comme singleton gère les requêtes concurrentes. Chaque appel à Read() est sûr pour le thread.
Problème 3 : Extraction de champs structurés sans JSON pré-analysé
Klippa : Klippa renvoie amount, merchant, date, et vat_amount comme propriétés JSON typées. La migration vers IronOCR signifie que ces champs ne sont plus pré-analysés.
Solution : OcrResult d'IronOCR fournit le texte brut et les coordonnées au niveau des mots pour construire une extraction équivalente. Pour les documents dont la mise en page est prévisible, la reconnaissance optique de caractères (OCR) basée sur les régions cible directement des champs spécifiques :
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60); // top header area
using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();
using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60); // top header area
using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();
using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
' Target specific layout regions instead of relying on pre-parsed cloud fields
Dim totalRegion As New CropRectangle(350, 580, 250, 50) ' bottom-right total area
Dim merchantRegion As New CropRectangle(50, 30, 400, 60) ' top header area
Using merchantInput As New OcrInput()
merchantInput.LoadImage("receipt.jpg", merchantRegion)
Dim merchantName As String = New IronTesseract().Read(merchantInput).Text.Trim()
End Using
Using totalInput As New OcrInput()
totalInput.LoadImage("receipt.jpg", totalRegion)
Dim totalText As String = New IronTesseract().Read(totalInput).Text.Trim()
End Using
Le guide OCR basé sur la région couvre l'utilisation CropRectangle en détail. Pour obtenir des modèles d'extraction complets pour les différents formats de reçus et de factures, le tutoriel sur la numérisation des reçus fournit un code fonctionnel complet.
Problème 4 : Documents arrivant sous forme de flux depuis les services en amont
Klippa : Klippa reçoit les documents sous forme de téléchargements de formulaires multipart — des octets de fichier enveloppés dans le contenu d'un formulaire HTTP. Si votre application reçoit des documents sous forme de flux provenant de S3, d'Azure Blob Storage ou d'API internes, vous lisiez le flux en octets, puis vous téléversiez ces octets sur Klippa.
Solution : IronOCR accepte directement les objets Stream. L'étape de conversion d'octets disparaît :
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
using var input = new OcrInput();
input.LoadImage(documentStream); // accepts Stream directly
var ocr = new IronTesseract();
var result = ocr.Read(input);
return result.Text;
}
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
using var input = new OcrInput();
input.LoadImage(documentStream); // accepts Stream directly
var ocr = new IronTesseract();
var result = ocr.Read(input);
return result.Text;
}
Imports System.IO
Imports System.Threading.Tasks
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Public Async Function ProcessDocumentStreamAsync(documentStream As Stream) As Task(Of String)
Using input As New OcrInput()
input.LoadImage(documentStream) ' accepts Stream directly
Dim ocr As New IronTesseract()
Dim result = ocr.Read(input)
Return result.Text
End Using
End Function
Pas de ReadAllBytes, pas de construction MultipartFormDataContent, pas de POST HTTP. Le flux va directement dans OcrInput. Le guide des entrées de flux couvre les types de flux et les modèles d'évacuation.
Problème 5 : Tests d'intégration dépendant de la simulation HTTP
Klippa : Les tests d'intégration pour le code Klippa simulent HttpClient ou utilisent des intercepteurs HTTP (par exemple, WireMock, MockHttp) pour simuler les réponses de l'API. Ces tests simulent la couche HTTP, et non la logique OCR.
Solution : Les tests IronOCR utilisent de vrais documents avec un résultat attendu connu. Aucune infrastructure de simulation n'est nécessaire. Les tests s'exécutent hors ligne :
[Fact]
public void ReadDocument_ReturnsExpectedText()
{
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
// Use a real test fixture — no HTTP mocking, runs fully offline
var result = ocr.Read("test-fixtures/sample-invoice.jpg");
Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
Assert.True(result.Confidence > 70);
}
[Fact]
public void ReadDocument_ReturnsExpectedText()
{
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var ocr = new IronTesseract();
// Use a real test fixture — no HTTP mocking, runs fully offline
var result = ocr.Read("test-fixtures/sample-invoice.jpg");
Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
Assert.True(result.Confidence > 70);
}
<Fact>
Public Sub ReadDocument_ReturnsExpectedText()
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim ocr = New IronTesseract()
' Use a real test fixture — no HTTP mocking, runs fully offline
Dim result = ocr.Read("test-fixtures/sample-invoice.jpg")
Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase)
Assert.True(result.Confidence > 70)
End Sub
Les tests qui nécessitaient auparavant une connexion Klippa en direct ou une configuration HTTP simulée complexe s'exécutent désormais en intégration continue sans accès réseau.
Problème n° 6 : Documents de faible qualité améliorés côté serveur par Klippa
Klippa : Le traitement dans le cloud applique une amélioration de l'image avant la reconnaissance. Les développeurs n'ont jamais besoin de configurer cela — cela se fait automatiquement sur les serveurs de Klippa. Lors de la migration, les documents traités silencieusement par Klippa peuvent présenter une précision moindre sans prétraitement explicite dans IronOCR.
Solution : Appliquer explicitement les filtres de prétraitement d'IronOCR. L'ensemble de filtres reflète celui appliqué côté serveur par les services cloud :
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew(); // fix rotation from camera or scanner
input.DeNoise(); // remove compression noise
input.Contrast(); // boost faded ink
input.Binarize(); // clean background for clearer character edges
var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew(); // fix rotation from camera or scanner
input.DeNoise(); // remove compression noise
input.Contrast(); // boost faded ink
input.Binarize(); // clean background for clearer character edges
var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
Imports IronOcr
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Using input As New OcrInput()
input.LoadImage("low-quality-scan.jpg")
input.Deskew() ' fix rotation from camera or scanner
input.DeNoise() ' remove compression noise
input.Contrast() ' boost faded ink
input.Binarize() ' clean background for clearer character edges
Dim result = New IronTesseract().Read(input)
Console.WriteLine($"Confidence: {result.Confidence}%")
End Using
Le guide de correction de la qualité d'image couvre tous les filtres de prétraitement et l'ordre dans lequel les appliquer pour différents types de dégradation de documents.
Liste de contrôle de migration Klippa OCR
Pré-migration
Avant de supprimer quoi que ce soit, vérifiez votre code source afin de localiser tout le code spécifique à Klippa :
# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .
# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .
# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .
# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .
# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .
# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .
# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .
# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .
# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
Notes d'inventaire :
- Enregistrez chaque classe qui encapsule
HttpClientpour les appels Klippa - Listez toutes les classes de modèle de désérialisation JSON (
KlippaResponse,KlippaParsedDocument, etc.) - Documentez tous les mappages de champs qui utilisent les propriétés JSON pré-analysées de Klippa
- Veuillez noter les politiques de nouvelle tentative Polly ou les boucles de nouvelle tentative personnalisées conçues pour Klippa.
Migration de code
- Installez le
IronOcrpackage NuGet (dotnet add package IronOcr) - Ajoutez
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"au démarrage de l'application - Supprimez les importations
System.Net.Http,System.Text.Json,Newtonsoft.Jsondes fichiers de service Klippa - Supprimez la classe
KlippaService(ou remplacez son corps par des appelsIronTesseract, en conservant l'interface) - Enregistrez
IronTesseractcomme singleton dans le conteneur DI - Remplacez les blocs de téléchargement
MultipartFormDataContentparOcrInput.LoadImage()ouOcrInput.LoadPdf() - Supprimez les classes de modèle de réponse JSON (
KlippaResponse,KlippaData,KlippaParsedDocument) - Remplacez les chaînes de navigation JSON nullable (
.Data?.ParsedDocument?.Text) parresult.Text - Supprimez les boucles de nouvelle tentative et
CancellationTokenSourcetimeouts des sites d'appel Klippa - Supprimer la gestion des limites de débit (blocs catch HTTP 429)
- Remplacez
await ProcessDocumentAsync(...)parawait ocr.ReadAsync(...)ouocr.Read(...)synchrones - Ajoutez des filtres de prétraitement
OcrInput(Deskew,DeNoise,Contrast) pour les entrées de documents de mauvaise qualité - Remplacer l'infrastructure de test HTTP simulée par des tests de données réelles
- Supprimez les stratégies de nouvelle tentative Polly ou le middleware de nouvelle tentative personnalisé limité aux appels Klippa
Après la migration
- Vérifier que le texte extrait correspond au contenu attendu des documents de test connus.
- Confirmer que les scores de confiance dépassent le seuil acceptable (généralement 70 % et plus) pour les types de documents de production
- Testez les entrées PDF : chargez des PDF multi-pages nativement et vérifiez l'accès au texte par page via
result.Pages - Testez les entrées de flux : passez
MemoryStreamet vérifiez queOcrInput.LoadImage(stream)produit le bon résultat - Vérifier que les filtres de prétraitement améliorent la précision des numérisations de faible qualité par rapport à la ligne de base non traitée
- Confirmez que le
IronTesseractsingleton injecté par DI gère les requêtes concurrentes sans contention - Exécuter les tests d'intégration hors ligne (sans connexion réseau) — tous les tests doivent réussir sans accès au cloud
- Vérifiez la sortie du PDF consultable avec
result.SaveAsSearchablePdf("output.pdf")pour les flux de documents numérisés - Testez
ReadAsyncdans le contexte du contrôleur ASP.NET Core avec la propagationCancellationToken - Confirmez que le modèle de disposition
using var input = new OcrInput()ne fuit pas de mémoire sous une charge soutenue
Principaux avantages de la migration vers IronOCR
Souveraineté des données dès le premier jour. Après la migration, vos documents financiers sensibles, les scans d'identité et les factures confidentielles restent à jamais dans votre infrastructure. Il n'y a pas de sous-traitant tiers dans le périmètre de l'audit, pas de politique de conservation des données à examiner et pas d'accord de transfert de données à maintenir. Les contraintes HIPAA, ITAR, CMMC et FedRAMP qui posaient auparavant problème à Klippa sont satisfaites par défaut. Le déploiement sur Docker , AWS ou Azure permet de conserver l'ensemble de vos ressources à l'intérieur des limites de votre propre infrastructure.
Complexité de l'infrastructure éliminée. La classe de service, le client HTTP, le code de téléchargement de formulaire, les modèles JSON, la politique de nouvelle tentative, la configuration du délai d'expiration — tout cela servait à encapsuler un appel réseau. Supprimez l'appel réseau et tout disparaît avec lui. Le code source qui en résulte est plus petit, plus facile à lire et comporte moins de sources de défaillance. Une instance unique IronTesseract injectée par DI remplace l'ensemble de la couche d'intégration HTTP.
Coût prévisible quel que soit le volume. Une licence IronOCR perpétuelle à $999 (Lite), 1 499 $ (Professional) ou 2 999 $ (Enterprise) couvre un traitement illimité des documents. Le traitement de 500 documents par mois ou de 500 000 documents par mois coûte la même chose. Le modèle de facturation par document qui rendait Klippa coûteux à grande échelle est structurellement absent. La page de licences IronOCR détaille tous les niveaux et ce que chacun inclut.
Portée des documents sans limites. IronOCR traite tout document contenant du texte. Les contrats numérisés, les dessins techniques, les formulaires médicaux, les bons de commande, les notes manuscrites, les captures d'écran, les archives TIFF — tous gérés par le même appel Read() avec la même API. La restriction de champ d'application spécialisée qui exigeait un deuxième système pour les documents ne relevant pas des catégories de formation de Klippa a disparu. Une seule bibliothèque, un seul point d'intégration, tout type de document.
Prise en charge désormais des environnements hors ligne et à accès restreint. Les applications déployées sur les réseaux bancaires, les systèmes gouvernementaux, les environnements périphériques ou toute infrastructure avec accès sortant restreint fonctionnent exactement comme dans les environnements ouverts. Il n'y a pas de vérification de connectivité, pas de test de santé vers un point de terminaison cloud, et pas de mode dégradé lorsque la connexion Internet est indisponible. Les déploiements isolés du réseau fonctionnent sans modification. Le guide de déploiement Linux et le guide de déploiement Docker couvrent les voies de déploiement conteneurisées et côté serveur pour ces environnements.
Contrôle total de l'amélioration d'image. Le prétraitement dans le cloud était une boîte noire : Klippa l'appliquait, vous observiez les résultats, vous n'aviez aucun paramètre à régler. Le pipeline de prétraitement d'IronOCR est explicite et composable : Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise(). Chaque filtre est optionnel et doit être commandé. Les améliorations en matière de précision sont mesurables, reproductibles et sous votre contrôle. Le guide de correction de la qualité d'image et la page relative aux fonctionnalités de prétraitement couvrent l'intégralité du catalogue de filtres et fournissent des indications sur le moment opportun pour appliquer chacun d'eux.
Questions Fréquemment Posées
Pourquoi devrais-je migrer de Klippa OCR API vers IronOCR ?
Parmi les motivations communes, citons l'élimination de la complexité de l'interopérabilité COM, le remplacement de la gestion des licences basée sur les fichiers, l'absence de facturation à la page, l'activation du déploiement Docker/conteneur et l'adoption d'un flux de travail NuGet-natif qui s'intègre à l'outillage .NET standard.
Quels sont les principaux changements de code lors de la migration de l'API OCR de Klippa vers IronOcr ?
Remplacer les séquences d'initialisation de Klippa par l'instanciation d'IronTesseract, supprimer la gestion du cycle de vie de COM (modèles explicites Create/Load/Close) et mettre à jour les noms des propriétés des résultats. Le résultat est une réduction significative du nombre de lignes de code.
Comment installer IronOCR pour commencer la migration ?
Exécutez "Install-Package IronOcr" dans la console du Package Manager ou "dotnet add package IronOcr" dans le CLI. Les packs de langues sont des paquets distincts : 'dotnet add package IronOcr.Languages.French' pour le français, par exemple.
IronOCR offre-t-il la même précision d'OCR que Klippa OCR API pour les documents commerciaux standard ?
IronOcr atteint un niveau de précision élevé pour les contenus commerciaux standard, notamment les factures, les contrats, les reçus et les formulaires dactylographiés. Les filtres de prétraitement d'image (désalignement, suppression du bruit, amélioration du contraste) améliorent encore la reconnaissance sur des données dégradées.
Comment IronOCR gère-t-il les données linguistiques que Klippa OCR API installe séparément ?
Les données linguistiques de l'IronOcr sont distribuées sous forme de packages NuGet. 'dotnet add package IronOcr.Languages.German' installe la prise en charge de l'allemand. Il n'y a pas de placement manuel de fichiers ou de chemins d'accès aux répertoires.
La migration de l'API OCR de Klippa vers IronOCR nécessite-t-elle des modifications de l'infrastructure de déploiement ?
IronOCR nécessite moins de changements d'infrastructure que l'API OCR de Klippa. Il n'y a pas de chemins binaires SDK, de placements de fichiers de licence ou de configurations de serveurs de licence. Le package NuGet contient le moteur OCR complet, et la clé de licence est une chaîne de caractères définie dans le code de l'application.
Comment configurer les licences IronOCR après la migration ?
Attribuer IronOcr.License.LicenseKey = "YOUR-KEY" dans le code de démarrage de l'application. Dans Docker ou Kubernetes, stockez la clé dans une variable d'environnement et lisez-la au démarrage. Utilisez License.IsValidLicense pour valider avant d'accepter le trafic.
IronOcr peut-il traiter les PDF de la même manière que Klippa ?
Oui, IronOCR lit aussi bien les PDF natifs que les PDF numérisés. Instanciez IronTesseract, appelez ocr.Read(input) où l'input est un chemin PDF ou OcrPdfInput, et itérez les pages OcrResult. Aucun pipeline de rendu PDF séparé n'est nécessaire.
Comment IronOcr gère-t-il le threading dans le cadre d'un traitement à haut volume ?
IronTesseract est sûr pour l'instanciation par thread. Créez une instance par thread dans un Parallel.ForEach ou un Task pool, exécutez l'OCR simultanément et disposez de chaque instance lorsque vous avez terminé. Aucun état global ou verrouillage n'est nécessaire.
Quels formats de sortie IronOCR prend-il en charge après l'extraction du texte ?
IronOCR renvoie des résultats structurés comprenant le texte, les coordonnées des mots, les scores de confiance et la structure des pages. Les options d'exportation comprennent le texte brut, le PDF interrogeable et les objets de résultats structurés pour le traitement en aval.
La tarification d'IronOCR est-elle plus prévisible que celle de l'API OCR de Klippa pour la mise à l'échelle des charges de travail ?
IronOCR utilise des licences perpétuelles forfaitaires sans frais par page ou par volume. Que vous traitiez 10 000 ou 10 millions de pages, le coût de la licence reste constant. Les options de licence en volume et en équipe sont disponibles sur la page de tarification d'IronOcr.
Qu'advient-il de mes tests existants après la migration de l'API Klippa OCR vers IronOcr ?
Les tests qui vérifient le contenu du texte extrait doivent continuer à passer après la migration. Les tests qui valident les modèles d'appel d'API ou le cycle de vie des objets COM devront être mis à jour pour refléter le modèle d'initialisation et de résultat plus simple d'IronOcr.

