Using C# to call Python RESTful API Web Services with Machine Learning Models

Ernest Bonat, Ph.D.
25 min readJan 25, 2019

--

1. Main Questions?
2. Building RESTful API Web Services with the Flask Microframework
3. Testing C# to consume a simple Python RESTful API Web Service
4. Developing Flask RESTful API Web Services for Image Classification
5. Base64 Image Encoding/Decoding — simple and good still!
6. Simple Image Preprocessing
7. Machine Learning Model Deserialization
8. JSON Data Class Encapsulation
9. Debugging Python RESTful API Web Services
10. A Simple Python OOP Design for Machine Learning Projects
11. Implementing Class Interface in Python — a big issue without no reasons!
12. Unit Tests in Machine Learning Projects
13. Image Classification RESTful API Web Service — complete code!
14. Conclusions

Building Microsoft .NET business applications using C# is in high demand for software engineering jobs in the IT market today. In fact, there are many .NET companies doing only C# business applications development. In another vein, Data Analytics jobs using Machine Learning (ML) algorithms with Python/R programming languages have exploded in the last couple of years, especially using the Python Data Ecosystem with 1000+ available libraries and frameworks Python has become a multi-purpose programming language with wide support and a robust community.

Suppose your company would like to use these two completely different technologies for Data Analytics and Image Processing projects, in this case we need to find out how C# developed business applications can call Python Machine Learning trained models. A simple solution that comes to my mind is to create a Python RESTful API web service with these ML models and let C# applications call them using standard HTTP client namespace. Let’s find out the main question to this solution and how to implement it.

1. Main Questions?

Many Data Scientists and Software Engineers use Python/R programming languages for their ML projects today. For example, in Python the final product of a data science workflow are serialized trained model files. These files needs to be deployed somehow to be used for real production bushiness applications. So in this case the main question is: How can C# deserialize a typical Python ML model to be used in .NET business applications? Another question could be if Python deserializes the ML model, how can C# call (consume) them? If we have a good and simple solution to this task, we can deploy these files practically anywhere we’d like to. Of course, a whole set of questions concerning run time performance also require answers: Is this solution stable? Is it fast enough in the selected production environment?

A few notes before we go further:

  • Below you’ll see a proposed approach, of which I post all the C# and Python code in hopes it aids in the development and deployment of trained model.
  • If anyone can find any other possible solutions on how to deserialize Python ML trained model(s) in C#, I would really like to know about it. Feel free to post your results at the end of this blog, and thank you for your work! I think many Data Scientists and Software Engineers would be very grateful.

2. Building RESTful API Web Services with the Flask Microframework

Flask is a simple Python-based microframework that enables developers to quickly build web applications. I have decided to use Flask to develop my Python RESTful API web service. To run a Flask application service the following minimum four lines of code are required.

# import flask microframework library
from flask import Flask

# initialize the flask application
app = Flask(__name__)

if __name__ == "__main__":
# run flask application in debug mode
app.run(debug=True)

How simple can this be? Make sure to change debug=True to False in a production environment. Below is the result after running using the default Flask local web server http://127.0.0.1:5000/.

* Serving Flask app "restful_api_image_classification" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 303-171-272
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

An example of an endpoint api_1() method could be defined as:

@app.route("/api_1", methods=["POST"])
def api_1()
# required implementation code…

In this case the Flask application service from above should be written as:

# import flask microframework library
from flask import Flask

# initialize the flask application
app = Flask(__name__)

# endpoint api_1() with post method
@app.route("/api_1", methods=["POST"])
def api_1()
# required implementation code…
if __name__ == "__main__":
# run flask application in debug mode
app.run(debug=True)

To learn more about Flask feel free to look into this blog paper How to Build RESTful APIs with Python and Flask.

3. Testing C# to consume a simple Python RESTful API Web Service

For this project I’ll be using C# in Visual Studio .NET 2017 with the following references libraries:

  • Json.NET — Json.NET is a popular high-performance JSON framework for .NET.
  • RestSharp — Simple REST and HTTP API Client.

A .NET console application was created to consume the Python RESTful API web service. Let’s create a simple Flask RESTful API endpoint to test how C# calls it. The code below gets the request JSON object and converts it to a response object. If there is no error, the response status code should be 200 (OK). If an error occurs, the exception message is formatted to JSON and the response status code is set to 400 (BAD_REQUEST).

@app.route("/api/v1.0/csharp_python_restfulapi_json", methods=["POST"])
def csharp_python_restfulapi_json():
"""
simple c# test to call python restful api web service
"""
try:
# get request json object
request_json = request.get_json()
# convert to response json object
response = jsonify(request_json)
response.status_code = 200
except:
exception_message = sys.exc_info()[1]
response = json.dumps({"content":exception_message})
response.status_code = 400
return response

As you can see three lines of code have been hardcoded: 200, 400 and the dictionary object {“content”:exception_message} in the except block. This code will be updated later to fix these bad programming practices. Remember, good Data Scientists and Software Engineers don’t hardcode anything in a program! Feel free to read this blog paper on programming best practices: Refactoring Python Code for Machine Learning Projects. Python “Spaghetti Code” Everywhere!

Below is the C# code to call this API endpoint http://localhost:5000/api/v1.0/csharp_python_restfulapi_json.

// Get folder image path
private static string folderImagePath = Properties.Settings.Default.FolderImagePath;

// Declare variables
string uirWebAPI, exceptionMessage, webResponse;

// Set the UIR endpoint link. It should go to the application config file
uirWebAPI = "http://localhost:5000/api/v1.0/csharp_python_restfulapi_json";
exceptionMessage = string.Empty;

// Get web response by calling the CSharpPythonRestfulApiSimpleTest() method
webResponse = csharpPythonRESTfulAPI.CSharpPythonRestfulApiSimpleTest(uirWebAPI, out exceptionMessage);

if (string.IsNullOrEmpty(exceptionMessage))
{
// No errors occurred. Write the string web response
Console.WriteLine(webResponse.ToString());
}
else
{
// An error occurred. Write the exception message
Console.WriteLine(exceptionMessage);
}

The C# code for CSharpPythonRestfulApiSimpleTest() method is shown below. This method will be included and explained later with the class file CSharpPythonRESTfulAPI.cs.

/// <summary>
/// C# test to call Python HttpWeb RESTful API
/// </summary>
/// <param name="uirWebAPI">UIR web api link</param>
/// <param name="exceptionMessage">Returned exception message</param>
/// <returns>Web response string</returns>

public string CSharpPythonRestfulApiSimpleTest(string uirWebAPI, out string exceptionMessage)
{
exceptionMessage = string.Empty;
string webResponse = string.Empty;
try
{
Uri uri = new Uri(uirWebAPI);
WebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
// Build employee test JSON objec
dynamic employee = new JObject();
employee.username = "theUserName";
employee.password = "thePassword";
streamWriter.Write(employee.ToString());
}
HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
webResponse = streamReader.ReadToEnd();
}
}
catch (Exception ex)
{
exceptionMessage = $"An error occurred. {ex.Message}";
}
return webResponse;
}

The result is simple employee JSON login info.

{
"password": "thePassword",
"username": "theUserName"
}

The console application includes the main class and interface files CSharpPythonRESTfulSAPI.cs and ICSharpPythonRESTfulAPI.cs. The C# code for the class file CSharpPythonRESTfulAPI.cs is shown below.

using System;
using System.Drawing;
using System.IO;
using System.Net;
using Newtonsoft.Json.Linq;
using RestSharp;

namespace CallPythonRESTfulAPI
{
public class CSharpPythonRESTfulSAPI : ICSharpPythonRESTfulAPI
{
/// <summary>
/// C# test to call Python HttpWeb RESTful API
/// </summary>
/// <param name="uirWebAPI">UIR web api link</param>
/// <param name="exceptionMessage">Returned exception message</param>
/// <returns>Web response string</returns>

public string CSharpPythonRestfulApiSimpleTest(string uirWebAPI, out string exceptionMessage)
{
exceptionMessage = string.Empty;
string webResponse = string.Empty;
try
{
Uri uri = new Uri(uirWebAPI);
WebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
dynamic employee = new JObject();
employee.username = "theUserName";
employee.password = "thePassword";
streamWriter.Write(employee.ToString());
}
HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
webResponse = streamReader.ReadToEnd();
}
}
catch (Exception ex)
{
exceptionMessage = $"An error occurred. {ex.Message}";
}
return webResponse;
}

/// <summary>
/// C# call Python HttpWeb RESTful API for image classification with Base64 encoded string
/// </summary>
/// <param name="uirWebAPI">UIR web api link</param>
/// <param name="imagePathName">Image path and name</param>
/// <param name="exceptionMessage">Exception message</param>
/// <returns>Web response string</returns>

public string CSharPythonRestfulApiImageClassificationBase64(string uirWebAPI, string imagePathName, out string exceptionMessage)
{
string base64String = string.Empty;
exceptionMessage = string.Empty;
string webResponse = string.Empty;
try
{
Uri uri = new Uri(uirWebAPI);
WebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
base64String = ImageFileToBase64String(imagePathName);
using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
dynamic imageJson = new JObject();
imageJson.content = base64String;
streamWriter.Write(imageJson.ToString());
}
HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
{
webResponse = streamReader.ReadToEnd();
}
}
catch (Exception ex)
{
exceptionMessage = $"An error occurred. {ex.Message}";
}
return webResponse;
}

/// <summary>
/// RestSharp call Python RESTful API for image classification with Base64 encode
/// </summary>
/// <param name="urlWebAPI">UIR web api link</param>
/// <param name="imagePathName">Image path and name</param>
/// <param name="exceptionMessage">Returned exception message</param>
/// <returns>Response content string</returns>

public string RestSharpPythonRestfulApiImageClassificationBase64(string urlWebAPI, string imagePathName, out string exceptionMessage)
{
string base64String = string.Empty;
string imageJsonString = string.Empty;
exceptionMessage = string.Empty;
string responseContent = string.Empty;
try
{
base64String = ImageFileToBase64String(imagePathName);
imageJsonString = BuildImageJsonString(base64String);
RestRequest restRequest = new RestRequest(Method.POST);
restRequest.AddHeader("content-type", "application/json");
restRequest.AddParameter("application/json", imageJsonString, ParameterType.RequestBody);
RestClient restClient = new RestClient(urlWebAPI);
IRestResponse iRestResponse = restClient.Execute(restRequest);
string errorMessage = iRestResponse.ErrorMessage;
if (string.IsNullOrEmpty(errorMessage))
{
responseContent = iRestResponse.Content;
}
else
{
responseContent = errorMessage;
}
}
catch (Exception ex)
{
exceptionMessage = $"An error occurred. {ex.Message}";
}
return responseContent;
}

/// <summary>
/// Convert image file to base64 encoded string
/// </summary>
/// <param name="imagePathName">Image path and name</param>
/// <returns>Base64 encoded string</returns>

private string ImageFileToBase64String(string imagePathName)
{
string base64String = string.Empty;
try
{
using (Image image = Image.FromFile(imagePathName))
{
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream, image.RawFormat);
byte[] imageBytes = memoryStream.ToArray();
base64String = Convert.ToBase64String(imageBytes);
}
}
}
catch (Exception ex)
{
string exceptionMessage = $"An error occurred. {ex.Message}";
}
return base64String;
}

/// <summary>
/// Build image json string object
/// </summary>
/// <param name="base64String">Base64 encoded string</param>
/// <returns>Image json string</returns>

private string BuildImageJsonString(string base64String)
{
string imageJsonString = string.Empty;
try
{
dynamic imageJson = new JObject();
imageJson.content = base64String;
imageJsonString = imageJson.ToString();
}
catch (Exception ex)
{
string exceptionMessage = $"An error occurred. {ex.Message}";
}
return imageJsonString;
}
}
}

The libraries included are: Newtonsoft.Json.Linq and RestSharp. Below is the description of these methods:

1. string CSharpPythonRestfulApiSimpleTest(string uirWebAPI, out string exceptionMessage) — this is a simple test to check and validate how C# calls Python RESTful API Web Services by passing an employee JSON object ({{“username”: “theUserName”, “password”: “thePassword”}}). This object was created using the JObject class from Newtonsoft.Json.Linq namespace. The method uses the traditional HttpWebRequest and HttpWebResponse classes from System.Net namespace. The StreamWriter and StreamReader were used to send and get requests and responses, respectively . Due to different exception handling approaches provided by the company rules, I set the exception message as exceptionMessage = $”An error occurred. {ex.Message}”;

2. string CSharPythonRestfulApiImageClassificationBase64(string uirWebAPI, string imagePathName, out string exceptionMessage — this method calls a private method imageFileToBase64String(string imagePathName) to convert an image file to base64 encoded string. The image JSON object encapsulates the content of the base64 encoded string.

3. string RestSharpPythonRestfulApiImageClassificationBase64(string urlWebAPI, string imagePathName, out string exceptionMessage) — this method is the most used in .NET RESTful Web Services development today. It uses the RestSharp namespace to create the RestRequest and RestClient objects. The following private methods are used:

  • string ImageFileToBase64String(string imagePathName) — Convert image file to base64 encoded string
  • string BuildImageJsonString(string base64String) — Build image JSON string object

The file ICSharpPythonRESTfulAPI.cs represents the required public interface, as usually we need to have it.

namespace CallPythonRESTfulAPI
{
public interface ICSharpPythonRESTfulAPI
{
string CSharpPythonRestfulApiSimpleTest(string uirWebAPI, out string exceptionMessage);
string CSharPythonRestfulApiImageClassificationBase64(string uirWebAPI, string imagePathName, out string exceptionMessage);
string RestSharpPythonRestfulApiImageClassificationBase64(string urlWebAPI, string imagePathName, out string exceptionMessage);
}
}

To be consistent and follow best software development practices, a required unit test was implemented for the third method.

using CallPythonRESTfulAPI;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CallPythonRESTfulAPITest
{
[TestClass]
public class PythonRESTfulAPITest
{
[TestMethod]
public void TestCallPythonRESTfulAPI()
{
string exceptionMessage = string.Empty;
string urlWebAPI = "http://localhost:5000/api/v1.0/image_classification_base64_encode_json";
string expectedOutputText = "{\n \"image type\": \"0\"\n}\n";
string folderImagePath = Properties.Settings.Default.FolderImagePath;
string imagePathName = folderImagePath + "flat.tif";
ICSharpPythonRESTfulAPI csharpPythonRESTfulAPI = new CSharpPythonRESTfulSAPI();
string actualOutputText = csharpPythonRESTfulAPI.RestSharpPythonRestfulApiImageClassificationBase64(urlWebAPI, imagePathName, out exceptionMessage);
Assert.AreEqual(expectedOutputText, actualOutputText);
}
}
}

The result of running this test is.

Summary
Last Test Run Passed (Total Run Time 0:00:02.7082708)
1 Test Passed

I would like to mention a good C# programing practice: creating a public interface and a unit test for each method in the class library. I hope every C# developer can see and understand the importance of unit testing. I have seen very good C# developers designing C# class libraries without a pubic interface or unit tests. Even some of my friends did not pass the technical interview for senior developer positions because of this.

4. Developing Flask RESTful API Web Services for Image Classification

One of the important tasks in image processing is image classification. As an example, I’ll be using the same ML binary classification model presented on the blog paper Using C# to run Python Scripts with Machine Learning Models. In this blog the model was deployed on a network folder and the C# code runs a Python script file with it using .NET System.Diagnostics namespace. In our case now, the model is deployed on a web server and a RESTful API web service will be handing the HTTP Request/Response data transfer using the following types of encoding/decoding techniques:

  1. Base64 Image Encoding/Decoding.
  2. Base64 Image Encoding/Decoding with JSON.
  3. OpenCV Image Encoding/Decoding.
  4. OpenCV Image Encoding/Decoding with JSON.
  5. Image File Encoding/Decoding with JSON.

Let’s look at the Python code implementation for the first one and then I’ll cover how I developed the whole web service using super-sub classes OOP design to organize and reduce the amount of Python code.

5. Base64 Image Encoding/Decoding — simple and good still!

Base64 algorithm for data encoding and decoding is simple and still exceptional today. It’s encoding and decoding arbitrary binary strings into text strings that can be sent safely by email, used as parts of URLs, or included as part of an HTTP POST request. The two lines of code below can read an image file and encode the string bytes into a bytes object.

image_bytes_string = image_file_read.read()
image_base64_encode = base64.encodebytes(image_bytes_string)

This bytes object image_base64_encode will be part of the request data as you can see from code below.

import os
import requests
import json
import base64
import config
from restful_api_image_subclass import RESTfulAPIImageSubclass

def main():
try:
restful_api_image_subclass = RESTfulAPIImageSubclass()
# get image path and name
project_directory_path = os.path.dirname(os.path.realpath(__file__))
image_path_name = os.path.join(project_directory_path, config.IMAGE_TYPE_1)
# get image base64 encode from file path and name
with open(image_path_name, "rb") as image_file_read:
image_bytes_string = image_file_read.read()
# base64 byte encoding
image_base64_encode = base64.encodebytes(image_bytes_string)
image_file_read.close()
# post http request
url = config.URL_IMAGE_CLASSIFICATION_BASE64_ENCODING
headers = {"content-type":config.HEADERS_IMAGE_TIF}
# get and print request for testing
request = requests.post(url, data=image_base64_encode, headers=headers)
result = request.json()
print(request, result)
except:
restful_api_image_subclass.print_exception_message()

if __name__ == '__main__':
main()

After running this code the result will be: <Response [200]> {‘image type’: ‘1’}.

The URL “http://localhost:5000/api/v1.0/image_classification_base64_encoding” defines API endpoint. The method name image_classification_base64_encoding was included for easy to read. The complete code of this web services will be shown at the end.

@app.route("/api/v1.0/image_classification_base64_encoding", methods=[“POST”])
def image_classification_base64_encoding():
response = restful_api_image_subclass.image_classification_base64_encoding(request)
return response

The service subclass restful_api_image_subclass.py contains the implementation of the method image_classification_base64_encoding(self, request).

def image_classification_base64_encoding(self, request):
try:
# get request data
request_data = request.data
# image string base64 decode)
image_base64_decode = base64.decodebytes(request_data)
# convert string of image data to uint8
np_array_encode = np.fromstring(image_base64_decode, np.uint8)
# image numpy array decode
image = cv2.imdecode(np_array_encode, cv2.IMREAD_GRAYSCALE)
# image data pre-processing:
# 1. image resize and array flatten

image_1d_nparray = self.image_resize_flatten_opencv(image)
# 2. image data cast and normalization
X_test = self.image_data_cast_normalization(image_1d_nparray)
# open and close image classification pickle model
project_directory_path = os.path.dirname(os.path.realpath(__file__))
mlp_classifier_model_pkl = open(os.path.join(project_directory_path, "fiducial_image_flat_tilt_classification.pkl"), "rb")
mlp_classifier_model_file = pickle.load(mlp_classifier_model_pkl)
mlp_classifier_model_pkl.close()
# get y predict
y_predict_file = mlp_classifier_model_file.predict(X_test)
if y_predict_file == 1:
result = {"image type":"{}".format(1)}
else:
result = {"image type":"{}".format(0)}
# get json result and set response with 200 status code
response = jsonify(result)
response.status_code = config.HTTP_200_OK
except:
# get exception message, json result and set response with 400 status code
exception_message = self.get_exception_message()
error_message_class = ErrorMessageClass(exception_message)
exception_message_json = json.dumps(error_message_class.__dict__)
response = jsonify(exception_message_json)
response.status_code = config.HTTP_400_BAD_REQUEST
return response

I think I have explained this method well enough for anyone to understand. I’ll go more in depth in the next section.

Here is the configuration file I used for this web service. I found out that a simple configuration file will allow me to avoid any hardcoding settings and parameters in my program.

X_MIN=0; 
X_MAX=255
ENCODING_ISO_8859_1="ISO-8859-1"
HTTP_200_OK=200
HTTP_400_BAD_REQUEST=400
IMAGE_WIDTH_RESIZE=153
IMAGE_HEIGHT_RESIZE=102
IMAGE_CLASSIFICATION_RESULT_1={"image type": "1"}
IMAGE_CLASSIFICATION_RESULT_0={"image type": "0"}
IMAGE_TYPE_1="image_type_1.tif"
IMAGE_TYPE_0="image_type_0.tif"
URL_IMAGE_CLASSIFICATION_BASE64_ENCODING = "http://localhost:5000/api/v1.0/image_classification_base64_encoding"
URL_IMAGE_CLASSIFICATION_BASE64_ENCODING_JSON = "http://localhost:5000/api/v1.0/image_classification_base64_encoding_json"
HEADERS_IMAGE_TIF="image/tif"

6. Simple Image Preprocessing

The image preprocessing was done at the web server side included in the above method image_classification_base64_encoding(self, request). Two calling functions were developed for image resize, array flatten, data cast and normalization.

# 1. image resize and array flatten          
image_1d_nparray = self.image_resize_flatten_opencv(image)
# 2. image data cast and normalization
X_test = self.image_data_cast_normalization(image_1d_nparray)

The code for these functions image_resize_flatten_opencv(image) and image_data_cast_normalization(image_1d_nparray) is shown below:

def image_resize_flatten_opencv(self, image, nparray_dimension="1d"):
"""
resize and flatten the image from 1d to 2d array with fixed width and height
:param image_path_name: image path and file name
:param nparray_dimension: nparray 1d dimension type

:return image flatten array
"""
image_nparray = None
try:
image_dimension = (self.image_width, self.image_height)
image = cv2.resize(image, image_dimension, interpolation=cv2.INTER_AREA)
image_nparray = np.fromstring(image.tobytes(), dtype=np.uint8)
if (nparray_dimension == "1d"):
image_nparray = image_nparray.reshape((1, self.image_height * self.image_width))
except:
self.print_exception_message()
return image_nparray


def image_data_cast_normalization(self, image_1d_nparray, cast_data_type="float32"):
"""
cast and normalize image 1-d flatten numpy array
:param image_1d_nparray: image 1-d flatten numpy array
:param cast_data_type: cast data type

:return image cast and normalize array
"""
try:
image_data_frame = pd.DataFrame(image_1d_nparray)
X_min = config.X_MIN
X_max = config.X_MAX
image_data_frame = (image_data_frame.astype(cast_data_type) - X_min) / (X_max - X_min)
except:
self.print_exception_message()
return image_data_frame
"""
image_nparray = None
try:
image_dimension = (self.image_width, self.image_height)
image = cv2.resize(image, image_dimension, interpolation=cv2.INTER_AREA)
image_nparray = np.fromstring(image.tobytes(), dtype=np.uint8)
if (nparray_dimension == "1d"):
image_nparray = image_nparray.reshape((1, self.image_height * self.image_width))
except:
self.print_exception_message()
return image_nparray


def image_data_cast_normalization(self, image_1d_nparray, cast_data_type="float32"):
"""
cast and normalize image 1-d flatten numpy array
:param image_1d_nparray: image 1-d flatten numpy array
:param cast_data_type: cast data type

"""
try:
image_data_frame = pd.DataFrame(image_1d_nparray)
X_min = config.X_MIN
X_max = config.X_MAX
image_data_frame = (image_data_frame.astype(cast_data_type) - X_min) / (X_max - X_min)
except:
self.print_exception_message()
return image_data_frame

These functions have been commented and the try-except block error handling has been implemented. Unfortunately, I see Python code without comments and error handling. Why?

7. Machine Learning Model Deserialization

To deserialize the ML model I’ll be using the pickle library. The final serialized pickle file image_classification.pkl should be opened in read-binary mode and assigned to a variable to be used for image classification. Don’t forget to close the object model at the end for memory release.

# open and close image classification pickle model
project_directory_path = os.path.dirname(os.path.realpath(__file__))
mlp_classifier_model_pkl = open(os.path.join(project_directory_path, "image_classification.pkl"), "rb")
mlp_classifier_model_file = pickle.load(mlp_classifier_model_pkl)
mlp_classifier_model_pkl.close()

8. JSON Data Class Encapsulation

In Python any JSON object can be built from any data dictionary object. For example: image_string_content = {“content”:image_string}. The issue with this line is that the JSON content has been hardcode and, of course, you should not do that. Python has a read-only attribute for the class object to build a data dictionary using __dict__. In this case we can use the class below to encapsulate any JSON content data.

class ImageByteBase64(object):
"""
encapsulate image json data
"""
def __init__(self, byte_encode):
self.content = byte_encode

Based on that we can create any JSON object with the following two lines of code.

# image byte convert to json
image_byte_base64 = ImageByteBase64(image_string)
image_encode_json = json.dumps(image_byte_base64.__dict__)

This class will also allow you to encapsulate any JSON data without doing any hardcoding in your programs.

9. Debugging Python RESTful API Web Services

When I was working on this image classification Flask RESTful API web service on my local WampServer, I was looking for the best debugging practices for it. If for any reason the API endpoint crashes, I want to know everything about the exception error so I can easily record and fix this problem. A simple exception message could look like:

<Response [400]>{'bytes' object has no attribute 'tostring'

This exception message doesn’t tell me anything about what caused the exception though. So let’s add more information that allows us to quickly find the issue. Something like this.

<Response [400]>{'An error occurred': "[Time Stamp]: 2018-10-02 09:17:16 AM [File Name]: C:\\Users\\ebonat\\eclipse-workspace\\flask_restful_api\\src\\restful_api_image_subclass.py [Procedure Name]: image_classification_encode [Error Message]: 'bytes' object has no attribute 'tostring' [Error Type]: <class 'AttributeError'> [Line Number]: 91 [Line Code]: request_data_string = request_data.tostring()"}

First of all, I created an error message class ErrorMessageClass(object) to encapsulate the error and build the JSON error object.

class ErrorMessageClass(object):
"""
encapsulate error message data
"""
def __init__(self, error_message):
self.error = error_message

This class is used in the following lines of code.

exception_message = self.get_exception_message()                                            
error_message_class = ErrorMessageClass(exception_message)
exception_message_json = json.dumps(error_message_class.__dict__)

Where the function get_exception_message() is implemented in the super class file restful_api_image_superclass.py. Three lines of code will give a complete exception message, that’s all.

For example, below is a good error handling statement when the connection to the web service has failed. Taking a little extra time to develop good exception handling allows us to find the problem quickly and fix it right away.

An error occurred: [Time Stamp]: 2018-12-25 01:32:49 PM; [File Name]: C:\Users\Ernest\AppData\Local\Continuum\envs\python3.6.7\lib\site-packages\requests\adapters.py; [Procedure Name]: send; [Error Message]: HTTPConnectionPool(host='localhost', port=5000): Max retries exceeded with url: /api/v1.0/image_classification_base64_encoding_json (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001F2D7AAE898>: Failed to establish a new connection: [WinError 10061] No connection could be made because the target machine actively refused it',)); [Error Type]: <class 'requests.exceptions.ConnectionError'>; [Line Number]: 516; [Line Code]: raise ConnectionError(e, request=request)

Let’s explain a simple OOP approach for organizing the Python code for ML projects. I really don’t want to do what many Data Scientists do today by copying and pasting a bunch of module files and functions code from one project to another. At the end of the day these files and functions will be increasing considerably and making a big beautiful mess — otherwise known as “Spaghetti Code”. I don’t believe you want to do that.

10. A Simple Python OOP Design for Machine Learning Projects

Each ML algorithm has its own math logic implementation. On the other hand, the ML pipeline flow for any project requires general steps like: features and label selection/engineering, data visualization/preprocessing, train/validation/test data split, data casting and normalization, model serialization/deserialization, etc.

If we create a general base (super) class and encapsulate all the general task codes required, we can use it in all our future ML projects. To handle specific ML algorithm, a new derived (sub) class needs to be created. This simple combination of super-sub classes will reduce the amount of code to maintain. Moreover, this will make class interface and unit test implementation very easy and consistent — everyone should know that! This is the beauty of OOP design. Below is the super class code I would like to share in this blog.

import sys
import time
import traceback
import json
# import numpy as np
import pandas as pd
import config
import logging

from flask import jsonify

from encapsulate_data_class import ErrorMessageClass
from encapsulate_data_class import ContentDataClass

class RESTfulAPIImageSuperclass(object):
def __init__(self):
pass

def is_json_data(self, data):
"""
check if data is a json format
:param data: any data
:return: json data format and boolean true/false
"""

json_data = None
try:
json_data = json.loads(data)
return json_data, True
except:
self.print_exception_message()
return json_data, False

def image_data_cast_normalization(self, image_1d_nparray, cast_data_type="float32"):
"""
cast and normalize image 1-d flatten numpy array
:param image_1d_nparray: image 1-d flatten numpy array
:param cast_data_type: cast data type
:return image cast and normalize array
"""

try:
image_data_frame = pd.DataFrame(image_1d_nparray)
X_min = config.X_MIN; X_max = config.X_MAX
image_data_frame = (image_data_frame.astype(cast_data_type) - X_min) / (X_max - X_min)
except:
self.print_exception_message()
return image_data_frame

def print_exception_message(self, message_orientation="horizontal"):
"""
print full exception message
:param message_orientation: horizontal or vertical
:return None
"""

try:
exc_type, exc_value, exc_tb = sys.exc_info()
file_name, line_number, procedure_name, line_code = traceback.extract_tb(exc_tb)[-1]
time_stamp = " [Time Stamp]: " + str(time.strftime("%Y-%m-%d %I:%M:%S %p"))
file_name = " [File Name]: " + str(file_name)
procedure_name = " [Procedure Name]: " + str(procedure_name)
error_message = " [Error Message]: " + str(exc_value)
error_type = " [Error Type]: " + str(exc_type)
line_number = " [Line Number]: " + str(line_number)
line_code = " [Line Code]: " + str(line_code)
if (message_orientation == "horizontal"):
print("An error occurred:{};{};{};{};{};{};{}".format(time_stamp, file_name, procedure_name, error_message, error_type, line_number, line_code))
elif (message_orientation == "vertical"):
print("An error occurred:\n{}\n{}\n{}\n{}\n{}\n{}\n{}".format(time_stamp, file_name, procedure_name, error_message, error_type, line_number, line_code))
except:
pass

def get_exception_message(self):
"""
get full exception message as string
:return exception message
"""

try:
exc_type, exc_value, exc_tb = sys.exc_info()
file_name, line_number, procedure_name, line_code = traceback.extract_tb(exc_tb)[-1]
exception_message = ''.join('[Time Stamp]: ' + str(time.strftime('%Y-%m-%d %I:%M:%S %p')) + ' '
+ '[File Name]: ' + str(file_name) + ' '
+ '[Procedure Name]: ' + str(procedure_name) + ' '
+ '[Error Message]: ' + str(exc_value) + ' '
+ '[Error Type]: ' + str(exc_type) + ' '
+ '[Line Number]: ' + str(line_number) + ' '
+ '[Line Code]: ' + str(line_code))
return exception_message
except:
pass

def write_log_file(self, log_file_name, event_level, message):
"""
write to a log file
:param log_file_name: log file path and name
:param event_level: even level name (CRITICAL, DEBUG, ERROR, INFO and WARNING)
:param message: message to be written
:return None
"""

try:
if event_level == "CRITICAL".lower():
logging.basicConfig(format=message, filename=log_file_name, level=logging.CRITICAL)
logging.critical(message)
elif event_level == "DEBUG".lower():
logging.basicConfig(format=message, filename=log_file_name, level=logging.DEBUG)
logging.debug(message)
elif event_level == "ERROR".lower():
logging.basicConfig(format=message, filename=log_file_name, level=logging.ERROR)
logging.error(message)
elif event_level == "INFO".lower():
logging.basicConfig(format=message, filename=log_file_name, level=logging.INFO)
logging.info(message)
elif event_level == "WARNING".lower():
logging.basicConfig(format=message, filename=log_file_name, level=logging.WARNING)
logging.warning(message)
except:
self.print_exception_message()

def create_json_error_message(self, message):
"""
create a json error message
:param message: string error message
:return json error message
"""

try:
encapsulate_data_class = ErrorMessageClass(message)
result = json.dumps(encapsulate_data_class.__dict__)
except:
exception_message = sys.exc_info()[1]
encapsulate_data_class = ErrorMessageClass(exception_message)
result = json.dumps(encapsulate_data_class.__dict__)
return jsonify(result)

def create_json_content_data(self, data):
"""
create any json content data
:param data: any data type
:return json content data
"""

try:
encapsulate_data_class = ContentDataClass(data)
result = json.dumps(encapsulate_data_class.__dict__)
except:
exception_message = sys.exc_info()[1]
encapsulate_data_class = ErrorMessageClass(exception_message)
result = json.dumps(encapsulate_data_class.__dict__)
return jsonify(result)

The derived (sub) class for this specific Flask RESTful API web service applied to image classification contains the following code.

import os
import sys
import numpy as np
import json
import config
import base64

import cv2
import pickle
from flask import jsonify

from restful_api_image_superclass import RESTfulAPIImageSuperclass

class RESTfulAPIImageSubclass(RESTfulAPIImageSuperclass):

def __init__(self, image_width_resize=None, image_height_resize=None):
super().__init__()
if image_width_resize is not None:
self.image_width = image_width_resize
if image_height_resize is not None:
self.image_height = image_height_resize

def image_resize_flatten_opencv(self, image, nparray_dimension="1d"):
"""
resize and flatten the image from 1d to 2d array with fixed width and height
:param image_path_name: image path and file name
:param nparray_dimension: nparray 1d dimension type
:return image flatten array
"""

image_nparray = None
try:
image_dimension = (self.image_width, self.image_height)
image = cv2.resize(image, image_dimension, interpolation=cv2.INTER_AREA)
image_nparray = np.fromstring(image.tobytes(), dtype=np.uint8)
if (nparray_dimension == "1d"):
image_nparray = image_nparray.reshape((1, self.image_height * self.image_width))
except:
self.print_exception_message()
return image_nparray

def image_classification_opencv_encoding_json(self, request):
try:
# encoding iso_8859_1
encoding = config.ENCODING_ISO_8859_1
# get request data
request_data = request.data
# decode json data
image_dencode_json = json.loads(request_data)
# get content string image data only
image_string = image_dencode_json["content"]
# convert string of image data to uint8
np_array_encode = np.fromstring(image_string.encode(encoding), np.uint8)
# image numpy array decode
image = cv2.imdecode(np_array_encode, cv2.IMREAD_GRAYSCALE)
# image data pre-processing:
# 1. resize and flatten

image_1d_nparray = self.image_resize_flatten_opencv(image)
# 2. cast and normalization
X_test = self.image_data_cast_normalization(image_1d_nparray)
# open and close image classification pickle model
project_directory_path = os.path.dirname(os.path.realpath(__file__))
mlp_classifier_model_pkl = open(os.path.join(project_directory_path, "fiducial_image_flat_tilt_classification.pkl"), "rb")
mlp_classifier_model_file = pickle.load(mlp_classifier_model_pkl)
mlp_classifier_model_pkl.close()
# get y predict
y_predict_file = mlp_classifier_model_file.predict(X_test)
if y_predict_file == 1:
result = {"image type":"{}".format(1)}
else:
result = {"image type":"{}".format(0)}
# get json result and set response with 200 status code
response = jsonify(result)
response.status_code = config.HTTP_200_OK
except:
# get exception message, json result and set response with 400 status code
exception_message = self.get_exception_message()
result = {"An error occurred":exception_message}
response = jsonify(result)
response.status_code = config.HTTP_400_BAD_REQUEST
return response

def image_classification_opencv_encoding(self, request):
try:
# get request data
request_data = request.data
# convert string of image data to uint8
np_array_encode = np.fromstring(request_data, np.uint8)
# image numpy array decode
image = cv2.imdecode(np_array_encode, cv2.IMREAD_GRAYSCALE)
# image data pre-processing:
# 1. resize and flatten

image_1d_nparray = self.image_resize_flatten_opencv(image)
# 2. cast and normalization
X_test = self.image_data_cast_normalization(image_1d_nparray)
# open and close image classification pickle model
project_directory_path = os.path.dirname(os.path.realpath(__file__))
mlp_classifier_model_pkl = open(os.path.join(project_directory_path, "fiducial_image_flat_tilt_classification.pkl"), "rb")
mlp_classifier_model_file = pickle.load(mlp_classifier_model_pkl)
mlp_classifier_model_pkl.close()
# get y predict
y_predict_file = mlp_classifier_model_file.predict(X_test)
if y_predict_file == 1:
result = {"image type":"{}".format(1)}
else:
result = {"image type":"{}".format(0)}
# get json result and set response with 200 status code
response = jsonify(result)
response.status_code = config.HTTP_200_OK
except:
# get exception message, json result and set response with 400 status code
exception_message = self.get_exception_message()
result = {"An error occurred":exception_message}
response = jsonify(result)
response.status_code = config.HTTP_400_BAD_REQUEST
return response

def image_classification_base64_encoding_json(self, request):
try:
# get request data
request_data = request.data
# decode json data
image_dencode_json = json.loads(request_data)
# get content string image data only
image_string = image_dencode_json["content"]
# image string base64 decode
image_string = base64.b64decode(image_string)
# convert string of image data to uint8
np_array_encode = np.fromstring(image_string, np.uint8)
# image numpy array decode
image = cv2.imdecode(np_array_encode, cv2.IMREAD_GRAYSCALE)
# image data pre-processing:
# 1. image resize and flatten

image_1d_nparray = self.image_resize_flatten_opencv(image)
# 2. image data cast and normalization
X_test = self.image_data_cast_normalization(image_1d_nparray)
# open and close image classification pickle model
project_directory_path = os.path.dirname(os.path.realpath(__file__))
mlp_classifier_model_pkl = open(os.path.join(project_directory_path, "fiducial_image_flat_tilt_classification.pkl"), "rb")
mlp_classifier_model_file = pickle.load(mlp_classifier_model_pkl)
mlp_classifier_model_pkl.close()
# get y predict
y_predict_file = mlp_classifier_model_file.predict(X_test)
if y_predict_file == 1:
result = {"image type":"{}".format(1)}
else:
result = {"image type":"{}".format(0)}
# get json result and set response with 200 status code
response = jsonify(result)
response.status_code = config.HTTP_200_OK
except:
# get exception message, json result and set response with 400 status code
exception_message = self.get_exception_message()
result = {"An error occurred":exception_message}
response = jsonify(result)
response.status_code = config.HTTP_400_BAD_REQUEST
return response

def image_classification_base64_encoding(self, request):
try:
# get request data
request_data = request.data
# image string base64 decode
image_base64_decode = base64.decodebytes(request_data)
# convert string of image data to uint8
np_array_encode = np.fromstring(image_base64_decode, np.uint8)
# image numpy array decode
image = cv2.imdecode(np_array_encode, cv2.IMREAD_GRAYSCALE)
# image data pre-processing:
# 1. image resize and flatten

image_1d_nparray = self.image_resize_flatten_opencv(image)
# 2. image data cast and normalization
X_test = self.image_data_cast_normalization(image_1d_nparray)
# open and close image classification pickle model
project_directory_path = os.path.dirname(os.path.realpath(__file__))
mlp_classifier_model_pkl = open(os.path.join(project_directory_path, "fiducial_image_flat_tilt_classification.pkl"), "rb")
mlp_classifier_model_file = pickle.load(mlp_classifier_model_pkl)
mlp_classifier_model_pkl.close()
# get y predict
y_predict_file = mlp_classifier_model_file.predict(X_test)
if y_predict_file == 1:
result = {"image type":"{}".format(1)}
else:
result = {"image type":"{}".format(0)}
# get json result and set response with 200 status code
response = jsonify(result)
response.status_code = config.HTTP_200_OK
except:
# get exception message, json result and set response with 400 status code
exception_message = self.get_exception_message()
result = {"An error occurred":exception_message}
response = jsonify(result)
response.status_code = config.HTTP_400_BAD_REQUEST
return response

As you can see from the code above the super class, RESTfulAPIImageSuperclass has been inherited and initialized in the constructor as super().__init__().

def __init__(self, image_width_resize=None, image_height_resize=None):
super().__init__()
if image_width_resize is not None:
self.image_width = image_width_resize
if image_height_resize is not None:
self.image_height = image_height_resize

Now that we have the code in the right places, we can refactor the first method csharp_python_restfulapi_json() we developed to test C# at the beginning of these blog.

From:

@app.route("/api/v1.0/csharp_python_restfulapi_json", methods=["POST"])
def csharp_python_restfulapi_json():
"""
simple c# test to call python restful api web service
"""

try:
# get request json object
request_json = request.get_json()
# convert to response json object
response = jsonify(request_json)
response.status_code = 200
except:
exception_message = sys.exc_info()[1]
response = json.dumps({"content":exception_message})
response.status_code = 400
return response

To:

@app.route("/api/v1.0/csharp_python_restfulapi_json", methods=["POST"])
def csharp_python_restfulapi_json():
"""
simple c# test to call python restful api web service
"""

try:
# get request json object
request_json = request.get_json()
# convert to response json object
response = jsonify(request_json)
response.status_code = config.HTTP_200_OK
except:
exception_message = sys.exc_info()[1]
encapsulate_data_class = ErrorMessageClass(exception_message)
response = json.dumps(encapsulate_data_class.__dict__)
response.status_code = config.HTTP_400_BAD_REQUEST
return response

As you can see, nothing has been hardcoded at this time and the code is clean and easy to maintain. This could be a simple and efficient approach for Python ML projects development and deployment. Feel free to use any OOP patterns you would like to in your production ML projects, believe me, you’ll reduce a lot of code and development time.

11. Implementing Class Interface in Python — a big issue without no reasons!

When we develop business applications using Java or C#, for example, we create class interfaces as a good programming practice. I think, you can use the same approach using Python too. Practically all my Python developer friends use class interfaces in their ML projects.

Some Python developers believe that the “Pythonic” way of programming doesn’t need class interface at all! Maybe it’s time for them to understand why class interfaces are necessary! There are couple of ways of implementing class interfaces in Python like simple class inheritance, abstract base classes using abc module, interface library, etc. I’ll be using the interface library because I have found it to be very easy to use and it’s closer to how we do it in Java and C# today. From the interface website we state: “interface is a library for declaring interfaces and for statically asserting that classes implement those interfaces. It provides stricter semantics than Python’s built-in abc module, and it supports Python 2.7 and Python 3.4+.

Let’s apply this interface library to our RESTful API web service. Based on the five developed methods in the sub class, the interface code will be.

from interface import Interface

class RESTfulAPIImageInterfaceclass(Interface):
"""
interface for the api endpoints sub class
"""

def image_resize_flatten_opencv(self, image, nparray_dimension="1d"):
pass
def image_classification_opencv_encoding_json(self, request):
pass
def image_classification_opencv_encoding(self, request):
pass
def image_classification_base64_encoding_json(self, request):
pass
def image_classification_base64_encoding(self, request):
pass

The main Interface class object had been imported from the interface library and the sub class has to inherit it. In this case the sub class code needs to be updated with two new imports.

from interface import implements
from restful_api_image_interface import RESTfulAPIImageInterfaceclass

And the final sub class definition code needs to be updated with the interface implementation.

class RESTfulAPIImageSubclass(implements(RESTfulAPIImageInterfaceclass), RESTfulAPIImageSuperclass):

It’s nice to see a real and practical example of multiple inheritance classes in Python. The official contract between the sub and the interface classes had to be established. Any changes made to a public method, the sub class will generate a not implemented error. Below is an example if the image_classification_base64_encoding method changes.

The following methods of RESTfulAPIImageInterfaceclass were not implemented:
- image_classification_base64_encoding(self, request)

This will prevent a person from opening the web service definition file and making changes to API public methods. You can apply the same logic in your production ML projects including Flask RESTful API web services. I really don’t want to see any production deployed Python code without class interfaces.

12. Unit Tests in Machine Learning Projects

As a reminder: It’s very a bad programming practice to deploy ML models in production without unit tests. As a consultant, I worked for many companies and they would not allow me to deploy a production application without a unit test implementation. That is the best advice that I can give to you. Every API endpoint method must have a unique unit test implementation respectively. I explained this clearly in my blog paper “Refactoring Python Code for Machine Learning Projects. Python “Spaghetti Code” Everywhere!”. Here is another good link Writing Unit Tests for REST API in Python for you to read it and understand it.

Let’s look at the API endpoint code for base64 image encoding/decoding implementation from web service sub class. The unit test code for this method will be.

import unittest
import os
import requests
import base64
import config

class ImageByteBase64JsonTest(unittest.TestCase):
"""
image base64 encoding class test
"""

def testImageBinaryClassification(self):
project_directory_path = os.path.dirname(os.path.realpath(__file__))
image_path_name = os.path.join(project_directory_path, config.IMAGE_TYPE_0)
with open(image_path_name, "rb") as image_file_read:
image_bytes_string = image_file_read.read()
image_base64_encode = base64.encodebytes(image_bytes_string)
image_file_read.close()
url = config.URL_IMAGE_CLASSIFICATION_BASE64_ENCODING
headers = {"content-type":config.HEADERS_IMAGE_TIF}
request = requests.post(url, data=image_base64_encode, headers=headers)
result = request.json()
self.assertEqual(result, config.IMAGE_CLASSIFICATION_RESULT_0, "Image Binary Classification Test Failed.")

if __name__ == "__main__":
unittest.main()

The unit test string method assertEqual() compares the request JSON obtained value with a fixed one defined in the config.py file IMAGE_TYPE_0=”image_type_0.tif”. If both are equal, the test passes with OK result.

Finding files... done.
Importing test modules ... done.

----------------------------------------------------------------------
Ran 1 test in 1.178s

OK

Below is an example when the test failed.

Finding files... done.
Importing test modules ... done.

AssertionError: {'image type': '0'} != {'image type': '1'}
- {'image type': '0'}
? ^
+ {'image type': '1'}
? ^
: Image Binary Classification Test Failed.

----------------------------------------------------------------------
Ran 1 test in 1.183s

FAILED (failures=1)

Here is the unit test using image base64 encoding with JSON data encapsulation. As you can see, it’s very similar with the previous one.

import unittest
import os
import requests
import json
import base64
import config

class ImageByteBase64(object):
"""
encapsulate image json data
"""

def __init__(self, byte_encode):
self.content = byte_encode

class ImageBase64EncodingJSONTest(unittest.TestCase):
"""
image base64 encoding json class test
"""
def testImageBinaryClassification(self):
"""
image binary classification test for base64 encoding
"""

project_directory_path = os.path.dirname(os.path.realpath(__file__))
image_path_name = os.path.join(project_directory_path, config.IMAGE_TYPE_0)
encoding=config.ENCODING_ISO_8859_1
with open(image_path_name, "rb") as image_file_read:
image_bytes_string = image_file_read.read()
image_bytes = base64.b64encode(image_bytes_string)
image_file_read.close()
image_string = image_bytes.decode(encoding)
image_byte_base64 = ImageByteBase64(image_string)
image_encode_json = json.dumps(image_byte_base64.__dict__)
url = config.URL_IMAGE_CLASSIFICATION_BASE64_ENCODING_JSON
headers = {"content-type":config.HEADERS_IMAGE_TIF}
request = requests.post(url, data=image_encode_json, headers=headers)
result = request.json()
self.assertEqual(result, config.IMAGE_CLASSIFICATION_RESULT_0, "Image Binary Classification Test Failed.")

if __name__ == "__main__":
unittest.main()

I hope this topic will give you some starting points on how to implement unit tests in your ML projects.

13. Image Classification RESTful API Web Service — complete code!

The complete main code of the image classification RESTful API web service is shown below. This includes the five developed API methods: image_classification_base64_encoding(), image_classification_base64_encoding_json(), image_classification_opencv_encoding() ,image_classification_opencv_encoding_json(), image_classification_file_encoding_json(). It’s simply organized and maintainable when OOP design approach is used.

import sys
import json
import config

from flask import Flask, jsonify, request
from restful_api_image_subclass import RESTfulAPIImageSubclass

app = Flask(__name__)

@app.route("/api/v1.0/image_classification_base64_encoding", methods=["POST"])
def image_classification_base64_encoding():
response = restful_api_image_subclass.image_classification_base64_encoding(request)
return response

@app.route("/api/v1.0/image_classification_base64_encoding_json", methods=["POST"])
def image_classification_base64_encoding_json():
response = restful_api_image_subclass.image_classification_base64_encoding_json(request)
return response

@app.route("/api/v1.0/image_classification_opencv_encoding", methods=["POST"])
def image_classification_opencv_encoding():
response = restful_api_image_subclass.image_classification_opencv_encoding(request)
return response

@app.route("/api/v1.0/image_classification_opencv_encoding_json", methods=["POST"])
def image_classification_opencv_encoding_json():
response = restful_api_image_subclass.image_classification_opencv_encoding_json(request)
return response

@app.route("/api/v1.0/image_classification_file_encoding_json", methods=["POST"])
def image_classification_file_encoding_json():
response = restful_api_image_subclass.image_classification_opencv_encoding_json(request)
return response

if __name__ == "__main__":
global restful_api_image_subclass
restful_api_image_subclass = RESTfulAPIImageSubclass(config.IMAGE_WIDTH_RESIZE, config.IMAGE_HEIGHT_RESIZE)
app.run(debug=True)

14. Conclusions

I would like to conclude this blog with the following ideas:

1. Building Python RESTful API web services with ML trained models allows any C# .NET business application to consume them for production Data Analytics projects implementation.
2. JSON data class encapsulation is a good technique to avoid content hardcoding.
3. A simple Python OOP design with super-sub classes allow developing reusable and maintainable ML projects. It will reduce your code and development time.
4. Hardcoding Python ML settings, parameters and hyperparameters can be easily avoided with a simple configuration file. Feel free to encrypt this file if necessary.
5. Class interfaces and API unit tests is a must for production Python ML project development and deployment.

--

--

Ernest Bonat, Ph.D.

I’m a Senior Data Scientist and Engineer consultant. I work on Machine Learning application projects for Life Sciences using Python and Python Data Ecosystem.