Skip to content

Commit

Permalink
updated dependencies and comments
Browse files Browse the repository at this point in the history
  • Loading branch information
alvertogit committed Apr 28, 2024
1 parent 74784e9 commit 1787c5a
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 36 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The code has been tested using:
- [Flask] (3.0): a microframework for [Python] based on Werkzeug, Jinja 2 and good intentions.
- [Gunicorn] (22.0): a [Python] [WSGI] HTTP Server for UNIX.
- [NGINX] (1.25): a free, open-source, high-performance HTTP server, reverse proxy, and IMAP/POP3 proxy server.
- [Docker] (26.0): an open platform for developers and sysadmins to build, ship, and run distributed applications, whether on laptops, data center VMs, or the cloud.
- [Docker] (26.1): an open platform for developers and sysadmins to build, ship, and run distributed applications, whether on laptops, data center VMs, or the cloud.
- [Docker Compose] (2.26): a tool for defining and running multi-container [Docker] applications.
- [Keras] ([TensorFlow] built-in): a high-level neural networks [API], written in [Python] and capable of running on top of [TensorFlow].
- [TensorFlow] (2.16): an open source software [Deep Learning] library for high performance numerical computation using data flow graphs.
Expand Down Expand Up @@ -158,7 +158,7 @@ It is possible to execute tests of [Flask] microservice created with [pytest] fr
~/app# make test
...
============================= test session starts ==============================
platform linux -- Python 3.10.14, pytest-8.1.1, pluggy-1.5.0
platform linux -- Python 3.10.14, pytest-8.1.2, pluggy-1.5.0
rootdir: /app/tests
collected 2 items

Expand Down
25 changes: 12 additions & 13 deletions app/app/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@

@api.route("/predictlabel", methods=["POST"])
def predict():
# result dictionary that will be returned from the view
"""
Predict the label of an uploaded image with the Deep Learning model.
Returns:
dict: The JSON response with the prediction results dictionary.
"""

result = {"success": False}

# ensure an image was properly uploaded to our endpoint
if request.method == "POST" and request.files.get("file"):
# read image as grayscale
image_req = request.files["file"].read()
Expand All @@ -33,19 +38,13 @@ def predict():

# classify the input image generating a list of predictions
model = current_app.config["model"]
preds = model.predict(preprocessed_image)
predictions = model.predict(preprocessed_image)

# add generated predictions to result
result["predictions"] = []

for i in range(0, 10):
pred = {"label": str(i), "probability": str(preds[0][i])}
result["predictions"].append(pred)

result["most_probable_label"] = str(np.argmax(preds[0]))

# indicate that the request was a success
result["predictions"] = [
{"label": str(i), "probability": str(pred)} for i, pred in enumerate(predictions[0])
]
result["most_probable_label"] = str(np.argmax(predictions[0]))
result["success"] = True

# return result dictionary as JSON response to client
return jsonify(result)
28 changes: 18 additions & 10 deletions app/app/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@


def init_model():
"""Function that loads Deep Learning model.
"""
Load the pre-trained Deep Learning model.
Returns:
model: Loaded Deep Learning model.
model (tensorflow.keras.Model): The loaded Deep Learning model.
"""

model = load_model(current_app.config["MODEL_PATH"])
Expand All @@ -24,16 +26,22 @@ def init_model():


def preprocess_image(image):
"""Function that preprocess image.
"""
Preprocess an image for the Deep Learning model.
Args:
image (numpy.ndarray): The input image.
Returns:
image: Preprocessed image.
preprocessed_image (numpy.ndarray): The preprocessed image.
"""

# invert grayscale image
image = util.invert(image)
# resize and reshape image for model
image = transform.resize(image, (28, 28), anti_aliasing=True, mode="constant")
image = np.array(image)
image = image.reshape((1, 28 * 28))
inverted_image = util.invert(image)

# resize and reshape image
resized_image = transform.resize(inverted_image, (28, 28), anti_aliasing=True, mode="constant")
resized_image = np.array(resized_image)
preprocessed_image = resized_image.reshape((1, 28 * 28))

return image
return preprocessed_image
29 changes: 26 additions & 3 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,54 @@


class DefaultConfig:
if os.environ.get("SECRET_KEY"):
SECRET_KEY = os.environ.get("SECRET_KEY")
else:
"""
Default configuration class.
"""

SECRET_KEY = os.environ.get("SECRET_KEY")
if not SECRET_KEY:
raise ValueError("SECRET KEY NOT FOUND!")

MODEL_PATH = os.environ.get("MODEL_PATH") or "/app/mnist_model.keras"

@staticmethod
def init_app(app):
"""
Initialize the application with the default configuration.
"""

print("PRODUCTION CONFIG")


class DevConfig(DefaultConfig):
"""
Development configuration class.
"""

DEBUG = True

@classmethod
def init_app(cls, app):
"""
Initialize the application with the development configuration.
"""

print("DEVELOPMENT CONFIG")


class TestConfig(DefaultConfig):
"""
Testing configuration class.
"""

TESTING = True

@classmethod
def init_app(cls, app):
"""
Initialize the application with the testing configuration.
"""

print("TESTING CONFIG")


Expand Down
17 changes: 17 additions & 0 deletions app/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,27 @@

@pytest.fixture
def app():
"""
Create a Flask app instance for testing.
Returns:
flask.Flask: The Flask app instance.
"""

app = create_app("testing")
return app


@pytest.fixture
def client(app):
"""
Create a Flask test client for the app.
Args:
app (flask.Flask): The Flask app instance.
Returns:
flask.testing.FlaskClient: The Flask test client.
"""

return app.test_client()
25 changes: 22 additions & 3 deletions app/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,30 @@


def test_index(client):
"""
Test the index route.
Args:
client (flask.testing.FlaskClient): The Flask test client.
"""

response = client.get("/")
# check response

# assert response status
assert response.status_code == 200

# assert response data
assert response.data == b"Deep Learning on Flask"


def test_api(client):
"""
Test the API endpoint to predict the label of an uploaded image with the Deep Learning model.
Args:
client (flask.testing.FlaskClient): The Flask test client.
"""

# server REST API endpoint url and example image path
SERVER_URL = "http://127.0.0.1:5000/api/predictlabel"
IMAGE_PATH = "../app/static/4.jpg"
Expand All @@ -26,7 +43,7 @@ def test_api(client):
payload = {"file": image}
response = client.post(SERVER_URL, data=payload)

# check response
# assert response status
assert response.status_code == 200

# JSON format
Expand All @@ -40,11 +57,13 @@ def test_api(client):
if json_response["success"]:
# most probable label
print(json_response["most_probable_label"])

# predictions
for dic in json_response["predictions"]:
print(f"label {dic['label']} probability: {dic['probability']}")

# assert the most probable label is 4
assert json_response["most_probable_label"] == "4"
# failed
else:
raise AssertionError()
raise AssertionError("API endpoint /predictlabel failed")
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3.8"

services:
web:
build: .
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ Flask==3.0.3
gunicorn==22.0.0
numpy==1.26.4
Pillow==10.3.0
pytest==8.1.1
pytest==8.1.2
requests==2.31.0
ruff==0.4.1
ruff==0.4.2
scikit-image==0.23.2
tensorflow==2.16.1
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
-r requirements.txt
jupyterlab==4.1.6
jupyterlab==4.1.8
matplotlib==3.8.4

0 comments on commit 1787c5a

Please sign in to comment.