Super-scaling, neural enhancement, super resolution, AI upscaling — various terms that represent the same idea. You can use an artificial neural network to upscale images, instead of regular bilinear/bicubic algorithms. Buuuuut, that’s not really what this article is about…

Left — Original, Right — Image Super-Resolution
This tutorial intends to present an example of how you can wrap any AI related solutions (that require you to stick with Python) in a convenient stateless service, that can be easily deployed, scaled, and used anywhere in your architecture.
What we will build today:
- Base microservice in Python, with Flask framework
- Endpoint for image upscaling via ISR package (ISR GitHub)
- Docker image for our microservice
- REST API client in Java to communicate with our microservice
As always — final code and sample images for this tutorial can be found in my GitHub repo by the link at the end of this article.
Part 1 —Basic Flask app
Let’s start by making a simple Flask application, a basic microservice in Python, with one ping
endpoint in it.
First we’re gonna need a JSON object message_protocol/ping_output.py
to return data from this endpoint
1import json23class PingOutput:4 def __init__(self, success: bool):5 self.success = success67 def toJSON(self):8 return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
Then let’s make the endpoint logic have its own class, and put it in resource_ping.py
, I know, looks excessive, but trust me it pays off when you have some real workload in these endpoints.
1from message_protocol.ping_output import PingOutput23class ResourcePing:4 def main(self) -> PingOutput:5 return PingOutput(True)
Make sure that the resource is instantiated only once (on app startup), by adding a separate script resources.py
1# Make sure all resources are instantiated here2from resource_ping import ResourcePing3resourcePing = ResourcePing()
Now the main piece — a Flask app that maps /ping
URL into our resource. Let’s name it flask_app.py
1from flask import Flask2from flask import request3from flask import Response4from resources import resourcePing56app = Flask(__name__)78@app.route('/ping', methods=['GET'])9def ping():10 output = resourcePing.main()11 json = output.toJSON()12 return Response(json, mimetype='application/json')
With that in place you can run the service in terminal via
1export FLASK_APP=flask_app.py2flask run --host=0.0.0.0 --port=7777
And test the response by doing GET http://localhost:7777/ping

The backbone of our Python microservice is now completed, and we can add new resources in a similar manner.
Part 2 — Integrate ISR
With the basic Flask app up and running we can add an endpoint for image upscaling, but first let’s take a look at how Image Super-Resolution works by doing a quick test outside the Flask application. You can find more detailed ISR usage examples on their GitHub, I’ll just provide a quick sample for our case. We will use the Artefact Cancelling GANS model in this tutorial.
1import numpy as np2from PIL import Image3from ISR.models import RDN45inputPath = '../../sample-images/t4.jpg'6outputPath = '../../sample-images/t4_up.jpg'7imagePil = Image.open(inputPath)8imageNumpy = np.array(imagePil)9model = RDN(weights='noise-cancel')10scaledNumpy = model.predict(imageNumpy)11scaledPil = Image.fromarray(scaledNumpy)12scaledPil.save(outputPath)

Left — Original, Right — Image Super-Resolution

Left — Original, Right — Image Super-Resolution
Now let’s put this scaling into a new resource. To pass images in JSON objects I will use base64 string image encoding, which simplifies this tutorial, but a more reasonable approach for real world applications would be passing only URLs to images.
We’ll define input/output JSON objects (although their contents are the same, I will use 2 different objects here, as in real applications we will probably want to pass additional data in them)
1import json23class UpscaleInput:4 def __init__(self, imageBase64: str):5 self.imageBase64 = imageBase6467 def toJSON(self):8 return json.dumps(self, default=lambda o: o.__dict__, sort_keys=False, indent=4)910def parseUpscaleInput(dictionary) -> UpscaleInput:11 imageBase64 = dictionary['imageBase64']12 return UpscaleInput(imageBase64)1314class UpscaleOutput:15 def __init__(self, imageBase64: str):16 self.imageBase64 = imageBase641718 def toJSON(self):19 return json.dumps(self, default=lambda o: o.__dict__, sort_keys=False, indent=4)2021def parseUpscaleOutput(dictionary) -> UpscaleOutput:22 imageBase64 = dictionary['imageBase64']23 return UpscaleOutput(imageBase64)
And now the resource itself, resource_upscale.py
1import io2import base643import numpy as np4from PIL import Image5from ISR.models import RDN6from message_protocol.upscale_input import UpscaleInput7from message_protocol.upscale_output import UpscaleOutput89MODEL = RDN(weights='noise-cancel')1011class ResourceUpscale:12 def main(self, upscaleInput: UpscaleInput) -> UpscaleOutput:13 # Parse base64 string into bytes array14 inputImageBytesArray = base64.b64decode(upscaleInput.imageBase64)1516 # Open the image17 imagePil = Image.open(io.BytesIO(inputImageBytesArray))18 imageNumpy = np.array(imagePil)1920 # Scale the image21 scaledNumpy = MODEL.predict(imageNumpy)22 scaledPil = Image.fromarray(scaledNumpy)2324 # Write scaled image as bytes array25 outputImageBytesArrayIO = io.BytesIO()26 scaledPil.save(outputImageBytesArrayIO, format=imagePil.format, quality=100)27 outputImageBytesArray = outputImageBytesArrayIO.getvalue()2829 # Convert back to base64 string30 outputImageBase64 = base64.b64encode(outputImageBytesArray).decode('utf-8')31 return UpscaleOutput(outputImageBase64)
Don’t forget to instantiate it in resources.py
1# Make sure all resources are instantiated here2from resource_ping import ResourcePing3from resource_upscale import ResourceUpscale4resourcePing = ResourcePing()5resourceUpscale = ResourceUpscale()
And map the /upscale
URL in flask_app.py
1from flask import Flask2from flask import request3from flask import Response4from resources import resourcePing, resourceUpscale5from message_protocol.upscale_input import parseUpscaleInput67app = Flask(__name__)89@app.route('/ping', methods=['GET'])10def ping():11 output = resourcePing.main()12 json = output.toJSON()13 return Response(json, mimetype='application/json')1415@app.route('/upscale', methods=['POST'])16def upscale():17 input = parseUpscaleInput(request.json)18 output = resourceUpscale.main(input)19 json = output.toJSON()20 return Response(json, mimetype='application/json')
At this stage let’s leave the Python side of things, and switch to building our environment and client to properly run and communicate with this service.
Part 3 — Docker image
Our Docker image will be pretty straight-forward, we will start with a base Ubuntu image, install Python, PIP and all the dependencies needed to run this service in a container, copy the source code and run the service on container launch. So the Dockerfile
ends up looking like this:
1FROM ubuntu:20.0423# Initial setup & install system utils4RUN apt-get update && apt-get upgrade -y5RUN apt-get install -y apt-utils software-properties-common6RUN apt-get install -y vim wget git78# Install Python 3.8 & Pip 39WORKDIR /10RUN add-apt-repository -y ppa:deadsnakes/ppa11RUN apt-get install -y python3.812RUN echo "alias python='python3.8'" >> ~/.bashrc13RUN echo "alias python3='python3.8'" >> ~/.bashrc14RUN apt-get install -y python3-pip15RUN echo "alias pip='pip3'" >> ~/.bashrc1617# Install Python dependencies18RUN pip3 install flask19RUN pip3 install numpy20RUN pip3 install tensorflow21RUN pip3 install ISR --ignore-installed tensorflow2223# Copy source code24RUN mkdir /python-service25COPY . /python-service26WORKDIR /python-service27RUN chmod a+x run.sh2829# Run30CMD './run.sh'
As for the run.sh
script
1#!/bin/sh2export FLASK_APP=flask_app.py3flask run --host=0.0.0.0 --port=7777
Now you can build and run the Docker container from terminal
1docker build -t service-ai-image . && docker run --rm -ti -p 7777:7777 --name service-ai-container service-ai-image
And test that it’s pinging by doing GET http://localhost:7777/ping
Part 4 — Java client
Probably the easiest part of this tutorial — we now need to build a REST API client that communicates with our service. I’m coding this in Java, but this part should be doable in any other language you prefer. I’m gonna use Lombok annotations to speed things up and reduce boilerplate code, Gson for easier JSON mappings, and Apache HTTP client to call the service. Then, of course, JUnit for testing.
JSON objects would look like this
1public interface JsonSerializable extends Serializable {2 default String toJson() {3 return new GsonBuilder().create().toJson(this);4 }5}67@Getter @AllArgsConstructor @NoArgsConstructor @ToString @EqualsAndHashCode8public class PingOutput implements JsonSerializable {9 private boolean success;10}1112@Getter @AllArgsConstructor @NoArgsConstructor @ToString @EqualsAndHashCode13public class UpscaleInput implements JsonSerializable {14 private String imageBase64;1516 public UpscaleInput(byte[] imageBytesArray) {17 this(Base64.getEncoder().encodeToString(imageBytesArray));18 }1920 public UpscaleInput(File imageFile) throws IOException {21 this(Files.readAllBytes(imageFile.toPath()));22 }23}2425@Getter @AllArgsConstructor @NoArgsConstructor @ToString @EqualsAndHashCode26public class UpscaleOutput implements JsonSerializable {27 private String imageBase64;2829 public byte[] toBytesArray() {30 return Base64.getDecoder().decode(imageBase64);31 }32}
We also need a helper class with some logic about handling HTTP requests (in real world apps you’d want some more try-catch blocks here, some decent error handling logic, but this works fine for our case)
1public class ClientUtils {2 private static String getJsonResponse(HttpResponse httpResponse) throws Exception {3 if (httpResponse.getStatusLine().getStatusCode() == 200) {4 HttpEntity httpEntity = httpResponse.getEntity();5 return EntityUtils.toString(httpEntity);6 } else {7 throw new IllegalStateException("Http response status code is invalid: " + httpResponse.getStatusLine().getStatusCode() + ", " + EntityUtils.toString(httpResponse.getEntity()));8 }9 }1011 public static <M> M parseJsonResponse(String jsonResponse, Type responseResultType) throws Exception {12 return new GsonBuilder().create().fromJson(jsonResponse, responseResultType);13 }1415 public static String doGet(String url) throws Exception {16 HttpClient httpClient = HttpClientBuilder.create().build();17 HttpGet httpGet = new HttpGet(url);18 httpGet.setHeader("Accept", "application/json");19 return getJsonResponse(httpClient.execute(httpGet));20 }2122 public static String doPost(String url, String jsonPayload) throws Exception {23 HttpClient httpClient = HttpClientBuilder.create().build();24 HttpPost httpPost = new HttpPost(url);25 httpPost.setEntity(new StringEntity(jsonPayload, ContentType.APPLICATION_JSON));26 httpPost.setHeader("Accept", "application/json");27 httpPost.setHeader("Content-type", "application/json");28 return getJsonResponse(httpClient.execute(httpPost));29 }30}
Now we can finally write the main client itself, with 2 methods — ping
and upscale
.
1public class Client {2 private static String BASE_URL = "http://localhost:7777";34 public static PingOutput ping() throws Exception {5 String url = BASE_URL + "/ping";6 String response = ClientUtils.doGet(url);7 return ClientUtils.parseJsonResponse(response, PingOutput.class);8 }910 public static UpscaleOutput upscale(UpscaleInput input) throws Exception {11 String url = BASE_URL + "/upscale";12 String response = ClientUtils.doPost(url, input.toJson());13 return ClientUtils.parseJsonResponse(response, UpscaleOutput.class);14 }15}
It’s time to test the whole puzzle together! Make sure the service is running in Docker, and launch these tests
1@Ignore2public class ClientTest {3 @Test4 public void ping() throws Exception {5 PingOutput output = Client.ping();6 assertNotNull(output);7 assertTrue(output.isSuccess());8 }910 @Test11 public void upscale() throws Exception {12 String inputFilePath = "../sample-images/t7.jpg";13 String outputFilePath = "./sample-images/t7_up.jpg";14 File inputFile = new File(inputFilePath);15 UpscaleInput input = new UpscaleInput(inputFile);16 UpscaleOutput output = Client.upscale(input);17 assertNotNull(output);18 assertFalse(output.getImageBase64().isEmpty());19 File outputFile = new File(outputFilePath);20 FileUtils.writeByteArrayToFile(outputFile, output.toBytesArray());21 }22}
If everything went well you should have a scaled image saved on your drive

Left — Original, Right — Image Super-Resolution

Left — Original, Right — Image Super-Resolution
That’s it! Thank you for following through with this tutorial, and a quick recap before we go — we’ve briefly examined image super-scaling, built an AI microservice as a Flask app in Python, wrapped it in a Docker container, and tested it though REST API client in Java. I hope this article was helpful and I managed to show you how to integrate Python AI services in your project.
GitHub repo with source code for this tutorial
In case you’d like to check my other work or contact me: