# Thursday, August 1, 2019

GCast 59:

Cognitive Services Text Recognition service

Learn to extract text from an image using the new Text Recognition service.

Thursday, August 1, 2019 11:53:50 PM (GMT Daylight Time, UTC+01:00)
# Tuesday, July 30, 2019

Frampton (6)A father and son sharing a passion is a wonderful thing.

Sunday night in Chicago, Jason Bonham's Led Zeppelin experience opened for Peter Frampton, where Jason played tribute to his father. Jason is the son of the late Zeppelin drummer John Bonham and he has assembled a group that sounds exactly like his father's band - down to the musical arrangements and a voice replica of Robert Plante.

Bonham's band would have been a great concert on its own, but the main attraction was Peter Frampton, the 69-year-old singer / guitarist, who recorded his first hit at the age of 18 with Humble Pie.

The thing I love about Peter Frampton is that he continued to evolve, without rejecting his past.

Frampton enjoyed his greatest commercial success in the 1970s, culminating with the recording and release of "Frampton Comes Alive" - Rolling Stone Magazine's 1976 album of the year.

Frampton (9)Sunday night in Chicago, Frampton drew extensively from his hits of that era - "Baby, I Love Your Way", "Do You Feel Like We Do", "Show Me the Way", ...), even opening with "Something's Happening", the song that opened his legendary live album; but he also exposed us to much of the new material on which he has been working the past 4 decades. He played from his cover of Soundgarden's "Black Hole Sun" from his Grammy-winning 2006 album "Fingerprints" and several tracks from his recent "All Blues" album.

Between songs, he charmed us with stories of his career and the musicians with whom he has interacted.

His 3-song encore consisted of songs from "Humble Pie", the band he co-founded at the age of 18.

No artist has become more associated with the Talk Box than Peter Frampton and he used it in this show - but sparingly enough that it remained fresh and not just a gimmick to prop up his songs.

DGAndTimThis was Frampton's final tour. He is retiring due to a diagnosed with a degenerative muscle disorder that will ultimately impair his ability to play guitar. But there was no evidence of any health problems at this show. His energy was high, and his playing was flawless. He and his band were on stage for nearly 2 hours.

I was excited to finally see Peter Frampton in concert - decades after originally discovering his music. And more excited that I shared the experience with my 25-year-old son, who seemed by far the youngest attendee.

A father and son sharing a passion is a wonderful thing.

Tuesday, July 30, 2019 2:36:44 PM (GMT Daylight Time, UTC+01:00)
# Monday, July 29, 2019

Episode 573

Ruth Yakubu on Machine Learning tools in Azure

Cloud Developer Advocate Ruth Yakubu describes Machine Learning Services and other new ML tools available in Azure.

Monday, July 29, 2019 8:48:00 AM (GMT Daylight Time, UTC+01:00)
# Sunday, July 28, 2019

HandfulOfDustTony Last appears to have a good life. He is are young and wealthy and married to a beautiful woman and he enjoys spending time at his family's country home. But Tony's wife Brenda is unfaithful; Tony is the only one unaware of Brenda's affair with John Beaver, the dull and lazy son of a well-to-do businesswoman.

Tony's biggest mistake is that he trusts his wife and expects she will soon spend more time at home, instead of traveling to London every weekend. As a result, he is unprepared when he learns of her betrayal. When she demands a divorce, he leaves his home in England, completely abandoning his comfort zone to cross the ocean and to search for a lost city in South America.

None of the characters are particularly likeable. Most are concerned with the self-absorbed, meaningless social life of 1930s high society England. Even Tony, for whom the reader feels the most sympathy, is shallow and too comfortable to ever consider leaving home until he is shocked into doing so by his impending divorce. Brenda, of course, is horrible. Not only is she cheating on her devoted husband; she cares so little for their young son that she expresses relief when she hears of his death, because she initially feared she was learning of the death of her lover.

The final third of the novel, in which Tony struggles through the South American jungles, seem a departure from the rest of the novel; but this section underscore how  much Tony's life has been turned upside down by the betrayal of a frivolous wife.

Evelyn Waugh's talent for satire is on full display in this A Handful of Dust. He mocks the English upper class, exposing many of them as frivolous, selfish, and cold-hearted.

This was an uncomfortable novel for me. Tony reminded me too much of my younger self, when I placed trust in someone I should not have. When I was betrayed, I was paralyzed by my own indecision, waiting for her to come to her senses. Like Tony, I was too focused inward and on my past and on my own comfortable routine to recognize what was happening and to prevent it. Tony's quest ended when he ran far from home, placing his trust in an incompetent explorer - a decision that cost him his freedom and his life deep in the Brazilian rain forest.

Hopefully, I am now smarter and have learned better than Tony how to control my own destiny.

Sunday, July 28, 2019 11:24:20 PM (GMT Daylight Time, UTC+01:00)
# Saturday, July 27, 2019

MrsDallowayNothing much happens in Virginia Woolf's Mrs. Dalloway\.

The entire book takes place on a single day in 1923 London. Clarissa Dalloway is preparing to host an evening dinner party; and Septimus Warren Smith is contemplating suicide, as he reflects on his experience fighting in World War I and watching his best friend Evans die shortly before the armistice. Septimus's wife Lucrezia wonders why he acts so strangely when the doctor said there is nothing wrong with him.

For every few lines of conversation, we get many paragraphs of inner monologue.

Mostly, the story consists of flashbacks: Septimus recalls his friendship with Evans and Clarissa remembers her old lover Peter (to whom she refused marriage, settling instead on the government bureaucrat Richard Dalloway) and her friend Sally (with whom she once shared a romantic kiss). The day is complicated when Peter shows up after years in India.

Septimus and Clarissa never meet in the novel, but they are tied together by their obsession with their own past and by a long-past relationship with a same-sex friend.

We also get glimpses into the lives and minds of other people - primarily through their connections with Clarissa Dalloway, even though that connection is often quite thin.

Woolf's stream-of-consciousness writing style and the lack of present-day action sometimes makes the book difficult to follow.

We shift from the thoughts of one person to another; and from the present to the past. It's all very confusing. But pay close attention. And we get commentaries on a wide range of topics:

  • The role and status of marriage in society: A woman attains higher status by marrying, but she loses much of her identity to her husband
  • Society's attitudes toward mental illness, specifically PTSD suffered by veterans
  • The effect our past choices and circumstances have on current lives
  • The inevitable movement of time and the importance of how we spend it
  • The shallow lifestyle embraced by many in high society.

There is much to absorb here. And Ms. Woolf's prose is enjoyable.

Saturday, July 27, 2019 7:34:05 PM (GMT Daylight Time, UTC+01:00)
# Thursday, July 25, 2019

GCast 58:

Creating and Deploying Azure Resources with ARM Templates

Learn how to generate an ARM template and use it to create and deploy resources to Azure.

Azure | DevOps | GCast | Screencast | Video
Thursday, July 25, 2019 10:34:22 PM (GMT Daylight Time, UTC+01:00)
# Saturday, July 20, 2019

DeliveranceFour middle-class suburbanites decide to get away from society for a weekend. They hope to break the tedium of their daily lives and paddle through the uncharted rivers in the hills of Georgia.

But on the second day, they are attacked, rescued, and further terrorized.

With no one to help them, they take matters into their own heads, resulting in a legal and moral crisis.

This is the scenario of James Dickey's novel Deliverance.

It is a story of survival and suspense and ambiguity and self-doubt; of the power of nature; and the brutality of man.

Dickey does a masterful job shifting between descriptions of the power and beauty of nature and building tension within the story.

I read the book in a single sitting. By the end of the it, I was emotionally drained. Dickey is known mostly as a poet, but this novel takes us on a dark and deadly journey that is impossible to forget. The attack in the book became one the most memorable scenes in movie history, when John Boorman turned the novel into a movie 2 years later.

Set aside some time to read this novel and ask yourself: What would you do in these circumstances?

Saturday, July 20, 2019 9:44:00 AM (GMT Daylight Time, UTC+01:00)
# Thursday, July 18, 2019

GCast 57:

Azure Data Factory GitHub Deployment

Learn how to set up automated deployment from a GitHub repository to an Azure Data Factory.

Azure | GCast | GitHub | Screencast | Video
Thursday, July 18, 2019 11:53:00 AM (GMT Daylight Time, UTC+01:00)
# Wednesday, July 17, 2019

In a recent article, I introduced you to the "Recognize Text" API that returns the text in an image - process known as "Optical Character Recognition", or "OCR".

In this article, I will show how to call this API from a .NET application.

Recall that the "Recognize Text" API consists of two web service calls:

We call the "Recognize Text" web service and pass an image to begin the process.

We call the "Get Recognize Text Operation Result" web service to check the status of the processing and retrieive the resulting text, when the process is complete.

The sample .NET application

If you want to follow along, the code is available in the RecognizeTextDemo found in this GitHub repository.

To get started, you will need to create a Computer Vision key, as described here.

Creating this service gives you a URI endpoint to call as a web service, and an API key, which must be passed in the header of web service calls.

The App

To run the app, you will need to copy the key created above into the App.config file. Listing 1 shows a sample config file:

Listing 1:

<configuration>
   <appSettings>
     <add key="ComputerVisionKey" value="5070eab11e9430cea32254e3b50bfdd5" />
   </appSettings>
 </configuration>
  

You will also need an image with some text in it. For this demo, we will use the image shown in Fig. 1.

rt01-Kipling
Fig. 1

When you run the app, you will see the screen in Fig. 2.

rt02-Form1
Fig. 2

Press the [Get File] button and select the saved image, as shown in Fig. 3.

rt03-SelectImage
Fig. 3

Click the [Open] button. The Open File Dialog closes, the full path of the image is displays on the form, and the [Start OCR] button is enabled, as shown in Fig. 4.

rt04-Form2
Fig. 4

Click the [Start OCR] button to call a service that starts the OCR. If an error occurs, it is possible that you did not configure the key correctly or that you are not connected to the Internet.

When the service call returns, the URL of the "Get Text" service displays (beneath the "Location Address" label), and the [Get Text] button is enabled, as shown in Fig. 5.

rt05-Form3
Fig. 5

Click the [Get Text] button. This calls the Location Address service and displays the status. If the status is "Succeeded", it displays the text in the image, as shown in Fig. 6.

rt06-Form4
Fig. 6

## The code

Let's take a look at the code in this application. It is all written in C#. The relevant parts are the calls to the two web service: "Recognize Text" and "Get Recognize Text Operation Result". The first call kicks off the OCR job; the second call returns the status of the job and returns the text found, when complete.

The code is in the TextService static class.

This class has a constant: visionEndPoint, which is the base URL of the Computer Vision Cognitive Service you created above. The code in the repository is in Listing 2. You may need to modify the URL, if you created your service in a different region.

Listing 2:

const string visionEndPoint = "https://westus.api.cognitive.microsoft.com/";
  

### Recognize Text

The call to the "Recognize Text" API is in Listing 1:

Listing 3:

public static async Task<string> GetRecognizeTextOperationResultsFromFile(string imageLocation, string computerVisionKey)
{
    var cogSvcUrl = visionEndPoint + "vision/v2.0/recognizeText?mode=Printed";
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", computerVisionKey);
    HttpResponseMessage response;
    // Convert image to a Byte array
    byte[] byteData = null;
    using (FileStream fileStream = new FileStream(imageLocation, FileMode.Open, FileAccess.Read))
    {
        BinaryReader binaryReader = new BinaryReader(fileStream);
        byteData = binaryReader.ReadBytes((int)fileStream.Length);
    }

    // Call web service; pass image; wait for response
    using (ByteArrayContent content = new ByteArrayContent(byteData))
    {
        content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        response = await client.PostAsync(cogSvcUrl, content);
    }

    // Read results
    RecognizeTextResult results = null;
    if (response.IsSuccessStatusCode)
    {
        var data = await response.Content.ReadAsStringAsync();
        results = JsonConvert.DeserializeObject<RecognizeTextResult>(data);
    }
    var headers = response.Headers;
    var locationHeaders = response.Headers.GetValues("Operation-Location");
    string locationAddress = "";
    IEnumerable<string> values;
    if (headers.TryGetValues("Operation-Location", out values))
    {
        locationAddress = values.First();
    }
    return locationAddress;
}
  

The first thing we do is construct the specific URL of this service call.

Then we use the System.Net.Http library to submit an HTTP POST request to this URL, passing in the image as an array of bytes in the body of the request. For more information on passing a binary file to a web service, see this article.

When the response returns, we check the headers for the "Operation-Location", which is the URL of the next web service to call. The URL contains a GUID that uniquely identifies this job. We save this for our next  call.

Get Recognize Text Operation Result

After kicking of the OCR, we need to call a different service to check the status and get the results. The code in Listing 4 does this.

Listing 4:

public static async Task<RecognizeTextResult> GetRecognizeTextOperationResults(string locationAddress, string computerVisionKey) 
 { 
    var client = new HttpClient(); 
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", computerVisionKey); 
    var response = await client.GetAsync(locationAddress); 
    RecognizeTextResult results = null; 
    if (response.IsSuccessStatusCode) 
    { 
        var data = await response.Content.ReadAsStringAsync(); 
        results = JsonConvert.DeserializeObject<RecognizeTextResult>(data); 
    } 
    return results; 
 }
  

This code is much simpler because it is an HTTP GET and we don't need to pass anything in the request body.

We simply submit an HTTP GET request and use the Newtonsoft.Json libary to convert the response to a string.

Here is the complete code in the TextService class:

Listing 5:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using TextLib.Models;

namespace TextLib
{

    public static class TextService
    {
        const string visionEndPoint = "https://westus.api.cognitive.microsoft.com/";

public static async Task<string> GetRecognizeTextOperationResultsFromFile(string imageLocation, string computerVisionKey)
{
    var cogSvcUrl = visionEndPoint + "vision/v2.0/recognizeText?mode=Printed";
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", computerVisionKey);
    HttpResponseMessage response;
    // Convert image to a Byte array
    byte[] byteData = null;
    using (FileStream fileStream = new FileStream(imageLocation, FileMode.Open, FileAccess.Read))
    {
        BinaryReader binaryReader = new BinaryReader(fileStream);
        byteData = binaryReader.ReadBytes((int)fileStream.Length);
    }

    // Call web service; pass image; wait for response
    using (ByteArrayContent content = new ByteArrayContent(byteData))
    {
        content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        response = await client.PostAsync(cogSvcUrl, content);
    }

    // Read results
    RecognizeTextResult results = null;
    if (response.IsSuccessStatusCode)
    {
        var data = await response.Content.ReadAsStringAsync();
        results = JsonConvert.DeserializeObject<RecognizeTextResult>(data);
    }
    var headers = response.Headers;
    var locationHeaders = response.Headers.GetValues("Operation-Location");
    string locationAddress = "";
    IEnumerable<string> values;
    if (headers.TryGetValues("Operation-Location", out values))
    {
        locationAddress = values.First();
    }
    return locationAddress;
}

        public static async Task<RecognizeTextResult> GetRecognizeTextOperationResults(string locationAddress, string computerVisionKey)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", computerVisionKey);
            var response = await client.GetAsync(locationAddress);
            RecognizeTextResult results = null;
            if (response.IsSuccessStatusCode)
            {
                var data = await response.Content.ReadAsStringAsync();
                results = JsonConvert.DeserializeObject<RecognizeTextResult>(data);
            }
            return results;
        }

    }
}
  

The remaining code

There is other code in this application to do things like select the file from disk and loop through the JSON to concatenate all the text; but this code is very simple and (hopefully) self-documenting. You may choose other ways to get the file and handle the JSON in the response.

In this article, I've focused on the code to manage the Cognitive Services calls and responses to those calls in order to get the text from a picture of text.

Wednesday, July 17, 2019 10:51:00 AM (GMT Daylight Time, UTC+01:00)
# Tuesday, July 16, 2019

Sometimes a web service requires us to pass a binary file, such as an image in the request body.

To do this, we need to submit the request with the POST verb, because other verbs - most notably "GET" - do not contain a body.

One simple web service that accepts a binary file is the Cognitive Services Image Analysis API. This API is fully documented here.

I created a console application (the simplest .NET app I can think of) to demonstrate how to pass the binary image to the web service. This application is named "ImageAnalysisConsoleAppDemo" and is included in my Cognitive Services demos, which you can download here.

Assumptions

Before you get started, you will need to create a Computer Vision Cognitive Service, as described here.

I have hard-coded the file name and location, along with the Cognitive Services URL, but you can change these to match what you are using. You will also need to add your API key to the App.config file.

The code

The first thing we need to do is to read the file and convert it into an array of bytes. The code to do this is in Listing 1 below.

Listing 1:

byte[] byteData; ;
using (FileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read))
{
    BinaryReader = new BinaryReader(fileStream);
    byteData = binaryReader.ReadBytes((int)fileStream.Length);
}
  

Next, we call the web service, passing the byte array. The System.Net.Http client library helps us to make this call. Notice the "using" construct that converts the byte array into a ByteArrayContent object that is required by the library.

Within that "using", we make an asynchronous call to the web service and capture the results.

Listing 2 shows this code.

Listing 2:

var cogSvcUrl = "https://westus.api.cognitive.microsoft.com/vision/v2.0/analyze?visualFeatures=Description&language=en"; 
HttpClient client = new HttpClient(); 
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", computerVisionKey); 
HttpResponseMessage response; 
using (ByteArrayContent content = new ByteArrayContent(byteData)) 
{ 
    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 
    response = await client.PostAsync(cogSvcUrl, content); 
}
  

Finally, we convert the results to a string, as shown in Listing 3. This web service returns JSON containing either information about the image or an error message.

Listing 3:

string webServiceResponseContent = await response.Content.ReadAsStringAsync();
  

Here is the full code:

Listing 4:

using System;
using System.Configuration;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace ImageAnalysisConsoleAppDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            MainAsync().Wait();
            Console.ReadLine();
        }

        static async Task MainAsync()
        {
            string key = GetKey();
            string imageFilePath = @"c:\test\kittens.jpg";
            if (!File.Exists(imageFilePath))
            {
                Console.WriteLine("File {0} does not exist", imageFilePath);
                return;
            }
            string results = await GetRecognizeTextOperationResultsFromFile(imageFilePath, key);
            Console.WriteLine(results);
        }


        public static async Task<string> GetRecognizeTextOperationResultsFromFile(string imageFilePath, string computerVisionKey)
        {
            // Convert file into Byte Array
            byte[] byteData; ;
            using (FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read))
            {
                BinaryReader binaryReader = new BinaryReader(fileStream);
                byteData = binaryReader.ReadBytes((int)fileStream.Length);
            }

            // Make web service call. Pass byte array in body
            var cogSvcUrl = "https://westus.api.cognitive.microsoft.com/vision/v2.0/analyze?visualFeatures=Description&language=en";
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", computerVisionKey);
            HttpResponseMessage response;
            using (ByteArrayContent content = new ByteArrayContent(byteData))
            {
                content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                response = await client.PostAsync(cogSvcUrl, content);
            }

            // Get results
            string webServiceResponseContent = await response.Content.ReadAsStringAsync();
            return webServiceResponseContent;
        }

        public static string GetKey()
        {
            string computerVisionKey = ConfigurationManager.AppSettings["ComputerVisionKey"];
            return computerVisionKey;
        }

    }
}
  

Fig. 1 shows the output when analyzing the image displayed in Fig. 2 (saved in “c:\test\kittens.jpg”).

AnalyzeImage
Fig. 1

Kittens
Fig. 2

This code is not complex, but it is not intuitive (at least not, to me). So, it's useful to understand how to write C# code to pass a binary file to a web service.

Tuesday, July 16, 2019 9:00:00 AM (GMT Daylight Time, UTC+01:00)