Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -60,15 +59,11 @@ protected FormRecognizerClient()
[ForwardsClientCalls]
public virtual RecognizeContentOperation StartRecognizeContent(Stream formFileStream, RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
{
// TODO: automate content-type detection
// https://github.com/Azure/azure-sdk-for-net/issues/10329
ResponseWithHeaders<ServiceAnalyzeLayoutAsyncHeaders> response = ServiceClient.RestClient.AnalyzeLayoutAsync(ContentType.Pdf, formFileStream, cancellationToken);
//Response response = ServiceClient.RestClient.AnalyzeLayoutAsync(ContentType.Pdf, formFileStream, cancellationToken);

// TODO: throw Exception if header is not present.
//response.Headers.TryGetValue("Operation-Location", out string operationLocation);
string operationLocation = response.Headers.OperationLocation;
return new RecognizeContentOperation(ServiceClient, operationLocation);
recognizeOptions ??= new RecognizeOptions();
ContentType contentType = recognizeOptions.ContentType ?? DetectContentType(formFileStream, nameof(formFileStream));

ResponseWithHeaders<ServiceAnalyzeLayoutAsyncHeaders> response = ServiceClient.RestClient.AnalyzeLayoutAsync(contentType, formFileStream, cancellationToken);
return new RecognizeContentOperation(ServiceClient, response.Headers.OperationLocation);
}

/// <summary>
Expand All @@ -82,15 +77,11 @@ public virtual RecognizeContentOperation StartRecognizeContent(Stream formFileSt
[ForwardsClientCalls]
public virtual async Task<RecognizeContentOperation> StartRecognizeContentAsync(Stream formFileStream, RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
{
// TODO: automate content-type detection
// https://github.com/Azure/azure-sdk-for-net/issues/10329
ResponseWithHeaders<ServiceAnalyzeLayoutAsyncHeaders> response = await ServiceClient.RestClient.AnalyzeLayoutAsyncAsync(ContentType.Pdf, formFileStream, cancellationToken).ConfigureAwait(false);
//Response response = await ServiceClient.RestClient.AnalyzeLayoutAsyncAsync(ContentType.Pdf, formFileStream, cancellationToken).ConfigureAwait(false);

// TODO: throw Exception if header is not present.
//response.Headers.TryGetValue("Operation-Location", out string operationLocation);
string operationLocation = response.Headers.OperationLocation;
return new RecognizeContentOperation(ServiceClient, operationLocation);
recognizeOptions ??= new RecognizeOptions();
ContentType contentType = recognizeOptions.ContentType ?? DetectContentType(formFileStream, nameof(formFileStream));

ResponseWithHeaders<ServiceAnalyzeLayoutAsyncHeaders> response = await ServiceClient.RestClient.AnalyzeLayoutAsyncAsync(contentType, formFileStream, cancellationToken).ConfigureAwait(false);
return new RecognizeContentOperation(ServiceClient, response.Headers.OperationLocation);
}

/// <summary>
Expand All @@ -106,12 +97,7 @@ public virtual RecognizeContentOperation StartRecognizeContentFromUri(Uri formFi
{
SourcePath_internal sourcePath = new SourcePath_internal(formFileUri.ToString());
ResponseWithHeaders<ServiceAnalyzeLayoutAsyncHeaders> response = ServiceClient.RestClient.AnalyzeLayoutAsync(sourcePath, cancellationToken);
//Response response = ServiceClient.RestClient.AnalyzeLayoutAsync(sourcePath, cancellationToken);

// TODO: throw Exception if header is not present.
//response.Headers.TryGetValue("Operation-Location", out string operationLocation);
string operationLocation = response.Headers.OperationLocation;
return new RecognizeContentOperation(ServiceClient, operationLocation);
return new RecognizeContentOperation(ServiceClient, response.Headers.OperationLocation);
}

/// <summary>
Expand All @@ -127,12 +113,7 @@ public virtual async Task<RecognizeContentOperation> StartRecognizeContentFromUr
{
SourcePath_internal sourcePath = new SourcePath_internal(formFileUri.ToString());
ResponseWithHeaders<ServiceAnalyzeLayoutAsyncHeaders> response = await ServiceClient.RestClient.AnalyzeLayoutAsyncAsync(sourcePath, cancellationToken).ConfigureAwait(false);
//Response response = await ServiceClient.RestClient.AnalyzeLayoutAsyncAsync(sourcePath, cancellationToken).ConfigureAwait(false);

// TODO: throw Exception if header is not present.
//response.Headers.TryGetValue("Operation-Location", out string operationLocation);
string operationLocation = response.Headers.OperationLocation;
return new RecognizeContentOperation(ServiceClient, operationLocation);
return new RecognizeContentOperation(ServiceClient, response.Headers.OperationLocation);
}

#endregion
Expand All @@ -144,18 +125,15 @@ public virtual async Task<RecognizeContentOperation> StartRecognizeContentFromUr
/// </summary>
/// <param name="receiptFileStream">The stream containing the one or more receipts to recognize values from.</param>
/// <param name="receiptLocale"></param>>
/// <param name="contentType"></param>
/// <param name="recognizeOptions"></param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>A <see cref="RecognizeReceiptsOperation"/> to wait on this long-running operation. Its <see cref="RecognizeReceiptsOperation"/>.Value upon successful
/// completion will contain the extracted receipt.</returns>
[ForwardsClientCalls]
public virtual async Task<RecognizeReceiptsOperation> StartRecognizeReceiptsAsync(Stream receiptFileStream, ContentType contentType, string receiptLocale = "en-US", RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
public virtual async Task<RecognizeReceiptsOperation> StartRecognizeReceiptsAsync(Stream receiptFileStream, string receiptLocale = "en-US", RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
{
// TODO: automate content-type detection
// https://github.com/Azure/azure-sdk-for-net/issues/10329

recognizeOptions ??= new RecognizeOptions();
ContentType contentType = recognizeOptions.ContentType ?? DetectContentType(receiptFileStream, nameof(receiptFileStream));

ResponseWithHeaders<ServiceAnalyzeReceiptAsyncHeaders> response = await ServiceClient.RestClient.AnalyzeReceiptAsyncAsync(contentType, receiptFileStream, includeTextDetails: recognizeOptions.IncludeTextContent, cancellationToken).ConfigureAwait(false);
return new RecognizeReceiptsOperation(ServiceClient, response.Headers.OperationLocation);
Expand All @@ -166,18 +144,15 @@ public virtual async Task<RecognizeReceiptsOperation> StartRecognizeReceiptsAsyn
/// </summary>
/// <param name="receiptFileStream">The stream containing the one or more receipts to recognize values from.</param>
/// <param name="receiptLocale"></param>
/// <param name="contentType"></param>
/// <param name="recognizeOptions">Whether or not to include raw page recognition in addition to layout elements.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
/// <returns>A <see cref="RecognizeReceiptsOperation"/> to wait on this long-running operation. Its <see cref="RecognizeReceiptsOperation"/>.Value upon successful
/// completion will contain the extracted receipt.</returns>
[ForwardsClientCalls]
public virtual RecognizeReceiptsOperation StartRecognizeReceipts(Stream receiptFileStream, ContentType contentType, string receiptLocale = "en-US", RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
public virtual RecognizeReceiptsOperation StartRecognizeReceipts(Stream receiptFileStream, string receiptLocale = "en-US", RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
{
// TODO: automate content-type detection
// https://github.com/Azure/azure-sdk-for-net/issues/10329

recognizeOptions ??= new RecognizeOptions();
ContentType contentType = recognizeOptions.ContentType ?? DetectContentType(receiptFileStream, nameof(receiptFileStream));

ResponseWithHeaders<ServiceAnalyzeReceiptAsyncHeaders> response = ServiceClient.RestClient.AnalyzeReceiptAsync(contentType, receiptFileStream, includeTextDetails: recognizeOptions.IncludeTextContent, cancellationToken);
return new RecognizeReceiptsOperation(ServiceClient, response.Headers.OperationLocation);
Expand Down Expand Up @@ -238,11 +213,9 @@ public virtual RecognizeReceiptsOperation StartRecognizeReceiptsFromUri(Uri rece
public virtual RecognizeCustomFormsOperation StartRecognizeCustomForms(string modelId, Stream formFileStream, RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
{
recognizeOptions ??= new RecognizeOptions();
ContentType contentType = recognizeOptions.ContentType ?? DetectContentType(formFileStream, nameof(formFileStream));

// TODO: automate content-type detection
// https://github.com/Azure/azure-sdk-for-net/issues/10329

ResponseWithHeaders<ServiceAnalyzeWithCustomModelHeaders> response = ServiceClient.RestClient.AnalyzeWithCustomModel(new Guid(modelId), ContentType.Pdf, formFileStream, includeTextDetails: recognizeOptions.IncludeTextContent, cancellationToken);
ResponseWithHeaders<ServiceAnalyzeWithCustomModelHeaders> response = ServiceClient.RestClient.AnalyzeWithCustomModel(new Guid(modelId), contentType, formFileStream, includeTextDetails: recognizeOptions.IncludeTextContent, cancellationToken);
return new RecognizeCustomFormsOperation(ServiceClient, modelId, response.Headers.OperationLocation);
}

Expand Down Expand Up @@ -278,11 +251,9 @@ public virtual RecognizeCustomFormsOperation StartRecognizeCustomFormsFromUri(st
public virtual async Task<RecognizeCustomFormsOperation> StartRecognizeCustomFormsAsync(string modelId, Stream formFileStream, RecognizeOptions recognizeOptions = default, CancellationToken cancellationToken = default)
{
recognizeOptions ??= new RecognizeOptions();
ContentType contentType = recognizeOptions.ContentType ?? DetectContentType(formFileStream, nameof(formFileStream));

// TODO: automate content-type detection
// https://github.com/Azure/azure-sdk-for-net/issues/10329

ResponseWithHeaders<ServiceAnalyzeWithCustomModelHeaders> response = await ServiceClient.RestClient.AnalyzeWithCustomModelAsync(new Guid(modelId), ContentType.Pdf, formFileStream, includeTextDetails: recognizeOptions.IncludeTextContent, cancellationToken).ConfigureAwait(false);
ResponseWithHeaders<ServiceAnalyzeWithCustomModelHeaders> response = await ServiceClient.RestClient.AnalyzeWithCustomModelAsync(new Guid(modelId), contentType, formFileStream, includeTextDetails: recognizeOptions.IncludeTextContent, cancellationToken).ConfigureAwait(false);
return new RecognizeCustomFormsOperation(ServiceClient, modelId, response.Headers.OperationLocation);
}

Expand Down Expand Up @@ -318,5 +289,35 @@ public virtual FormTrainingClient GetFormTrainingClient()
}

#endregion Training client

/// <summary>
/// Used as part of argument validation. Detects the <see cref="ContentType"/> of a stream and
/// throws an <see cref="ArgumentException"/> in case of failure.
/// </summary>
/// <param name="stream">The stream to which the content type detection attempt will be performed.</param>
/// <param name="paramName">The original parameter name of the <paramref name="stream"/>. Used to create exceptions in case of failure.</param>
/// <returns>The detected <see cref="ContentType"/>.</returns>
/// <exception cref="ArgumentException">Happens when detection fails or cannot be performed.</exception>
private static ContentType DetectContentType(Stream stream, string paramName)
{
ContentType contentType;

if (!stream.CanSeek)
{
throw new ArgumentException("Content type cannot be detected because stream is not seekable.", paramName);
}

if (!stream.CanRead)
{
throw new ArgumentException("Content type cannot be detected because stream is not readable.", paramName);
}

if (!stream.TryGetContentType(out contentType))
{
throw new ArgumentException("Content type of the stream could not be detected.", paramName);
}

return contentType;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public enum ServiceVersion
#pragma warning restore CA1707 // Identifiers should not contain underscores
}


/// <summary>
/// </summary>
public ServiceVersion Version { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Text;

namespace Azure.AI.FormRecognizer.Models
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ public RecognizeOptions()
/// </summary>
public bool IncludeTextContent { get; set; } = false;

/// <summary>
/// </summary>
public ContentType? ContentType { get; set; } = null;
}
}
102 changes: 102 additions & 0 deletions sdk/formrecognizer/Azure.AI.FormRecognizer/src/StreamExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.IO;
using Azure.AI.FormRecognizer.Models;

namespace Azure.AI.FormRecognizer
{
internal static class StreamExtensions
{
/// <summary>The set of bytes expected to be present at the start of PDF files.</summary>
private static byte[] PdfHeader = new byte[] { 0x25, 0x50, 0x44, 0x46 };

/// <summary>The set of bytes expected to be present at the start of PNG files.</summary>
private static byte[] PngHeader = new byte[] { 0x89, 0x50, 0x4E, 0x47 };

/// <summary>The set of bytes expected to be present at the start of JPEG files.</summary>
private static byte[] JpegHeader = new byte[] { 0xFF, 0xD8 };

/// <summary>The set of bytes expected to be present at the start of TIFF (little-endian) files.</summary>
private static byte[] TiffLeHeader = new byte[] { 0x49, 0x49, 0x2A, 0x00 };

/// <summary>The set of bytes expected to be present at the start of TIFF (big-endian) files.</summary>
private static byte[] TiffBeHeader = new byte[] { 0x4D, 0x4D, 0x00, 0x2A };

/// <summary>
/// Attemps to detect the <see cref="ContentType"/> of a stream of bytes. The algorithm searches through
/// the first set of bytes in the stream and compares it to well-known file signatures.
/// </summary>
/// <param name="stream">The stream to which the content type detection attempt will be performed.</param>
/// <param name="contentType">If the detection is successful, outputs the detected content type. Otherwise, <c>default</c>.</param>
/// <returns><c>true</c> if the detection was successful. Otherwise, <c>false</c>.</returns>
/// <exception cref="NotSupportedException">Happens when <paramref name="stream"/> is not seekable or readable.</exception>
public static bool TryGetContentType(this Stream stream, out ContentType contentType)
{
var originalPosition = stream.Position;
var bytesCount = Math.Min(stream.Length, PdfHeader.Length);

bool hasFoundType = false;
contentType = default;

bool couldBePdf = true;
bool couldBePng = true;
bool couldBeJpeg = true;
bool couldBeTiffLe = true;
bool couldBeTiffBe = true;

Func<byte[], int, bool> isAtEnd = (array, index) => index == array.Length - 1;
Func<byte[], int, byte, bool> hasByteAt = (array, index, expected) => index < array.Length && array[index] == expected;

for (int i = 0; i < bytesCount; i++)
{
byte currentByte = (byte)stream.ReadByte();

couldBePdf = couldBePdf && hasByteAt(PdfHeader, i, currentByte);
if (couldBePdf && isAtEnd(PdfHeader, i))
{
contentType = ContentType.Pdf;
hasFoundType = true;
break;
}

couldBePng = couldBePng && hasByteAt(PngHeader, i, currentByte);
if (couldBePng && isAtEnd(PngHeader, i))
{
contentType = ContentType.Png;
hasFoundType = true;
break;
}

couldBeJpeg = couldBeJpeg && hasByteAt(JpegHeader, i, currentByte);
if (couldBeJpeg && isAtEnd(JpegHeader, i))
{
contentType = ContentType.Jpeg;
hasFoundType = true;
break;
}

couldBeTiffLe = couldBeTiffLe && hasByteAt(TiffLeHeader, i, currentByte);
if (couldBeTiffLe && isAtEnd(TiffLeHeader, i))
{
contentType = ContentType.Tiff;
hasFoundType = true;
break;
}

couldBeTiffBe = couldBeTiffBe && hasByteAt(TiffBeHeader, i, currentByte);
if (couldBeTiffBe && isAtEnd(TiffBeHeader, i))
{
contentType = ContentType.Tiff;
hasFoundType = true;
break;
}
}

stream.Position = originalPosition;

return hasFoundType;
}
}
}
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading