Skip to content

Commit

Permalink
[FR] Add multipage Business card test and update documentation (Azure…
Browse files Browse the repository at this point in the history
…#16018)

* Add multipage BC test + update documentation for BC

* add businesscards bool
  • Loading branch information
maririos authored and annelo-msft committed Feb 17, 2021
1 parent 14850dc commit bb57573
Show file tree
Hide file tree
Showing 18 changed files with 5,226 additions and 153 deletions.
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].

```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
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)
{
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

0 comments on commit bb57573

Please sign in to comment.