Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] Add multipage Business card test and update documentation #16018

Merged
merged 2 commits into from
Oct 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion sdk/formrecognizer/Azure.AI.FormRecognizer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Azure Cognitive Services Form Recognizer is a cloud service that uses machine le
- Recognize Custom Forms - Recognize and extract form fields and other content from your custom forms, using models you trained with your own form types.
- Recognize Form Content - Recognize and extract tables, lines, words, and selection marks like radio buttons and check boxes in forms documents, without the need to train a model.
- Recognize Receipts - Recognize and extract common fields from US receipts, using a pre-trained receipt model.
- Recognize Business Card - Recognize and extract common fields from business cards, using a pre-trained business cards model.

[Source code][formreco_client_src] | [Package (NuGet)][formreco_nuget_package] | [API reference documentation][formreco_refdocs] | [Product documentation][formreco_docs] | [Samples][formreco_samples]

Expand Down Expand Up @@ -103,6 +104,7 @@ var client = new FormRecognizerClient(new Uri(endpoint), new DefaultAzureCredent
- Recognizing form fields and content, using custom models trained to recognize your custom forms. These values are returned in a collection of `RecognizedForm` objects. See example [Recognize Custom Forms](#recognize-custom-forms).
- Recognizing form content, including tables, lines, words, and selection marks like radio buttons and check boxes without the need to train a model. Form content is returned in a collection of `FormPage` objects. See example [Recognize Content](#recognize-content).
- Recognizing common fields from US receipts, using a pre-trained receipt model on the Form Recognizer service. These fields and meta-data are returned in a collection of `RecognizedForm` objects. See example [Recognize Receipts](#recognize-receipts).
- Recognizing common fields from business cards, using a pre-trained business cards model on the Form Recognizer service. These fields and meta-data are returned in a collection of `RecognizedForm` objects. See example [Recognize Business Cards](#recognize-business-cards).

### FormTrainingClient

Expand Down Expand Up @@ -131,6 +133,7 @@ The following section provides several code snippets illustrating common pattern
* [Recognize Content](#recognize-content)
* [Recognize Custom Forms](#recognize-custom-forms)
* [Recognize Receipts](#recognize-receipts)
* [Recognize Business Cards](#recognize-business-cards)
* [Train a Model](#train-a-model)
* [Manage Custom Models](#manage-custom-models)

Expand Down Expand Up @@ -204,7 +207,7 @@ foreach (RecognizedForm form in forms)
```

### Recognize Receipts
Recognize data from US sales receipts using a prebuilt model.
Recognize data from US sales receipts using a prebuilt model. Receipt fields recognized by the service can be found [here][service_recognize_receipt_fields].
maririos marked this conversation as resolved.
Show resolved Hide resolved

```C# Snippet:FormRecognizerSampleRecognizeReceiptFileStream
using (FileStream stream = new FileStream(receiptPath, FileMode.Open))
Expand Down Expand Up @@ -291,6 +294,78 @@ using (FileStream stream = new FileStream(receiptPath, FileMode.Open))
}
```

### Recognize Business Cards
maririos marked this conversation as resolved.
Show resolved Hide resolved
Recognize data from business cards using a prebuilt model. Business card fields recognized by the service can be found [here][service_recognize_business_cards_fields].

```C# Snippet:FormRecognizerSampleRecognizeBusinessCardFileStream
using (FileStream stream = new FileStream(busienssCardsPath, FileMode.Open))
{
RecognizedFormCollection businessCards = await client.StartRecognizeBusinessCardsAsync(stream).WaitForCompletionAsync();

// To see the list of the supported fields returned by service and its corresponding types, consult:
// https://aka.ms/formrecognizer/businesscardfields

foreach (RecognizedForm businessCard in businessCards)
{
FormField ContactNamesField;
if (businessCard.Fields.TryGetValue("ContactNames", out ContactNamesField))
{
if (ContactNamesField.Value.ValueType == FieldValueType.List)
{
foreach (FormField contactNameField in ContactNamesField.Value.AsList())
{
Console.WriteLine($"Contact Name: {contactNameField.ValueData.Text}");

if (contactNameField.Value.ValueType == FieldValueType.Dictionary)
{
IReadOnlyDictionary<string, FormField> contactNameFields = contactNameField.Value.AsDictionary();

FormField firstNameField;
if (contactNameFields.TryGetValue("FirstName", out firstNameField))
{
if (firstNameField.Value.ValueType == FieldValueType.String)
{
string firstName = firstNameField.Value.AsString();

Console.WriteLine($" First Name: '{firstName}', with confidence {firstNameField.Confidence}");
}
}

FormField lastNameField;
if (contactNameFields.TryGetValue("LastName", out lastNameField))
{
if (lastNameField.Value.ValueType == FieldValueType.String)
{
string lastName = lastNameField.Value.AsString();

Console.WriteLine($" Last Name: '{lastName}', with confidence {lastNameField.Confidence}");
}
}
}
}
}
}

FormField emailFields;
if (businessCard.Fields.TryGetValue("Emails", out emailFields))
{
if (emailFields.Value.ValueType == FieldValueType.List)
{
foreach (FormField emailField in emailFields.Value.AsList())
{
if (emailField.Value.ValueType == FieldValueType.String)
{
string email = emailField.Value.AsString();

Console.WriteLine($" Email: '{email}', with confidence {emailField.Confidence}");
}
}
}
}
}
}
```

### Train a Model
Train a machine-learned model on your own form types. The resulting model will be able to recognize values from the types of forms it was trained on.

Expand Down Expand Up @@ -484,6 +559,7 @@ Samples showing how to use the Cognitive Services Form Recognizer library are av
- [Recognize form content][recognize_content]
- [Recognize custom forms][recognize_custom_forms]
- [Recognize receipts][recognize_receipts]
- [Recognize business cards][recognize_business_cards]
- [Train a model][train_a_model]
- [Manage custom models][manage_custom_models]
- [Copy a custom model between Form Recognizer resources][copy_custom_models]
Expand Down Expand Up @@ -522,13 +598,16 @@ This project has adopted the [Microsoft Open Source Code of Conduct][code_of_con


[labeling_tool]: https://docs.microsoft.com/azure/cognitive-services/form-recognizer/quickstarts/label-tool
[service_recognize_receipt_fields]: https://aka.ms/formrecognizer/receiptfields
[service_recognize_business_cards_fields]: https://aka.ms/formrecognizer/businesscardfields
[dotnet_lro_guidelines]: https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-longrunning

[logging]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/core/Azure.Core/samples/Diagnostics.md

[recognize_content]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample1_RecognizeFormContent.md
[recognize_custom_forms]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample2_RecognizeCustomForms.md
[recognize_receipts]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample3_RecognizeReceipts.md
[recognize_business_cards]: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample9_RecognizeBusinessCards.md
[train_a_model]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample5_TrainModel.md
[manage_custom_models]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample6_ManageCustomModels.md
[copy_custom_models]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample7_CopyCustomModel.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Azure Cognitive Services Form Recognizer is a cloud service that uses machine le
- [Recognize form content](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample1_RecognizeFormContent.md)
- [Recognize custom forms](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample2_RecognizeCustomForms.md)
- [Recognize receipts](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample3_RecognizeReceipts.md)
- [Recognize business cards](https://github.com/maririos/azure-sdk-for-net/blob/businesscards/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample9_RecognizeBusinessCards.md)
- [Recognize business cards](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample9_RecognizeBusinessCards.md)
- [Train a model](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample5_TrainModel.md)
- [Manage custom models](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample6_ManageCustomModels.md)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ using (FileStream stream = new FileStream(businessCardsPath, FileMode.Open))

To see the full example source files, see:

* [Recognize business cards from URI](https://github.com/maririos/azure-sdk-for-net/blob/businesscards/sdk/formrecognizer/Azure.AI.FormRecognizer/tests/samples/Sample12_RecognizeBusinessCardsFromUri.cs)
* [Recognize business cards from file](https://github.com/maririos/azure-sdk-for-net/blob/businesscards/sdk/formrecognizer/Azure.AI.FormRecognizer/tests/samples/Sample12_RecognizeBusinessCardsFromFile.cs)
* [Recognize business cards from URI](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/tests/samples/Sample12_RecognizeBusinessCardsFromUri.cs)
* [Recognize business cards from file](https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/tests/samples/Sample12_RecognizeBusinessCardsFromFile.cs)

[README]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer#getting-started
[strongly_typing_a_recognized_form]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples/Sample4_StronglyTypingARecognizedForm.md
29 changes: 26 additions & 3 deletions sdk/formrecognizer/Azure.AI.FormRecognizer/src/FormField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,9 @@ internal FormField(string name, FieldValue_internal fieldValue, IReadOnlyList<Re
// TODO: FormEnum<T> ?
FieldBoundingBox boundingBox = new FieldBoundingBox(fieldValue.BoundingBox);

// Issue https://github.com/Azure/azure-sdk-for-net/issues/15845
int page = fieldValue.Page.HasValue ? fieldValue.Page.Value : 1;
int fieldPage = fieldValue.Page.HasValue ? fieldValue.Page.Value : CalculatePage(fieldValue);

ValueData = new FieldData(boundingBox, page, fieldValue.Text, fieldElements);
ValueData = new FieldData(boundingBox, fieldPage, fieldValue.Text, fieldElements);
}

Value = new FieldValue(fieldValue, readResults);
Expand Down Expand Up @@ -163,5 +162,29 @@ private static FormElement ResolveTextReference(IReadOnlyList<ReadResult> readRe

throw new InvalidOperationException($"Failed to parse element reference: {reference}");
}

/// <summary>
/// Business Cards pre-built model doesn't return a page number for the `ContactNames` field.
/// This function looks into the FieldValue_internal to see if it corresponds to
/// `ContactNames` and verifies that the page value before returning it.
/// </summary>
/// <returns>Page value if the field is `ContactNames` for Business cards. If not, defaults to 1.</returns>
private static int CalculatePage(FieldValue_internal field)
maririos marked this conversation as resolved.
Show resolved Hide resolved
{
int page = 1;
if (field.Type == FieldValueType.Dictionary)
{
IReadOnlyDictionary<string, FieldValue_internal> possibleContactNamesField = field.ValueObject;
if (possibleContactNamesField.Count == 2 && possibleContactNamesField.ContainsKey("FirstName")
&& possibleContactNamesField.ContainsKey("LastName"))
{
if (possibleContactNamesField["FirstName"].Page == possibleContactNamesField["LastName"].Page)
{
page = possibleContactNamesField["FirstName"].Page.Value;
}
}
}
return page;
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ public void StartRecognizeReceiptsFromUriThrowsForNonExistingContent()

[Test]
[TestCase(true)]
[TestCase(false, Ignore = "File not on Github") ]
[TestCase(false) ]
public async Task StartRecognizeBusinessCardsPopulatesExtractedJpg(bool useStream)
{
var client = CreateFormRecognizerClient();
Expand Down Expand Up @@ -885,6 +885,7 @@ public async Task StartRecognizeBusinessCardsPopulatesExtractedJpg(bool useStrea
var contactNames = form.Fields["ContactNames"].Value.AsList();
Assert.AreEqual(1, contactNames.Count);
Assert.AreEqual("Dr. Avery Smith", contactNames.FirstOrDefault().ValueData.Text);
Assert.AreEqual(1, contactNames.FirstOrDefault().ValueData.PageNumber);

var contactNamesDict = contactNames.FirstOrDefault().Value.AsDictionary();

Expand Down Expand Up @@ -934,7 +935,7 @@ public async Task StartRecognizeBusinessCardsPopulatesExtractedJpg(bool useStrea

[Test]
[TestCase(true)]
[TestCase(false, Ignore = "File not on Github")]
[TestCase(false)]
public async Task StartRecognizeBusinessCardsPopulatesExtractedPng(bool useStream)
{
var client = CreateFormRecognizerClient();
Expand Down Expand Up @@ -991,6 +992,7 @@ public async Task StartRecognizeBusinessCardsPopulatesExtractedPng(bool useStrea
var contactNames = form.Fields["ContactNames"].Value.AsList();
Assert.AreEqual(1, contactNames.Count);
Assert.AreEqual("Dr. Avery Smith", contactNames.FirstOrDefault().ValueData.Text);
Assert.AreEqual(1, contactNames.FirstOrDefault().ValueData.PageNumber);

var contactNamesDict = contactNames.FirstOrDefault().Value.AsDictionary();

Expand Down Expand Up @@ -1091,6 +1093,7 @@ public async Task StartRecognizeBusinessCardsCanParseBlankPage()

Assert.AreEqual(0, blankPage.Lines.Count);
Assert.AreEqual(0, blankPage.Tables.Count);
Assert.AreEqual(0, blankPage.SelectionMarks.Count);
}

[Test]
Expand Down Expand Up @@ -1121,6 +1124,68 @@ public void StartRecognizeBusinessCardsFromUriThrowsForNonExistingContent()
Assert.AreEqual("FailedToDownloadImage", ex.ErrorCode);
}

[Test]
[TestCase(true)]
[TestCase(false, Ignore ="File not on Github")]
public async Task StartRecognizeBusinessCardsCanParseMultipageForm(bool useStream)
{
var client = CreateFormRecognizerClient();
var options = new RecognizeBusinessCardsOptions() { IncludeFieldElements = true };
RecognizeBusinessCardsOperation operation;

if (useStream)
{
using var stream = FormRecognizerTestEnvironment.CreateStream(TestFile.BusinessMultipage);
using (Recording.DisableRequestBodyRecording())
{
operation = await client.StartRecognizeBusinessCardsAsync(stream, options);
}
}
else
{
var uri = FormRecognizerTestEnvironment.CreateUri(TestFile.BusinessMultipage);
operation = await client.StartRecognizeBusinessCardsFromUriAsync(uri, options);
}

RecognizedFormCollection recognizedForms = await operation.WaitForCompletionAsync(PollingInterval);
maririos marked this conversation as resolved.
Show resolved Hide resolved

Assert.AreEqual(2, recognizedForms.Count);

for (int formIndex = 0; formIndex < recognizedForms.Count; formIndex++)
{
var recognizedForm = recognizedForms[formIndex];
var expectedPageNumber = formIndex + 1;

Assert.NotNull(recognizedForm);

ValidatePrebuiltForm(
recognizedForm,
includeFieldElements: true,
expectedFirstPageNumber: expectedPageNumber,
expectedLastPageNumber: expectedPageNumber);

// Basic sanity test to make sure pages are ordered correctly.
Assert.IsTrue(recognizedForm.Fields.ContainsKey("Emails"));
FormField sampleFields = recognizedForm.Fields["Emails"];
Assert.AreEqual(FieldValueType.List, sampleFields.Value.ValueType);
var field = sampleFields.Value.AsList().Single();

if (formIndex == 0)
{
Assert.AreEqual("johnsinger@contoso.com", field.ValueData.Text);
}
else if (formIndex == 1)
{
Assert.AreEqual("avery.smith@contoso.com", field.ValueData.Text);
}

// Check for ContactNames.Page value
Assert.IsTrue(recognizedForm.Fields.ContainsKey("ContactNames"));
FormField contactNameField = recognizedForm.Fields["ContactNames"].Value.AsList().Single();
Assert.AreEqual(formIndex+1, contactNameField.ValueData.PageNumber);
}
}

#endregion

#region StartRecognizeCustomForms
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public static class TestFile
/// <summary>A business card file.</summary>
public const string BusinessCardtPng = "businessCard.png";

/// <summary>A file with two business cards, one per page.</summary>
public const string BusinessMultipage = "multipleBusinessCards.pdf";

/// <summary>A basic invoice file.</summary>
public const string InvoicePdf = "Invoice_1.pdf";

Expand Down
Loading