Python for Data Science Series - Exploring the syntax
Introduction
In the last post we discussed the importance of programming in the data science context and why Python is considered one of the top languages used by data scientists. In this week's post, we will explore the syntax of Python by creating a simple program that uses Google Cloud Vision API to detect faces in an image.
You will learn today:
- What is a computer program?
- How to write a program in Python?
- Python syntax
- How to organize your code in python using functions
- What is a REST API, and how to use it?
- How to use Google Cloud Vision API to detect faces in an image?
So lets started!!!
What is a computer program?
A computer program is a sequence of instructions we write using a programming language to tell the computer what to do for us. This sequence of instructions contains but is not limited to: Show information to the user, ask the user for input, save and recover data in memory/disk, and perform calculations. So, programming languages provide a set of built-in functions and instructions that can be used to accomplish these tasks.
If we think about programming languages, we can compare them to different idioms we use to communicate with others. We choose the appropriate language based on the application or where our program will run.
How to write a program in Python?
When we write programs independent of the programming language we decide to use, writing down our algorithm in simple words is always helpful. In a way, we can have a mental model of what our program will be doing and how it will be executed. To do so, we can use Pseudocode, a simplified version of computer programs written in natural or human-readable language that can be easily interpreted. You can check this cheat sheet that will help you to write your program in Pseudocode.
Finite set of rules to be followed in calculations or other problem-solving operations, especially by a computer.
So, let's define our pseudocode for our face detection program:
VAR image_path as STRING = INPUT("Please provide the path of the image: ")
IF image_path is empty or image_path dont exist THEN
PRINT("image path could not be empty or the image does not exist")
EXIT
ENDIF
FUNCTION read_image(image_path) as STRING
VAR image_bytes as BYTES = read(image_path)
RETURN image_bytes
ENDFUNCTION
FUNCTION detect_faces_on_image(image_bytes as BYTES) as LIST:
api_response as LIST = call_face_detection_gcp_api(image_bytes)
IF api_response is empty THEN
PRINT("No faces found")
RETURN
faces as LIST = []
FOR json_entity in api_response THEN
face as DICT = {
"confidence": json_entity.confidence,
"bounding_box": json_entity.bounding_box,
"is_happy" : json_entity.joy_likelihood == "VERY_LIKELY"
}
faces.append(face)
ENDFOR
RETURN faces
ENDFUNCTION
image_bytes = read_image(image_path)
faces_list = detect_faces(image_bytes)
display_detect_faces(faces_list)
As you can see in Pseudocode, we can skip the implementation details of our program. We write down our algorithm using a high-level language, so in this way, we have a big picture of the tasks we need to perform that we can use later to translate our algorithm into a programming language. In line 13, for instance, we need to call the Google Cloud Vision API to detect the faces in the image, but we have yet to determine how it will be implemented.
Python syntax
To learn about python syntax, we will navigate through the Pseudocode and convert it into a python script.
Variables
Programming is all about data and manipulating it to solve problems. So, we need to have a way to store data on our computer that we can access later during execution. To do so, we use variables. Variables are a way to store data in memory where we can save almost any data. In Python, it is straightforward to define a variable; we need to use the assignment operator =
followed by the value we want to store, and the Python interpreter will take care of the rest. Under the hood, it will allocate memory for the variable and store its value. We can save strings, integers, floats, booleans, lists, dictionaries, and other data types. Let's see an example:
# String
my_string = "Hello World"
# Integer
my_integer = 1
# Float
my_float = 1.0
# Boolean
my_boolean = True
# List
my_list = [1, 2, 3]
# Dictionary
my_dict = {"key": "value"}
We can obtain the memory address and type of a variable using the id()
and type()
functions respectively.
my_string = "Hello World"
my_string_2 = "Hello World"
print(id(my_string))
print(id(my_string_2))
print(type(my_string))
print(type(my_string_2))
In our program, in line 1, we define a variable image_path
and assign it the value of the user input. In Python, the input()
function allows us to grab information from the user so we can save the value into a variable. Let's see how we can translate this Pseudocode line into Python:
image_path = input("Please provide the path of the image:")
The syntax is very similar to the Pseudocode. However, you can notice that in Python, we don't specify the variable type. That is because Python is a dynamically typed language, meaning that the variable type is inferred during the execution. In terms of productivity, this is very convenient because we don't need to worry about specifying the type of the variables when we define them. However, it can sometimes be a source of errors if we are not carefully doing operations.
Python will raise an error if we try to perform an operation that is not supported by the type of the variable. Let's see an example:
a = 2
b = "2"
print(a + b)
# TypeError: unsupported operand type(s) for +: 'int' and 'str'
- A variable name must start with a letter or an underscore character.
- A variable name cannot start with a number.
- A variable name can only contain alpha-numeric characters and underscores (A-z, 0-9, and _ ).
- Variable names are case-sensitive (name, Name and NAME are three different variables).
- The reserved words(keywords) cannot be used naming the variable.
Conditional blocks
A common task in programming is to execute a block of code only if a condition is met. In Python, we can use the if
statement to do so. Let's see an example:
a = 2
if a == 2:
print("a is equal to 2")
In the example above, we check if the variable a
is equal to 2. If that is the case, we print the message "a is equal to 2". We can also use the else
statement to execute a block of code if the condition is not met. Let's see an example:
a = 2
if a == 2:
print("a is equal to 2")
else:
print("a is not equal to 2")
In our program, we need to check if the user input is empty or if the image path does not exist. We can use the if
statement to do so. Let's see how we can translate this Pseudocode line into Python:
import os
image_path = input("Please provide the path of the image:")
if image_path == "" or not os.path.exists(image_path):
print("image path could not be empty or the image does not exist")
exit()
Again, we can observe that the syntax is very similar to the Pseudocode, just with the addition of the os.path.exists()
function in the condition to check whether a image exists or not. The os module it is included in the Python standard library, and it provides a way to interact with the operating system. We also use the exit()
function to exit the program in case the condition is met. We are going to discuss about modules later in this article.
The Python Standard Library is a set of modules that comes with the Python installation. It provides a wide range of built-in functions and classes that we can use in our programs for different purposes. You can find more information about the Python Standard Library here.
Some of the most used modules are:
- os: provides a way to interact with the operating system.
- sys: provides a way to interact with the Python interpreter.
- json: provides a way to work with JSON data.
- re: provides a way to work with regular expressions.
- math: provides a way to work with mathematical operations.
- random: provides a way to work with random numbers.
- datetime: provides a way to work with dates and times.
- urllib: provides a way to work with URLs.It is a very useful module to work with APIs. We are going to use it in the next section to call the Google Cloud Vision API.
Functions
Functions are a way to encapsulate a block of code that we can reuse in our program. In Python, we can define a function using the def
keyword followed by the function name and the parameters. Let's see an example:
def add(a, b):
"""
This function adds two numbers
:param a: first number
:param b: second number
:return: sum of the two numbers
"""
return a + b
if __name__ == "__main__":
print(add(1, 2))
The paremeters are the variables that we need to pass to the function to perform the task. In the example above, we define a function called add
that takes two parameters a
and b
, and the function returns the sum of the values of the two parameters. We can call the function by using the function name followed by the parameters. In the example above, we call the function add
with the parameters 1
and 2
. The function returns the value 3
and we print it in the console.
In our program, we have two main functions that we need to implement, read_image
and call_face_detection_gcp_api.
The first takes the image path as a parameter and returns the image data. The second takes the image data as a parameter, requests the Google Cloud Vision API to detect faces in the image, and returns the face annotations in JSON format. Let's see how we can translate the read_image
function from Pseudocode into Python:
def read_image(image_path: str) -> bytes:
"""
Read image from file and return as bytes
:param image_path: path of the image
:return: image as bytes
"""
with open(image_path, "rb") as f:
return f.read() # read the image's bytes
There is a new syntax here in the read_image
function to be discussed.
- Function annotations: Although not mandatory, we can specify the parameters' type and the functions' return value in Python. These are called function annotations. Although the Python interpreter does not enforce them, and we still have to check the type of the parameters programmatically, annotations are extremely useful for other project contributors to navigate through the code and understand how the function must be called. In the example above, we specify that the function takes a string as a parameter and returns a bytes object.
Another good thing about function annotations is that It makes the function more readable and will also helps to avoid errors when function is called in other parts of the program.
-
Context Managers: It can also be noticed that we use the
with
statement to open the file in theread_image
function. These blocks of code are called context managers in Python, and in this case, it ensures that the file is closed after the block of code is executed. We will discuss context managers later in other articles since this is an advanced topic. For more information about context managers, you can check the Python documentation. -
Encoding: We can also see that we use the
rb
mode to open the file. This mode allows us to read the file as bytes so we can encode it in base64 to send it to the Google Cloud Vision API. That is required because the API only accepts images encoded in this format. For more information about therb
mode, you can check the Python documentation, and face detection API documentation here.
Encodings are a way to represent a sequence of bytes in a different format. The most common encodings are ASCII, UTF-8, and base64. ASCII is a 7-bit encoding that represents the first 128 characters of Unicode. UTF-8 is a variable-length encoding that represents the first 1,112,064 characters of Unicode. Base64 is a way to represent binary data in ASCII characters and it is used to send binary data in text-based protocols such as HTTP. For more information about encodings, you can check the Python documentation.
- Error handling: In order to catch the errors in our python functions we can use the
try
andexcept
block. Thetry
statement allows us to execute a block of code and catch the errors that can happen in theexcept
statement. Afinally
block can also be used to execute a block of code after thetry
andexcept
blocks. Let's see how to do this:
def read_image(image_path: str) -> bytes:
"""
Read image from file and return as bytes
:param image_path: path of the image
:return: image as bytes
"""
try:
# read and load the image into memory
with open(image_path, "rb") as f:
return f.read() # read the image's bytes
except Exception as e:
raise Exception("Error reading the image: ", e)
finally:
print("finally block")
Modules
Modules are a way to group a set of functions and classes in our programs. In Python, we can import a module using the import
keyword followed by the module name.
import os # import the os module
if __name__ == "__main__":
print(os.path.exists("image.jpg"))
In the example above, we import the os
module and use the os.path.exists()
function to check if the file image.jpg
exists. We can also import a specific function from a module using the from
keyword. Let's see an example:
from os import path
if __name__ == "__main__":
print(path.exists("image.jpg"))
Following with our example, to implement the call_face_detection_gcp_api
function, we need to import the urllib
module. This module provides a set of function we can use to call the Google Cloud Vision API. Let's see how to do this:
import base64
import urllib.error
import urllib.parse
import urllib.request
import json
import os
def read_image(image_path: str) -> bytes:
"""
Read image from file and return as bytes
:param image_path: path of the image
:return: image as bytes
"""
# read and load the image into memory
with open(image_path, "rb") as f:
return f.read() # read the image's bytes
def image_to_base64(image_bytes: bytes) -> str:
"""
Convert image to base64 string so it can be sent to the API
:param image_bytes:
:return: base64 string
"""
return base64.b64encode(image_bytes).decode("utf-8")
def call_face_detection_gcp_api(image_bytes: bytes, API_KEY: str = None) -> dict:
"""
Call GCP Face Detection API
:param API_KEY: API Key for Google Cloud Platform
:param image_bytes: image as bytes
:return: response the face annotations as JSON
"""
api_url = f"https://vision.googleapis.com/v1/images:annotate?key={API_KEY}"
image_base64 = image_to_base64(image_bytes)
request_body = {
"requests": [
{
"image": {
"content": image_base64
},
"features": [
{
"type": "FACE_DETECTION",
"maxResults": 10
}
]
}
]
}
# Convert request body to JSON format
request_body = json.dumps(request_body).encode("utf-8")
# Create request
request = urllib.request.Request(api_url, data=request_body)
# Set request header
request.add_header("Content-Type", "application/json")
try:
# Send request
response = urllib.request.urlopen(request)
# Read response body as bytes
response_body_bytes = response.read()
# # Convert response body to JSON format
response_body_text = response_body_bytes.decode("utf-8")
# Convert response body to JSON format
response_body_json = json.loads(response_body_text)
# Convert response to JSON format
return response_body_json["responses"][0]["faceAnnotations"]
except urllib.error.HTTPError as e:
# Get error message
error_message = json.loads(e.read())["error"]["message"]
error_code = e.code
if e.code == 400:
error_status = "Bad Request"
elif e.code == 401:
error_status = "Unauthorized"
elif e.code == 403:
error_status = "Forbidden"
elif e.code == 404:
error_status = "Not Found"
elif e.code == 500:
error_status = "Internal Server Error"
elif e.code == 503:
error_status = "Service Unavailable"
else:
error_status = "Unknown Error"
raise Exception(f"Error {error_code} calling the GCP Face Detection API: {error_status} - {error_message}")
For more information about using the urlib
module, you can check the Python documentation.
REST stands for Representational State Transfer. It is an architectural style for designing networked applications, that allows to expose data and functionality to external clients in public(wan) or private(lan) networks. Clients could be web applications, mobile applications, or even other services. For more information about REST APIs, you can check the Wikipedia page.REST APIs are implemented using HTTP methods. The most common methods are GET
, POST
, PUT
, PATCH
, and DELETE
, you can check the Wikipedia page. It also provides standard data formats to send and receive data, for instance JSON
and XML
. More information here.
The video below give you a quick overview of how REST APIs work:
Loops
Loops are a way to execute a block of code multiple times. In Python, we can use the for
loop to iterate over a list of elements. Let's see an example:
for i in range(10):
print(i)
The range
function returns a list of numbers from 0 to 10. The for
loop iterates over the list and prints each element. The range
function can also receive a start and end value. Let's see an example:
for i in range(5, 10):
print(i)
The range
function can also receive a step value. Let's see an example:
for i in range(5, 10, 2):
print(i)
The for
loop can also be used to iterate over a list of elements. Let's see an example:
for i in [1, 2, 3, 4, 5]:
print(i)
The for
loop can also be used to iterate over a dictionary. Let's see an example:
for key, value in {"a": 1, "b": 2, "c": 3}.items():
print(f"key: {key}, value: {value}")
The for
loop can also be used to iterate over a string. Let's see an example:
for char in "Hello World":
print(char)
The while
loop is used to execute a block of code while a condition is true. Let's see an example:
i = 0
while i < 10:
print(i)
i += 1
In our code in the last function of our script, we need to iterate over the list of faces returned by the API. Let's see how we do this in line 18
def detect_faces_on_image(image_bytes: bytes, API_KEY: str = None) -> list:
"""
Detect faces on image
:param API_KEY: API Key for Google Cloud Platform
:param image_bytes: image as bytes
:return: list of faces found
"""
# Call Google Cloud Platform Face Detection API
api_response = call_face_detection_gcp_api(image_bytes, API_KEY)
# Check if API response is empty
if not api_response:
print("No faces found")
return [] # return empty list
# Create list to store faces
faces = []
for json_entity in api_response:
face = {
"bounding_box": json_entity["boundingPoly"],
"is_happy": json_entity["joyLikelihood"] in ["VERY_LIKELY", "LIKELY"],
}
faces.append(face)
return faces
I skipped the call_face_detection_gcp_api
function explanation since it was supposed to be an introductory tutorial. However, I have tried my best to comment on the code so you can develop an intuition on what the function does. To get more information about how to call the GCP face detection API,
you can check the official documentation here. You must create a Google Cloud Platform account to use the API. To see how to create the project and get the API key, you can check the official documentation.
In the next section, we will see how to do more advance things with Python using third party packages and libraries. For now I will leave you with the full code of the script:
import base64
import urllib.error
import urllib.parse
import urllib.request
import json
import os
def read_image(image_path: str) -> bytes:
"""
Read image from file and return as bytes
:param image_path: path of the image
:return: image as bytes
"""
# read and load the image into memory
with open(image_path, "rb") as f:
return f.read() # read the image's bytes
def image_to_base64(image_bytes: bytes) -> str:
"""
Convert image to base64 string
:param image_bytes:
:return:
"""
# Convert image to base64 string, so it can be sent to the API
return base64.b64encode(image_bytes).decode("utf-8")
def call_face_detection_gcp_api(image_bytes: bytes, API_KEY: str = None) -> dict:
"""
Call Google Cloud Platform Face Detection API
:param API_KEY: API Key for Google Cloud Platform
:param image_bytes: image as bytes
:return:
"""
api_url = f"https://vision.googleapis.com/v1/images:annotate?key={API_KEY}"
# Convert image to base64 string, so it can be sent to the API
image_base64 = image_to_base64(image_bytes)
# Create request body
request_body = {
"requests": [
{
"image": {
"content": image_base64
},
"features": [
{
"type": "FACE_DETECTION",
"maxResults": 10
}
]
}
]
}
# Convert request body to JSON format
request_body = json.dumps(request_body).encode("utf-8")
# make request
request = urllib.request.Request(api_url, data=request_body)
# Set request header
request.add_header("Content-Type", "application/json")
try:
# Send request
response = urllib.request.urlopen(request)
# Read response body as bytes
response_body_bytes = response.read()
# # Convert response body to JSON format
response_body_text = response_body_bytes.decode("utf-8")
# Convert response body to JSON format
response_body_json = json.loads(response_body_text)
# Convert response to JSON format
return response_body_json["responses"][0]["faceAnnotations"]
except urllib.error.HTTPError as e:
# Get error message
error_message = json.loads(e.read())["error"]["message"]
error_code = e.code
if e.code == 400:
error_status = "Bad Request"
elif e.code == 401:
error_status = "Unauthorized"
elif e.code == 403:
error_status = "Forbidden"
elif e.code == 404:
error_status = "Not Found"
elif e.code == 500:
error_status = "Internal Server Error"
elif e.code == 503:
error_status = "Service Unavailable"
else:
error_status = "Unknown Error"
raise Exception(f"Error {error_code} calling the GCP Face Detection API: {error_status} - {error_message}")
def detect_faces_on_image(image_bytes: bytes, API_KEY: str = None) -> list:
"""
Detect faces on image
:param API_KEY: API Key for Google Cloud Platform
:param image_bytes: image as bytes
:return:
"""
# Call Google Cloud Platform Face Detection API
api_response = call_face_detection_gcp_api(image_bytes, API_KEY)
# Check if API response is empty
if not api_response:
print("No faces found")
return [] # return empty list
# Create list to store faces
faces = []
for json_entity in api_response:
face = {
"bounding_box": json_entity["boundingPoly"],
"is_happy": json_entity["joyLikelihood"] in ["VERY_LIKELY", "LIKELY"],
}
faces.append(face)
return faces
def main():
try:
image_path = input("Please provide the path of the image:")
assert image_path != "" and os.path.exists(image_path), "image path could not be empty or the image does not exist"
# read and return image as bytes
image = read_image(image_path)
# pass the image to the face detection function to detect faces
faces = detect_faces_on_image(image, API_KEY="<GCP API KEY>")
# print the number of faces found
print("number of faces found:", len(faces))
# iterate over the faces and do something
for face in faces:
print(face["is_happy"])
except Exception as e:
print(f"Error running the script: {e}")
if __name__ == "__main__":
main()
This is all for this tutorial. I hope you enjoyed it. If you have any questions, please leave a comment below or contact me on LinkedIn. If you want to see more tutorials like this, please subscribe to my newsletter (See top menu). To access the code for this tutorial, you can check the GitHub repository