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 all commits
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
9 changes: 8 additions & 1 deletion sdk/formrecognizer/Azure.AI.FormRecognizer/src/FieldValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ public readonly struct FieldValue
{
private readonly FieldValue_internal _fieldValue;
private readonly IReadOnlyList<ReadResult> _readResults;
private readonly bool _isBusinessCard;

internal FieldValue(FieldValue_internal fieldValue, IReadOnlyList<ReadResult> readResults)
: this(fieldValue, readResults, false) { }

internal FieldValue(FieldValue_internal fieldValue, IReadOnlyList<ReadResult> readResults, bool isBusinessCard)
: this()
{
ValueType = fieldValue.Type;
_fieldValue = fieldValue;
_readResults = readResults;
_isBusinessCard = isBusinessCard;
}

/// <summary>
Expand Down Expand Up @@ -334,7 +339,9 @@ public IReadOnlyList<FormField> AsList()
List<FormField> fieldList = new List<FormField>();
foreach (var fieldValue in _fieldValue.ValueArray)
{
fieldList.Add(new FormField(null, fieldValue, _readResults));
// Business card has a special condition on how to calculate pages
// so we need to tell the FormField that it is from BusinessCards
fieldList.Add(new FormField(null, fieldValue, _readResults, _isBusinessCard));
}

return fieldList;
Expand Down
33 changes: 28 additions & 5 deletions sdk/formrecognizer/Azure.AI.FormRecognizer/src/FormField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal FormField(string name, int pageNumber, KeyValuePair field, IReadOnlyLis
Value = new FieldValue(new FieldValue_internal(field.Value.Text), readResults);
}

internal FormField(string name, FieldValue_internal fieldValue, IReadOnlyList<ReadResult> readResults)
internal FormField(string name, FieldValue_internal fieldValue, IReadOnlyList<ReadResult> readResults, bool isBusinessCard = default)
{
Confidence = fieldValue.Confidence ?? Constants.DefaultConfidenceValue;
Name = name;
Expand All @@ -56,13 +56,12 @@ 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 = isBusinessCard ? CalculatePage(fieldValue) : fieldValue.Page.Value;

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

Value = new FieldValue(fieldValue, readResults);
Value = new FieldValue(fieldValue, readResults, isBusinessCard);
}

/// <summary>
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ private static RecognizedFormCollection ConvertToRecognizedForms(AnalyzeResult a
List<RecognizedForm> businessCards = new List<RecognizedForm>();
for (int i = 0; i < analyzeResult.DocumentResults.Count; i++)
{
businessCards.Add(new RecognizedForm(analyzeResult.DocumentResults[i], analyzeResult.PageResults, analyzeResult.ReadResults, default));
businessCards.Add(new RecognizedForm(analyzeResult.DocumentResults[i], analyzeResult.PageResults, analyzeResult.ReadResults, default, isBusinessCards: true));
}
return new RecognizedFormCollection(businessCards);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal RecognizedForm(PageResult pageResult, IReadOnlyList<ReadResult> readRes
}

internal RecognizedForm(DocumentResult documentResult, IReadOnlyList<PageResult> pageResults, IReadOnlyList<ReadResult> readResults, string modelId)
: this(documentResult, pageResults, readResults, modelId, false) { }

internal RecognizedForm(DocumentResult documentResult, IReadOnlyList<PageResult> pageResults, IReadOnlyList<ReadResult> readResults, string modelId, bool isBusinessCards)
{
// Recognized form from a model trained with labels.
FormType = documentResult.DocType;
Expand All @@ -42,7 +45,7 @@ internal RecognizedForm(DocumentResult documentResult, IReadOnlyList<PageResult>

Fields = documentResult.Fields == null
? new Dictionary<string, FormField>()
: ConvertSupervisedFields(documentResult.Fields, readResults);
: ConvertSupervisedFields(documentResult.Fields, readResults, isBusinessCards);
Pages = ConvertSupervisedPages(pageResults, readResults);
ModelId = documentResult.ModelId.HasValue ? documentResult.ModelId.Value.ToString() : modelId;
FormTypeConfidence = documentResult.DocTypeConfidence ?? Constants.DefaultConfidenceValue;
Expand Down Expand Up @@ -118,15 +121,15 @@ private static IReadOnlyDictionary<string, FormField> ConvertUnsupervisedFields(
return fieldDictionary;
}

private static IReadOnlyDictionary<string, FormField> ConvertSupervisedFields(IReadOnlyDictionary<string, FieldValue_internal> fields, IReadOnlyList<ReadResult> readResults)
private static IReadOnlyDictionary<string, FormField> ConvertSupervisedFields(IReadOnlyDictionary<string, FieldValue_internal> fields, IReadOnlyList<ReadResult> readResults, bool isBusinessCards)
{
Dictionary<string, FormField> fieldDictionary = new Dictionary<string, FormField>();

foreach (var field in fields)
{
fieldDictionary[field.Key] = field.Value == null
? null
: new FormField(field.Key, field.Value, readResults);
: new FormField(field.Key, field.Value, readResults, isBusinessCards);
}

return fieldDictionary;
Expand Down
Binary file not shown.
Loading