April 05, 2016

Call the Tone Analyzer Service from C#

After I figured out how call the Tone Analyzer service from Postman (detailed in my previous post), the next step was to call it from an app and do something with the results.

To get started, I decided to create a simple C# app that calls the service. You enter the text you want to analyze and click the Analyze button. The code calls the service, returns the analysis results in JSON, and displays the results in three charts. Those charts correspond to the three categories of analysis that the service does: emotion, writing/language, and social.

This app is was inspired by the Tone Analyzer demo page. It’s an old school Windows forms app, but that means it’s easy to download and run. The code for it is on GitHub.

PREREQUISITES
  • Visual Studio – If you don’t have Visual Studio installed, you can install the community version for free from the VS downloads page.
  • Service Credentials – You need an instance of the Tone Analyzer service and the service credentials. You’ll need to create an instance of the service on Bluemix, which is IBM’s cloud platform. It’s a pretty straightforward process, and you get a 30-day trial to test it out. Just follow the first steps in my previous blog post under the Create the Service & Credentials section.
ABOUT THE APP
To run the app on your machine, just download it, open it in Visual Studio and click Start. Be sure to put the username and password for your instance of the service in the text fields. You might want to set the Text property so you don’t have to enter them every time you run the code.

The first thing you’ll probably notice is that all of the code is in the btnAnalyze_Click event. Not a best practice by any means, but it makes it easy to see what’s going on. The one dependency this app has is on Json.NET. This is a free, open source framework for working with JSON.

The code has two primary parts: The first part of the code creates a WebRequest to communicate with the service and sets the credentials. Then it sends the request, gets the response JSON back, and shows it in the second text box.
  string baseURL;
  string username;
  string password;

  // Set the URL to the Tone Analyzer service and creds for the instance of the service
  // Be sure to set these vars or the text controls with the username and password of your instance of the Tone Analyzer service
  baseURL = "https://gateway.watsonplatform.net/tone-analyzer/api/v3/tone?version=2016-05-19&sentences=false";
  username = txtUsername.Text;
  password = txtPassword.Text;

  // Get the data to be analyzed for tone
  string postData = "{\"text\": \"" + txtInput.Text + "\"}";

  // Create the web request
  var request = (HttpWebRequest)WebRequest.Create(baseURL);

  // Configure the BlueMix credentials
  string auth = string.Format("{0}:{1}", username, password);
  string auth64 = Convert.ToBase64String(Encoding.ASCII.GetBytes(auth));
  string credentials = string.Format("{0} {1}", "Basic", auth64);

  // Set the web request parameters
  request.Headers[HttpRequestHeader.Authorization] = credentials;
  request.Method = "POST";
  request.Accept = "application/json";
  request.ContentType = "application/json";

  byte[] byteArray = Encoding.UTF8.GetBytes(postData);
  // Set the ContentLength property of the WebRequest
  request.ContentLength = byteArray.Length;

  // Get the request stream
  Stream dataStream = request.GetRequestStream();
  // Write the data to the request stream.
  dataStream.Write(byteArray, 0, byteArray.Length);
           
  // Get the response
  WebResponse response = request.GetResponse();
  // Display the status
  Console.WriteLine(((HttpWebResponse)response).StatusDescription);
  // Get the stream containing content returned by the service
  dataStream = response.GetResponseStream();
  // Open the stream using a StreamReader for easy access
  StreamReader reader = new StreamReader(dataStream);
  // Read and format the content
  string responseFromServer = reader.ReadToEnd();
  responseFromServer = ToneAnalyzerTools.JsonPrettify(responseFromServer);
  // Display the content
  txtOutput.Text = responseFromServer;
The second part of the code first sets up the three chart controls. This app uses the standard chart control that comes with Visual Studio, but I think a third-party chart control like ChartFX would probably generate cleaner and more modern looking charts.

Then the code uses the Json.NET objects to dynamically loop through the returned JSON. Many thanks to Rick Strahl for his blog post Using JSON.NET for dynamic JSON parsing! If not for that post, I’m pretty sure I’d still be trying to figure that out.
  // Dynamically assign the JSON to objects
  JObject DocumentTone = JObject.Parse(responseFromServer);
  JArray ToneCategories = (JArray)DocumentTone["document_tone"]["tone_categories"];
  
  // Loop through the categories returned in the JSON
  dynamic categories = ToneCategories;
  foreach (dynamic category in categories)
  {
      // Random troubleshooting code; did this in the beginning to check values
      Console.WriteLine(category.category_id);
      Console.WriteLine(category.category_name);

      // Add the emotion scores to the chart
      // This is the brute force approach; it's likely this can be done by assigning an array to the datasource of the chart
      // or something elegant like that
      if (category.category_id == "emotion_tone")
      {
          int i = 0;
          foreach (dynamic tone in category.tones)
                     
          {
              switch ((string)tone.tone_id)
              {
                  case "anger":
                      crtEmotion.Series["Emotions"].Points.AddXY((string)tone.tone_name, (double)tone.score);
                      crtEmotion.Series["Emotions"].Points[i].Color = Color.Red;
                      i++;
                      break;
                  case "disgust":
                      crtEmotion.Series["Emotions"].Points.AddXY((string)tone.tone_name, (double)tone.score);
                      crtEmotion.Series["Emotions"].Points[i].Color = Color.Purple;
                      i++;
                      break;
                  case "fear":
                      crtEmotion.Series["Emotions"].Points.AddXY((string)tone.tone_name, (double)tone.score);
                      crtEmotion.Series["Emotions"].Points[i].Color = Color.Green;
                      i++;
                      break;
                  case "joy":
                      crtEmotion.Series["Emotions"].Points.AddXY((string)tone.tone_name, (double)tone.score);
                      crtEmotion.Series["Emotions"].Points[i].Color = Color.Yellow;
                      i++;
                      break;
                  case "sadness":
                      crtEmotion.Series["Emotions"].Points.AddXY((string)tone.tone_name, (double)tone.score);
                      crtEmotion.Series["Emotions"].Points[i].Color = Color.Blue;
                      i++;
                      break;
              }
          }
      }

      // Add the language/writing scores to the chart
      if (category.category_id == "writing_tone")
      {                
          foreach (dynamic tone in category.tones)
          {
              crtLanguage.Series["Language"].Points.AddXY((string)tone.tone_name, (double)tone.score);
          }
      }

      // Add the social scores to the chart
      if (category.category_id == "social_tone")
      {
          foreach (dynamic tone in category.tones)
          {
              crtSocial.Series["Social"].Points.AddXY((string)tone.tone_name, (double)tone.score);
          }
      }
  }

  // Sort the emotion chart by emotion names; it just so happens the alphabetical order matches the priority order
  // The emotions like anger and disgust should be at the top of the chart
  crtEmotion.DataManipulator.Sort(System.Windows.Forms.DataVisualization.Charting.PointSortOrder.Descending, "AxisLabel", "Emotions");

  // Clean up the streams.
  reader.Close();
  dataStream.Close();
  response.Close();
Each tone category – emotion, language, and social – has subcategories. For example, the emotion category has five subcategories: anger, disgust, fear, joy, and sadness. For each subcategory, 0 is the low score and 1 is the high score. You’ll see values returned like 0.039091 and 0.655229.
There’s lots more information about the different systems and algorithms used to calculate the tone scores in the IBM documentation. This blog post also has more details about the different types of tone scores and how they’re modeled.

Note that I did encounter some issues with form resizing after I added the chart controls. Right now, the form resizes such that the chart controls are cut off of the right side of the form and on the bottom.

USE CASES
When I first heard about IBM’s Tone Analyzer service, I was jazzed. I could picture so many ways that functionality could be used in practical ways.
  • Call Center Chat – This service could be integrated into customer service tools like Salesforce’s Live Agent which enables reps to chat with customers. Every time a customer sends text through the chat tool, you could call the service and pass it the customer’s chat content. A rep could see in real-time the emotional state of the customer.
  • Outlook/Word Add-in – The ability to click a button and analyze an email or a document would be awesome. Credit for this idea goes to Paige McAndrew, dev lead at Not Rocket Science. When I was telling her about Tone Analyzer and my app, her first suggestion was an Outlook add-in. Instead of clicking Send, a user could click a button and then decide whether to click Send.
  • Self-Coaching – More and more companies are implementing enterprise social networking tools for internal collaboration and communication. Tools like Chatter (Salesforce), Yammer (Microsoft), and Facebook at Work (Facebook). All those posts mean that everyone is doing a lot of writing. It would be great to be able to click a button and find out the general tenor of your own writing.
I think the real strength of Tone Analyzer is realized when it’s integrated into existing apps. I hope this blog post and the C# app help you get started.

No comments: