Python Lesson 47: Image Rotation (AI pt. 13)

Hello loyal readers,

Michael here, and in this post, we’ll cover another fun OpenCV topic-image rotation!

Let’s rotate an image!

First off, let’s figure out how to rotate images with OpenCV. Here’s the image we’ll be working with in this example:

This is an image of the Jumbotron at First Horizon Park in Nashville, TN, home ballpark of the Nashville Sounds (Minor League Baseball affilate of the Milwaukee Brewers)

Now, how do we rotate this image? First, let’s read in our image in RGB colorscale:

import cv2
import matplotlib.pyplot as plt

ballpark=cv2.imread(r'C:\Users\mof39\Downloads\20230924_140902.jpg', cv2.IMREAD_COLOR)
ballpark=cv2.cvtColor(ballpark, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10, 10))
plt.imshow(ballpark)

Now, how do we rotate this image? Let’s start by analyzing a 90-degree clockwise rotation:

clockwiseBallpark = cv2.rotate(ballpark, cv2.ROTATE_90_CLOCKWISE)
plt.figure(figsize=(10, 10))
plt.imshow(clockwiseBallpark)

All it takes to rotate an image in OpenCV is the cv2.rotate() method and two parameters-the image you wish to rotate and one of the following OpenCV rotation codes (more on these soon):

  • cv2.ROTATE_90_CLOCKWISE (rotates image 90 degrees clockwise)
  • cv2.ROTATE_180 (rotates image 180 degrees clockwise)
  • cv2.ROTATE_90_COUNTERCLOCKWISE (rotates image 270 degrees clockwise-or 90 degrees counterclockwise)

Let’s analyze the image rotation with the other two OpenCV rotation codes-first off, the ballpark image rotated 180 degrees clockwise:

clockwiseBallpark = cv2.rotate(ballpark, cv2.ROTATE_180)
plt.figure(figsize=(10, 10))
plt.imshow(clockwiseBallpark)

Alright, pretty impressive. It’s an upside down Jumbotron!

Now to rotate the image 270 degrees clockwise:

clockwiseBallpark = cv2.rotate(ballpark, cv2.ROTATE_90_COUNTERCLOCKWISE)
plt.figure(figsize=(10, 10))
plt.imshow(clockwiseBallpark)

Well well, it’s the amazing rotating Jumbotron!

And yes, in case you’re wondering, the rotation code cv2.ROTATE_90_COUNTERCLOCKWISE is the correct rotation code for a 270 degree clockwise rotation because a 90 degree counterclockwise rotation is the same thing as a 270 degree clockwise rotation.

Now, I know I just discussed three possible ways to rotate an image. However, what if you wanted to rotate an image in a way that’s not 90, 180, or 270 degrees. Well, if you try to do so with the cv2.rotate() method, you’ll get an error:

clockwiseBallpark = cv2.rotate(ballpark, 111)
plt.figure(figsize=(10, 10))
plt.imshow(clockwiseBallpark)

TypeError: Image data of dtype object cannot be converted to float

When I tried to rotate this image 111 degrees clockwise, I got an error because the cv2.rotate() method will only accept one of the three rotation codes I mentioned above.

Let’s rotate an image (in any angle)!

However, if you want more freedom over how you rotate your images in OpenCV, use the cv2.getRotationMatrix2D() method. Here’s an example as to how to use it:

height, width = ballpark.shape[:2]
center = (width/2, height/2)
rotationMatrix = cv2.getRotationMatrix2D(center,55,1)
rotatedBallpark = cv2.warpAffine(ballpark, rotationMatrix,(height, width)) 
plt.figure(figsize=(10, 10))
plt.imshow(rotatedBallpark)

To rotate an image in OpenCV using an interval that’s not a multiple of 90 degrees (90, 180, 270), you’ll need to use both the cv2.getRotationMatrix2D() and the cv2.warpAffine() method. The former method sets the rotation matrix, which refers to the degree (either clockwise or counterclockwise) that you wish to rotate this image. The latter method actually rotates the image.

Since both of these are new methods for us, let’s dive into them a little further! First off, let’s explore the parameters of the cv2.getRotationMatrix2D() method:

  • center-this parameter indicates the center of the image, which is necessary for rotations not at multiples-of-90-degrees. To get the center, first retrieve the image’s shape and from there, retrieve the height and width. Once you have the image’s height and width, create a center 2-element tuple where you divide the image’s width and height by 2. It would also be ideal to list the width before the height, but that’s just a programmer tip from me.
  • angle-the angle you wish to use for the image rotation. In this example, I used 55, indicating that I want to rotate the image 55 degrees clockwise. However, if I wanted to rotate the image 55 degrees counterclockwise, I would’ve used -55 as the value for this parameter.
  • scale-This is an integer that represents the factor you wish to use to zoom in the rotated image. In this example, I used 1 as the value for this parameter, indicating that I don’t want to zoom in the rotated image at all. If I’d used a value greater than 1, I’d be zooming in, and if I was using a value less than 1, I’d be zooming out.

Next, let’s explore the parameters of the cv2.warpAffine() method!

  • src-The image you wish to rotate (in this example, I used the base ballpark image)
  • M-The rotation matrix you just created for the image using the cv2.getRotationMatrix2D() method (ideally you would’ve stored the rotation matrix in a variable).
  • dsize-A 2-element tuple indicating the size of the rotated image; in this example, I used the base image’s height and width to keep the size of the rotated image the same.

Now for some extra notes:

  • Why is the rotation method called warpAffine()? This is because the rotation we’re performing on the image is also known as an affine transformation, which transforms the image (in this case rotating it) while keeping its same shape.
  • You’ll notice that after rotating the image using the cv2.warpAffine method, the entire image isn’t visible on the plot. I haven’t figured out how to make the image visible on the plot but when I do, I can certainly share my findings here. Though I guess a good workaround solution would be to play around with the size of the plot.

Thanks for reading, and for my readers in the US, have a wonderful Thanksgiving! For my readers elsewhere on the globe, have a wonderful holiday season (and no, this won’t be my last post for 2023)!

Python Lesson 46: Image Blurring (AI pt. 12)

Hello everybody,

Michael here, and in this post, we’ll explore image blurring! Image blurring is a pretty self-explanatory process since the whole point of image blurring is to make the image, well, blurry. This process has many uses, such as blurring the background on your work video calls (and yes, I do that all the time during work video calls).

Intro to image blurring

Now that we know a little bit about image blurring, let’s explore it with code. Here’s the image that we’ll be using:

The photo above is of Stafford Park, a lovely municipal park in Miami Springs, FL.

Unlike image eroding, image blurring has a pretty self-explanatory description since the aim of this process is to, well, blur images. How can we accomplish this through OpenCV?

Before we get into the fun image-blurring code, let’s discuss the three main types of image blurring that are possible with OpenCV:

  • Gaussian blur-this process softens out any sharp edges in the image
  • Median blur-this process helps remove image noise* by changing pixel colors wherever necessary
  • Bilateral blur-this process makes the central part of the image clearer while making any non-central part of the image fuzzier

*For those unfamiliar with image processing, image noise is (usually unwanted) random brightness or color deviations that appear in an image. Median blurring assists you with removing image noise.

Now that we know the three different types of image blurring, let’s see them in action with the code

Gaussian blur

Before we start to blur the image, let’s read in the image in RGB colorscale:

import cv2
import matplotlib.pyplot as plt

park=cv2.imread(r'C:\Users\mof39\OneDrive\Documents\20230629_142648.jpg', cv2.IMREAD_COLOR)
park=cv2.cvtColor(park, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10, 10))
plt.imshow(park)

Next, let’s perform a Gaussian blur of the image:

gaussianPark = cv2.GaussianBlur(park, (7, 7), 10, 10)
plt.figure(figsize=(10,10))
plt.imshow(gaussianPark)

Notice anything different about this image? The sharp corners in this photo (such as the sign lettering) have been smoothed out, which is the point of Gaussian blurring (to smooth out rough edges in an image).

Now, what parameters does the cv2.GaussianBlur() method take?

  • The image you wish to blur (park in this case)
  • A 2-integer tuple indicating the size of the kernel you wish to use for the blurring process-yes, this is similar to the kernels we used for image erosion in the previous post Python Lesson 45: Image Resizing and Eroding (AI pt. 11) (we’re using a 7-by-7 kernel here).
  • Two integers that represent the sigmaX and sigmaY of the Gaussian blur that you wish to perform. What are sigmaX and sigmaY? Both integers represent the numerical factors you wish to use for the image blurring-sigmaX being the factor for horizontal blurring and sigmaY being the factor for vertical blurring.

A few things to keep in mind regarding the Gaussian blurring process:

  • Just as you did with image erosion, ensure that both dimension of the blurring kernel are positive and odd-numbered integers (like the 7-by-7 kernel we used above).
  • sigmaX and sigmaY are optional parameters, but keep in mind if you don’t include a value for either of them, both will default to a 0 value, which might not blur your picture the way you intended. Likewise, if you use a very high value for both sigmas, you’ll end up with a very, very blurry picture.

Median blur

Since median blurring helps remove image noise, we’re going to be using this altered park image with a bunch of noise for our demo:

Next up, let’s explore the median blur with our noisyPark image:

medianPark = cv2.medianBlur(noisyPark, 5)
plt.figure(figsize=(10,10))
plt.imshow(medianPark)

As you can see, median blurring the noisyPark image cleared out a significant chunk of the image noise! But how does this function work? Let’s explore some of its parameters:

  • The image you wish to blur (noisyPark in this case)
  • A single integer indicating the size of the kernel you wish to use for the blurring process-yes, this is similar to the kernels we used for Gaussian blurring, but you only need a single integer instead of a 2-integer tuple (we’re using a 5-by-5 kernel here). The integer must be a positive and odd number since the kernel must be an odd number (same rules as the Gaussian blur apply here for kernel creation).

Bilateral blur

Last but not least, let’s explore bilateral blurring! This time, let’s use the non-noise altered park image.

bilateralPark = cv2.bilateralFilter(park, 12, 120, 120) 
plt.figure(figsize=(10,10))
plt.imshow(bilateralPark)

Wow! As I mentioned earlier, the purpose of bilteral blurring is to make a central part of the image clearer while make other, non-central elements of the image blurrier. And boy, does that seem to be the case here since the central element of the image (the park sign and all its lettering) really pops out while everything in the background seems a bit blurrier.

How does the cv2.bilateralFilter() function work its magic? Here’s how:

  • The image you wish to blur (park in this case)
  • The diameter (in pixels) of the region you wish to iterate through to blur-in this case, I chose a 12-pixel diameter as my “blurring region”. It works in a similar fashion to the kernels we used for our “erosion region” in the previous lesson.
  • The next two integers-both 120-are the sigmaColor and sigmaSpace variables, respectively. The sigmaColor variable is a factor that considers how much color should be considered in the blurring process while the sigmaSpace variable is a factor that considers the proximity of several elements in the image (such as the runners in the background). The higher both of these values are, the blurrier the background will be.

Thanks for reading,

Michael

Python Lesson 45: Image Resizing and Eroding (AI pt. 11)

Hello everybody,

Michael here, and today’s lesson will be our first foray into image manipulation with OpenCV. We’ll learn two new techniques for image manipulation-resizing and eroding.

Let’s begin!

Resizing images

First off, let’s start this post by exploring how to resize images in OpenCV. Here is the image we’ll be working with throughout this post

This is an image of a hawk on a soccer goal at Sevier Park (Nashville, TN), taken in August 2021.

Now, how could we possible resize this image? Take a look at the code below (and yes, we’ll work with the RGB colorscale version of the image) to first upload and display the image:

import cv2
import matplotlib.pyplot as plt

hawk=cv2.imread(r'C:\Users\mof39\Downloads\20210807_172420.jpg', cv2.IMREAD_COLOR)
hawk=cv2.cvtColor(hawk, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(9, 9))
plt.imshow(hawk)

Before we start with resizing the image, let’s first get the image’s size (I’ll explain why this information will be helpful later):

print(hawk.shape)

(3000, 4000, 3)

To get the image’s size, use the print([image variable].shape) method. This method returns a 3-integer tuple that indicates height, width and dimensions; in the case of the hawk image, the image is 3000 px tall by 4000 px wide and 3-dimensional overall (px stands for pixels-recall that computer image dimensions are measured in pixels).

Now, how can we resize this image? Take a look at the code below:

smallerHawk = cv2.resize(hawk, (2000, 1500))
plt.imshow(smallerHawk)

As you can see here, we reduced the size of the hawk image in half without cropping out any of the image’s elements. How did we do that? We used the cv2.resize() method and passed in not only the hawk image but also a 2-integer tuple-(2000, 1500)-to indicate that I wanted to reduced the size of the hawk image in half.

Now, there’s something interesting about the (2000, 1500) tuple I want to point out. See, when we listed the shape of the image, the 3-inter tuple that was returned (3000, 4000, 3) listed the image’s height before its width. However, in the tuple we passed to the cv2.resize() method, the image’s width (well, half of the image’s width) was listed before the image’s height (rather, half the height). Listing the width before the height allows you to properly resize the image the way you intended.

Now, what happens when we make this image bigger? Take a look at the following code:

largerHawk = cv2.resize(hawk, (6000, 8000))
plt.figure(figsize=(9, 9))
plt.imshow(largerHawk)

Granted, the image may not appear larger at first, but that’s mostly due to how we’re plotting it on MATPLOTLIB. If you look closely at the tick marks on each axis of the plot, you will see that the image size has indeed doubled to 6000 by 8000 px.

Image erosion

The next image manipulation technique I want to discuss is image erosion. What does image erosion do?

The simple answer is that image erosion, well, erodes away the boundaries on an image’s foreground object, whatever that may be (if it helps, think of the OpenCV image erosion process like geological erosion, only for images). How the image erosion is acoomplished is more complicated than a simple method like cv2.resize(), however let’s explore the image erosion process in the code below:

import numpy as np
kernel = np.ones((5,5), np.uint8)
erodedHawk = cv2.erode(hawk, kernel)
plt.figure(figsize=(10,10))
plt.imshow(erodedHawk)

OK, so aside from the cv2.erode() method, we’re also creating a numpy array. Why is that?

Well, the numpy array kernel (aptly called kernel) is essentially a matrix of 1s like so:

[1 1 1 1 1
 1 1 1 1 1
 1 1 1 1 1
 1 1 1 1 1
 1 1 1 1 1]

Since we specified that our matrix is of size (5, 5), we get a 5-by-5 matrix of ones. Pretty simple right? Here are some other things to keep in mind when creating the kernel:

  • Make sure the kernel’s dimensions are both odd numbers to ensure the presence of a central point in the kernel.
  • Theoretically, you could create a kernel of 0s, but a kernel of 1s is better suited for image erosion.
  • Ideally, you should also include np.uint8 as the second parameter in the kernel creation. For those who don’t know, np.unit8 stands for numpy unsigned 8-bit integer. The reason I suggest using this parameter is because doing so will store the matrix as 8-bit integers, which is beneficial for memory optimization in computer programs.

Now, how does this kernel help with image erosion? See, the 5-by-5 kernel that we just created iterates through the image we wish to erode (hawk in this case) by checking if each pixel that borders the kernel’s central pixel is set to 0 or 1. If all pixels that border the central pixel in the image are set to 1, then the central pixel is also set to 1. Otherwise, the central pixel is set to 0?

What do the 0s and 1s all mean here? Notice how the leaves on the tree in this eroded image look slightly darker than the tree leaves in the original image. That’s because image erosion manipulates an image’s foreground (in this case, OpenCV percieves the tree as the foreground) by removing pixels from the foreground’s boundaries, thus making certain parts of the image appear slightly darker after erosion. The slightly darker tree leaves make the image of the hawk stand out more than it did in the original image.

Thanks for reading,

Michael

Python Lesson 44: Image Color Spaces (AI pt. 10)

Hello everybody,

Michael here, and today’s post will cover how to understand color spaces in images.

Granted, I’ve previously discussed various colorscales you can find in computer programming in this post-Colors in Programming-but in this post, we’ll take a deeper dive into the use of colors in images.

But first, what is a color space?

Well, as the header above asks, what is a color space? In the context of images, a color space is a way to represent a certain color channel in an image.

Still confused? Let’s take the image we used in our first computer vision lesson (it can be found here Python Lesson 42: Intro To Computer Vision Part One-Reading Images (AI pt. 8)). Assuming we’re analyzing the RGB image of Orange Boy, the color spaces simply represent the intensities (or spaces) of red, blue and green light in the image.

And now let’s analyze colorspaces in OpenCV

As the header says, let’s examine color spaces in Open CV! Here’s the image we’ll be using for this tutorial:

This is a photo of autumn at Bicentennial Capitol Mall State Park in Nashville, TN, taken in October 2022.

Before we start exploring colorspaces, let’s read in this image to our IDE using the RGB colorscale (which means you should remember to convert the image’s default colorscale):

import cv2
import matplotlib.pyplot as plt
park=cv2.imread(r'C:\Users\mof39\Downloads\20221022_101648.jpg', cv2.IMREAD_COLOR)
park=cv2.cvtColor(park, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(18, 18))
plt.imshow(park)

Great! Now that we have our RGB image, let’s explore the different color channels!

First off, let’s examine this image’s red colorspace! How can we do that? Take a look at the code below:

B, G, R = cv2.split(park)
plt.figure(figsize=(18, 18))
plt.imshow(R, cmap='Reds')

plt.show()

In this example, I used the first line of code (the one with B, G, R) to split the image into three distinct colorspaces-blue, green and red.

Aside from the standard plt.figure() functions, I did make a slight modification to the plt.imshow() function. Instead of simply passing in the park image, I passed in the R variable so that we see the image’s red colorspace AND passed in the cmap parameter with a value of Reds to display the red colorspace in, well, red.

Now, how can we show the green and blue colorspaces? We’d use the same logic as we did for the red colorspace, except swap the R in the plt.imshow() function for G and B for the green and blue colorspaces and change the cmap values to Greens and Blues, respectively.

Here’s the image’s blue colorspace:

plt.figure(figsize=(18, 18))
plt.imshow(B, cmap='Blues')
plt.show()

And here’s the image’s green colorspace:

plt.figure(figsize=(18, 18))
plt.imshow(G, cmap='Greens')
plt.show()

As you can see from all three of these color-altered images, the sky, park lawn, and buildings in the background are ceratinly more coloed than the trees, which look bright-white in all three color-altered images.

A little more on colorspace

Now that we’ve examined image colorspaces a bit, let’s see how we can find the most dominant color in an image! Take a look at the code below (which uses the park image):

from colorthief import ColorThief
colorthief =
ColorThief(r'C:\Users\mof39\Downloads\20221022_101648.jpg')
dominantColor = colorthief.get_color(quality=1)
print(dominantColor)

(120, 94, 72)

Granted, you could realistically use a package like numpy to find the most dominant color in an image, but the colortheif module is a much more efficient (and more fun) approach.

  • In case you didn’t know, you’ll need to pip install the colortheif module.

After creating a ColorTheif object (and passing in the image’s filepath on your computer), you’ll then need to use the get_color() method and pass in quality=1 as this method’s parameter. Using the quality=1 parameter will extract the most dominant color in an image.

  • You can certainly use a variable to store the most dominant color like I did here (I used the dominantColor variable) but that’s completely optional.

Once you print the dominant color, you’ll notice you don’t get a color name, but rather a 3-integer tuple that represents the frequency of red, blue and green in the image (the tuple is based off of the RGB colorscale). In this case, our most dominant color is RGB(120, 94, 72). What does that translate to?

In plain English, the most dominant color in this image is a very desaturated dark orange. If you take a look at the original RGB image, it makes sense not only because of the color of the park lawn but also due to all the trees and buildings in the image.

What if you want to know not only the most dominant color in an image, but also its color palette? The colortheif module can help you there too! Here’s how:

palette = colorthief.get_palette(color_count=5)
print(palette)

[(120, 94, 72), (179, 192, 208), (130, 160, 197), (28, 31, 32), (182, 141, 108)]

Just as colortheif did with the most dominant color in an image, all colors are represented as RGB 3-integer tuples. The get_palette() function helps returns the top X colors used in the image-the X is represented by the value of the color_count parameter. In plain English, five colors used in this image include:

  • very desaturated dark orange (the most dominant color)
  • grayish blue
  • slightly desaturated blue
  • very dark almost black blue
  • slightly desaturated orange.

This feature is like imagining a painter’s palette in Python form-pretty neat right! As you can see, our painter’s paletter for the park image has a lot of blues and oranges.

Thanks for reading!

Michael

Python Lesson 43: Intro to Computer Vision Part Two-Writing & Saving Images (AI pt. 9)

Hello everybody,

Michael here, and in today’s post, we’ll continue our introduction to computer vision, but this time we’ll explore how to write images to a certain place on your computer using OpenCV.

Let’s begin!

Let’s write an image!

Before we begin, here’s the image we will be working with:

This is an image of Simba/Orange Boy and his sister Marbles (on Christmas Day 2017 excited to get their presents), both of whom got an acknowledgement in The Glorious Five-Year Plan Part Two.

Now, here’s the code to read in the image to the IDE:

cats=cv2.imread(r'C:\Users\mof39\Downloads\IMG_4778 (1).jpg', cv2.IMREAD_COLOR)
cats=cv2.cvtColor(cats, cv2.COLOR_BGR2RGB)

Once this image is read onto the IDE, here’s the code we’d use to not only write this image but also save it to a certain directory on your computer:

import os

imagePath = r'C:\Users\mof39\Downloads\IMG_4778 (1).jpg'
imageDestination = r'C:\Users\mof39\OneDrive\Documents'

cats = cv2.imread(imagePath)
os.chdir(imageDestination)

savedImage = 'simbaandmarbles.jpg'
cv2.imwrite(savedImage, cats)

What does all of this code mean? Let me explain.

You’ll first need to import the os module (or pip install it if you haven’t already done so)-this will help you write and save the image to a specific directory.

The two variables that follow-imagePath and imageDestination-represent the current location of the image on my computer and the location on my computer where I wish to write and save the image, respectively. In this case, my image is currently located in my Downloads folder and I wish to send it to my Documents folder.

The cats variable is the result of reading in the image of the cats to the IDE. The os.chdir() function takes in one parameter-the string containing the image destination path. This function will allow you to set the destination of the image to ensure that your image is written and saved to the location you set in the imageDestination variable.

The savedImage variable allows you to set both the image name and the image extension to the image you wish to save and write-in this case, my image will be named simbaandmarbles and it will have a jpg extension.

Last but not least, use the cv2.imwrite() function to write and save the image to your desired directory (represented by the imageDestination variable). You’ll notice that this function takes two parameters-savedImage and cats in this example-but why might that be? Take a look at the code above and you’ll see why!

See, savedImage is the name we’d like to use for the saved image-this is a necessary paramater because we want OpenCV to save the image using the name/extension we specified. cats saves the image itself to the desired location (or imageDestination).

  • You should certainly change the values of imagePath, imageDestination and savedImage to reflect accurate image locations/destinations/names/extensions on your computer!

But wait! How do we know if our code worked? Take a look at the output below:

True

Since the output of this code returned True, the image was succesfully written and saved to the desired destination on our computer! Want another way to verify if our code worked? Take a look at my Documents folder (which was my imageDestination):

As you can see, my image was succesfully written to my Documents folder with the name/extension I specified (simbaandmarbles/JPG).

Now we know the image was succesfully written and saved to the Documents folder, but how do we know if the rendering worked? In other words, did OpenCV zoom in or crop too much of the image (or change the colorscale during the writing/saving process)? Click on the image to find out:

As you can see, not only did OpenCV correctly write and save the image to the correct location, but it also wrote and saved the image without changing the zoom-in/zoom-out view or the image’s colorscale!

And that, dear readers, is how you can write and save an image anywhere on your computer using eight simple lines of code!

Thanks for reading.

Michael

Python Lesson 42: Intro To Computer Vision Part One-Reading Images (AI pt. 8)

Hello everybody,

Michael here, and today’s Python lesson looks to be quite a bit of fun! Wonder why?

We’re introducing a new topic today-computer vision!

Now, one of the Python concepts I’ve covered over the course of this blog’s run is NLP-or natural language processing, which is a form of AI. Computer vision (or CV for short) is also a form of AI, but instead of using text language, computer vision deals with images.

An intro to OpenCV

To further explore computer vision in Python, we’ll introduce a package called OpenCV. If you don’t already have this package installed, here’s the line of code to run (either on your IDE or command prompt) to install it:

pip install opencv-python
  • Yes, you’ll need to install the opencv-python package. Using the line pip install opencv won’t work.
  • If you pip installing this or any other package on your IDE, include the ! before the pip install line.

Once we get our package installed, let’s start exploring the fun stuff it can do!

And now, time to explore OpenCV

The OpenCV concepts we’ll explore in this post are two of its simpler functions-reading an image onto the IDE and displaying that image onto the IDE!

Before we begin, here’s the image I’ll be using for this section in case you want to follow along with this tutorial.

  • Regular readers of Michael’s Programming Byte’s will likely recognize this cat as Simba/The Orange Boy who (along with his sister Marbles) got a well-deserved recognition on the second part of my fifth anniversary post (The Glorious Five-Year Plan Part Two).

Now to read the image into Python, here’s the code we’ll use for this tutorial:

import cv2

cat = cv2.imread(r'C:\Users\mof39\Downloads\IMG_5427.jpg', cv2.IMREAD_COLOR)
cv2.imshow("image", cat)
cv2.waitKey(60000)
cv2.destroyAllWindows()

Not sure what all this code means? Let’s break it down:

  • To use Python’s OpenCV package, you’d need to run the line import cv2, not import opencv.
  • The cv2.imread() function takes two parameters-the path to your image and the mode you want to use to read the image into the IDE. As of this writing, OpenCV has 13 different modes to read an image into the IDE! The IMREAD_COLOR mode allows you to display the image as a standard color image
  • The cv2.imshow() function also takes two parameters-the word image and the image variable (cat in this case) and unlike the cv2.imread() function, this function displays the image on your IDE (in this case, a window will pop up)
  • The cv2.waitKey() method takes an integer as a parameter. The point of this function is to close the window with the image after a specified number of milliseconds-I used 60000 milliseconds in this case (equal to 1 minute).
  • The cv2.destroyAllWindows() function takes no parameters since all it does is, well, destroy all open windows in the IDE after the specified number of millseconds (specified by the cv2.waitKey() function).

The cv2.waitKey() and cv2.destoryAllWindows() functions are optional to include, but if you don’t include them, the window with the image will simply stay open unless you close it.

Troubleshooting and the magic of OpenCV colorscales

Now here’s what the image looks like after it’s read into the IDE with OpenCV:

I’ll be honest, even though OpenCV did succesfully read and display the image, it didn’t do a good job of processing the image. How can we fix this? Take a look at the code below:


import matplotlib.pyplot as plt
cat=cv2.imread(r'C:\Users\mof39\Downloads\IMG_5427.jpg', cv2.IMREAD_COLOR)
plt.figure(figsize=(8,8))
plt.imshow(cat)

Now, let’s see what kind of output we get:

OK, so what did we do differently? Well, we used a combination of the MATPLOTLIB* and OpenCV packages to read our image onto the IDE and display it as well. While that combination of packages did the trick when it came to displaying the entire image on the IDE, you’ll notice that the cat (along with most other things in the image) looks rather blue.

*The plt.imshow() function is a MATPLOTLIB function.

Why might that be? After all, we did use the IMREAD_COLOR mode to read the image into the IDE, so how did we get this result? Take a look at the revised code below for a solution to this issue:

cat=cv2.imread(r'C:\Users\mof39\Downloads\IMG_5427.jpg', cv2.IMREAD_COLOR)
cat=cv2.cvtColor(cat, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(8,8))
plt.imshow(cat)

Here’s the output we get:

I used most of the same code as I did for the previous example, with one additional line-the line that uses the cv2.cvtColor() function.

Why did I need to use this function? Well, if you’re wondering why Orange Boy looked a little blue in the first image, that’s because OpenCV read the first image in the BGR colorscale, which is the OpenCV default method of reading images.

The BGR colorscale stands for blue, green, red colorscale and it reads in images based off of the intensity of blue, green, and red light in the image. Since the blue light in the image was the most intense, the image looked blue upon display.

Now, here’s where the cv2.cvtColor() function comes in! This function takes two parameters-the image whose colorscale you want to convert and the conversion mode you want to use for the image. There are over a dozen conversion modes you can use for the image, but in this example, we’ll use the COLOR_BGR2RGB conversion mode, which changes the image’s colorscale from BGR to RGB (or red, green, blue). After running this function, then run the plt.imshow() function on the color-converted Orange Boy image so that you can see the normal-looking image, not the Blue Boy image.

Now, why does OpenCV read in images with a BGR colorscale by default? The reason for this is because back when OpenCV was first developed (in the summer of 2000), the BGR colorscale was the more popular colorscale to use for computer graphics. However, the RGB colorscale has since become the more widely-adopted colorscale for computer graphics (including Python packages like MATPLOTLIB)-and in all my honesty, I think it makes images display a lot better.

Another colorscale conversion, why not?

Now that I’ve taught you the basics of reading images into the Python IDE with the OpenCV package, let’s have a little fun with the colorscale conversions, shall we? Take a look at the code below:

cat=cv2.imread(r'C:\Users\mof39\Downloads\IMG_5427.jpg', cv2.IMREAD_COLOR)
cat=cv2.cvtColor(cat, cv2.COLOR_BGR2RGB)
cat=cv2.cvtColor(cat, cv2.COLOR_RGB2HSV)
plt.figure(figsize=(8,8))
plt.imshow(cat)

In this example, I converted the image of Orange Boy first to the RGB colorscale and then to the HSV (hue, saturation, value) colorscale. As you can see, the cat looks like something out of a thermal camera.

  • If you want an image like the one I got here, then you’ll need to first convert the image to RGB colorscale BEFORE converting to another colorscale!
  • The cv2.cvtColor() function contains about 150 different color conversion modes!
  • Just in case you’re wondering what the HSV colorscale is, here’s a simple three-bullet-point explanation:
  • H stands for hue, which represents the type of color the image contains. The value for H is represented on the color wheelusing a value from 0-360 degrees (as in angle degrees).
  • S stands for saturation, which represents the intensity (or well, saturation) of the color. The value for S is represented on a scale between 0% (fully desaturated pure grey)-100% (fully saturated pure color)
  • V stands for value (or brightness), which represents how bright or dark the color will appear in the image. The value for V, just as with S, is represented on a scale between 0% (fully black)-100% (fully illuminated color).

Thanks for reading and be sure to stay tuned for Part Two of Intro to Computer Vision!

Michael

Post No. 12.25i: And Now Let’s Explore Irrational Numbers

Hello everybody,

Michael here, and today for my 150th post I thought I’d do something a little different. This time, something with a broader scope-irrational nnumbers in programming. This isn’t my first broad post (after all, I did Colors in Programming in November 2021) but hey, what better time for a broad post than my 150th overall post (did anyone catch the little detail in the post title hinting that this is the 150th post?).

But first, a little bit on irrational numbers

What are irrational numbers? You’ve likely heard of them if you’ve at least taken pre-algebra but for those who don’t know or don’t remember, irrational numbers arre simply numbers that can’t be expressed as fractions.

What would a rational number look like? Well, think of the 10 cardinal digits from 0-9, any fraction like 1/2 or 3/4 (yes, even improper fractions count as rational numbers), and any decimal (regardless of how many decimal points it carries on for).

Now, what would an irrational number look like? I went over one of them in an earlier post-e, also known as Euler’s Number-2.71828 (check out R Lesson 30: Logarithms for more information on Euler’s number). Some other irrational numbers include imaginary numbers (like 3+2i), the square roots of all negative numbers and positive prime numbers (which in turn are imaginary), the golden ratio, and of course, PI.

Imaginary numbers are their own special type of irrational numbers-and it’s important to remember that while all imaginary numbers are irrational, not all irrational numbers are imaginary. Imaginary numbers always contains an imaginary unit i (though the letter used to represent that imaginary unity can vary-more on that later), which equals the square root of -1.

If a number has a “real” and “imaginary” part, then it is referred to as a complex number. Take the complex number 3+2i. The 3 would be the “real” part and the 2i would be the “imaginary” part. The same logic would apply for a number like 6i, which, even though it only has a single part, is still consered a complex number since it has a “real” part (a hidden 0) and an imaginary part (6i).

  • For those that haven’t heard of the golden ratio before, it’s a mathematical constant (just like PI and e) that equals approximately 1.61 that is often considered as an “aesthethically pleasing” number as it can be found in fields such as art, nature, and photography (just to name a few).

Imaginary numbers in programming

Now that we’ve discussed the basics of what irrational numbers are all about, let’s explore how they’re used in programming.

First off, let’s see how imaginary numbers work in programming! Here’s an example of simple arthemic with imaginary numbers in Python:

print(3+5j*2)
(3+10j)

As you can see, Python is capabale of simple arithmetic with imaginary numbers (and is likely capabale of far more complex mathematics too). Also notice how Python uses a j to represent an imaginary unit instead of an i. Watch what happens when you try to use an i in this expression:

print(3+5i*2)

File "<ipython-input-5-f1daeb22835b>", line 1
    print(3+5i*2)
             ^
SyntaxError: invalid syntax

Trying to use i for the imaganiary unit will give you an error in this expression.

Now let’s take a look at how imaginary numbers are used in other programming languages! Here are two examples of expressions with imaginary numbers in R:

(3i+4)*(2i-2)
[1] -14+2i

(7i+3)-2
[1] 1+7i

As you can see, R-just like Python-performs simple imaginary number arithmetic quite well. One interesting quirk with R is that, no matter how I list the imaginary number in my expression (whether in the form of real part+imaginary part or imaginary part+real part), the output always displays in the form of real part+imaginary part.

Other irrational numbers in programming

Now that we briefly explored imaginary numbers in programming, let’s turn our attention to other irrational numbers!

Here’s an example in Python showing how to obtain irrational, non-imaginary numbers-e, the golden ratio, and pi.

from scipy import constants

from scipy import constants

print(constants.golden)
print(constants.pi)
print(constants.e)

1.618033988749895
3.141592653589793
1.602176634e-19

In case you ever wanted to use pi, the golden ratio (denoted by scipy.constants.golden) or e in any Python calculation, you now know the easy way to access these irrational numbers-just use the from scipy import constants line to access the scipy constants package and then use the line scipy.constants.[number you wish to use] to access the irrational number.

If that seems cool, here are some other constants the scipy.constants package contains (and there are far too many to mention for this entry):

from scipy import constants

print(constants.year)
print(constants.day)
print(constants.hour)

31536000.0
86400.0
3600.0

These are just a few of the time constants that the scipy.constants module contains-and in case your wondering why year gave you such a large number, it’s because these time constants are stored in seconds (yes, a year contains 31,536,000 seconds). The same logic applies as to why day and hour give you seemingly odd large numbers.

  • In case you’re wondering what the SciPy package is, first of all, it stands for scientific Python. The SciPy package is built upon the numpy package and contains various helpful mathematical and scientific functions and modules (like the constants module we just discussed).
  • Let me know if you all would like to see a SciPy series of lessons!

Now that we know how to obtain these irrational, non-imaginary numbers in Python, let’s see them used in computations!

print(constants.golden*3)
print(constants.pi**2)
print(constants.e/100)

4.854101966249685
9.869604401089358
1.6021766339999998e-21

Not gonna lie, Python’s computations of these mathematical constants work like a charm!

Now to explore these irrational, non-imaginary numbers in another programming language! I know, let’s try Java!

import java.lang.Math;

public class Numbers {


    public static void main(String[] args) {
        long product = (long) (Math.E*6);
        long sum = (long) (Math.PI+12);
        
        System.out.println(product);
        System.out.println(Math.PI);
        System.out.println(Math.E);
        System.out.println(sum);
        
    }
    
}

16
3.141592653589793
2.718281828459045
15

As you can see, Java can be a bit trickier than R or Python when it comes to working with irrational numbers (after all, there are more imports necessary-plus the need to cast any expression with these irrational numbers into type long)!

Another thing you’ll notice with Java is that in it’s Math class, PI and E are the only irrational number constants built-in to the class. The golden ratio isn’t built in, but you can easily define it. Here’s how you’d do so in Java:

long golden = (long) (1+Math.sqrt(5)/2)

And here’s what that number would look like in the output:

2

For some odd reason, if I use a long for my golden ratio in Java, I get a 2 in return (even though the approximation of the golden ratio equals 1.61).

Thanks for reading these past 12.25i posts. Here’s to the next 12.25i!

Michael

R Lesson 31: Logarithmic Graphs

Hello everybody,

Michael here, and today’s lesson will be a sort-of contiuation of my previous post on R logarithms (R Lesson 30: Logarithms). However, in this post, I’ll cover how to create logarithmic graphs in R.

Let’s begin!

Two types of log plots

There are two main types of logarithmic plots in R-logarithmic scale plots and log-log plots.

What do these log plots do, exactly? Well, in the case of the logarithmic scale plot, only one of the plot’s axes uses a logarithmic scale while the other maintains a linear scale while with the log-log plot both axes use logarithmic scales.

When would you use each type of plot? In the case of logarithmic scale plots, you’d use them for analyses such as exponential growth/decay or percentage changes over a period of time. As for the log-log plots, they’re better suited for analyses such as comparative analyses (which involve comparing datasets with different scales or units) and data where both the x-axis and y-axis have a wide range of values.

And now for the logarithmic scale plots!

Just as the header says, let’s create a logarithmic scale plot in R!

Before we begin, let’s be sure we have the necessary data that we’ll use for this analysis-

This dataset is simpler than most I’ve worked with on this blog, as it only contains 11 rows and two columns. Here’s what each column means:

  • Date-the date that the bitcoin mining reward will be halved
  • Reward-the amount of bitcoin you will recieve after a successful mining as of the halving date.

For those unfamiliar with basic Bitcoin mining, the gist of the process is that when you mine Bitcoin you get a reward. However, after every 3-4 years, the reward for mining bitcoin is halved. For instance, on the first ever day that you could mine bitcoin (January 1, 2009), you would be able to recieve 50 bitcoin (BTC) for a successful haul. In 2012, the reward was halved to 25 BTC for a successful haul. The next halving is scheduled to occur in Spring 2024, where the reward will be halved to 3.125 BTC. Guess bitcoin mining isn’t as profitable as it was nearly 15 years ago?

There will likely be no more Bitcoin left to mine by the year 2140, so between now and then, the Bitcoin mining reward will get progressively smaller (a perfect example of exponential decay). I did mention that the Bitcoin mining reward will be less than 1 BTC by the year 2032.

  • I mean, don’t take my word for it, but maybe the supply of mineable bitcoin won’t run out by 2140 and the reward instead would get progressively smaller until it’s well over 1/1,000,000,000th of 1 BTC. Just my theory though :-).

Now, enough about the Bitcoin mining-let’s see some logarithmic scale plots! Take a look at the code below.

First, we’ll read in our dataset:

bitcoin <- read.csv("C:/Users/mof39/OneDrive/Documents/bitcoin halving.csv", encoding="utf-8")

Next, we’ll create our logarithmic scale plot! Since there is only one axis that will use a log-scale (the y-axis in this case), let’s remember to specify that

plot(bitcoin$Date, bitcoin$Reward, log = "y", pch = 16, col = "blue", xlab = "Year", ylab = "BTC Reward")

Error in plot.window(...) : need finite 'xlim' values
In addition: Warning messages:
1: In xy.coords(x, y, xlabel, ylabel, log) : NAs introduced by coercion
2: In min(x) : no non-missing arguments to min; returning Inf
3: In max(x) : no non-missing arguments to max; returning -Inf

If we tried to use the plot() function along with all the parameters specified here, we’d get this error-Error in plot.window(...) : need finite 'xlim' values. Why does this occur?

The simple reason we got the error is because we used a column with non-numeric values for the x-axis. How do we fix this? Let’s create a numeric vector of the years in the Reward column (with the exception of 2009, all of the years in the Reward column are leap years until 2080) and use that vector as the x-axis:

years <- c(2009, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2044, 2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076, 2080)

plot(years, bitcoin$Reward, log = "y", pch = 16, col = "red", xlab = "Year", ylab = "BTC Reward", main = "BTC Rewards 2009-2080")

Voila! As you can see here, we have a nice log-scale plot showing the exponential decay in bitcoin mining rewards from 2009 to 2080.

How did create this nice-looking plot? Well, we set the value of the log parameter of the plot() function equal to y, as we are only creating a log-scale plot. If we wanted to create a log-log plot, we would se the value of the log parameter to xy, which would indicate that we would use a logarithmic scale for both the x and y axes.

As for the rest of the values of the parameters in the plot() function, keep them the same as you would for a normal, non-logarithmic R scatter plot (except of course adapting your x- and y-axes and title to fit the scatterplot).

Now, one thing I did want to address on this graph is the scale on the y-axis, which might look strange to you if you’re not familiar with log-scale plots. See, the plot() function’s log parameter in R uses base-10 logs by default, and in turn, the y-axis will use powers of 10 in the scale (the scientific notation makes the display a little neater). For instance, 1e+01 represents 10, 1e+00 represents 0, and so on. Don’t worry, all the data points in the dataset were plotted correctly here.

And now, let’s create a log-log plot

Now that we’ve created a log-scale plot, it’s time to explore how to create a log-log plot in R!

loglog <- data.frame(x=c(2, 4, 8, 16, 32, 64), y=c(3, 9, 27, 81, 243, 729))

plot(loglog$x, loglog$y, log = "xy", pch = 16, col = "red", xlab = "Power of 2", ylab = "Power of 3", main = "Sample Log-Log Plot")

In this example, I created the dataframe loglog and filled both axes with powers of 2 and 3 to provide a simple way to demonstrate the creation of log-log plots in R.

As for the plot() function, I made sure to set the value of the log parameter to xy since we’re creating a log-log plot and thus need both axes to use a logarithmic scale. Aside from that, remember to change the plot’s axes, labels, and titles as appropriate for your plot.

Now, you might’ve noticed somthing about this graph. In R, both log-log and log-scale plots utilize base-10 logs for creating the plot. However, you likely noticed that the scale for the log-scale plot displays its values using scientific notation and powers of 10. The scale (or should I say scales) for the log-log plot doesn’t use scientifc notation and powers of 10 to display its values. Rather, the log-log plot uses conventional scaling to display its values-in other words, the scale for the log-log plot bases its values of the range of values in both axes rather than a powers-of-10 system. Honestly, I think this makes sense since the plot is already using a logarithmic scale for both axes, which would make the whole powers-of-10 thing in the scale values redundant.

  • Of course, if you want to change the scale displays in either the log-log or log-scale plots, all you would need to do is utilize the axis() function in R after creating the plot. Doing so would allow you to customize your plot’s axis displays to your liking.

Thnaks for reading!

Michael

R Lesson 30: Logarithms

Hello everybody,

Michael here, and I hope you had a little fun with my two-part fifth anniversary post (particularly the puzzle). For the next several posts, we’ll be exploring more mathematics with R. Today we’ll discuss the wonderful world of R logarithms (and logarithms in general).

But first, a word on logarithms

As you may have noticed from my previous R mathematics posts, I always explain the concept in both the context of R and the context of manual calculation so you can have a basic understanding of the concept itself before exploring it in R. I plan to do the same thing here today.

So, what are logarithms exactly? They’re simply another type of mathematical operation. Take a look at this illustration below:

In this example, I used the simple example of 4^3=64. I also noted that the logarithmic form of this expression is log4(64)=3.

What does this mean? The base of the exponentional expression (4^3=64)-4-serves at the base of the logarithm. The number in parentheses-64-serves as the logarithm’s argument, which is the value used to find the logarithm’s exponent (or result), which is 3 in this case.

How do you read a logarithmic expression? In this example, you’d read the expression as log base-4 of 64 equals 3.

That’s how logarithms work. Now let’s explore how to manage them in R.

Logarithms, R style

So, how would you work with logarithms in R? Let’s take a look at the code below:

> log(32, base=2)
[1] 5

Calculating logs in R is as simple as using R’s built-in log() function and adding in two parameters-the argument and the base. With these two parameters and the log() function, R will return the logarithm’s exponent, which in this case is 5 since log base-2 of 32 equals 5.

However, what I discussed above was a very, very basic example of logarithms in R. Let’s look a more specific scenario:

> log10(1000)
[1] 3

In this example, I’m showing what is known as a base-10 logarithm, which is a logarithm with a base of, well, 10. In this case, I’m showing you what log base-10 of 1000 equals-in this case, the exponent/result is 3. Notice how this expression uses the log10() function rather than the log() function.

  • NOTE: To calculate this base-10 logarithm you can also use the code log(1000, base=10), but I just wanted to point out the log10() function as another way to solve a logarithm.

Another specific logarithmic scenario is binary, or base-2 logarithms. Let’s take a look at those:

> log2(8)
[1] 3

In this example, I’m showing a base-2 logarithm, which is a logarithm with a base of, well, 2. In this case, the base-2 log of 8 is 3, since 2^3=8.

  • NOTE: Just like the case with the base-10 logarithm, you can also use the code log(8, base=2) to calculate this base-2 algorithm.

Now let’s explore two rather unusual logarithmic scenarios-logarithms of imaginary numbers and logarithms with base e (e as in the mathematical constant). What does it all mean? Let me explain!

> log(11)
[1] 2.397895

> log(3+2i, base=4)
[1] 0.9251099+0.4241542i

The first example above shows the expression log base-e of 11 along with the result, 2.397895. The second example shows the expression log base-4 of 3+2i along with the result, 0.9251099+0.4241542i.

If you’re not familiar with the concept of imaginary numbers and the mathematical constant e, fear not, for I will explain both concepts right here!

A rational section about some irrational numbers

If you have even the most basic knowledge of numbers, you’ll be quite familiar with the numbers 0-9. After all, when you first learned basic counting, you very likely learned how to count to 10.

However, if you study more advanced math (like alegbra and calculus), you’ll notice there are a few numbers that aren’t so neat (and no, I’m not talking about decimals, fractions or negative numbers). These are known as irrational numbers, which are non-terminating, non-repeating numbers that can’t be expressed as simple fractions or decimals.

We just explored calculating logarithms with two types of irrational numbers-e and imaginary numbers. How do these types of numbers work?

In the case of e, e is a mathematical constant known as Euler’s number-named after Swiss mathematician Leonhard Euler. e is an irrational number that stretches on for infinity, but its approximate value is 2.71828. The number e is used in various exponential growth/decay functions, such as a city’s population growth/decline over a certain time period or a substance’s radioactive decay.

A log with a base e is also known as a natural logarithm (like the log(11) example I used earlier). In R, natural logarithms are denoted as log(number) with no base parameter specified. Here’s what natural logarithms look like:

In the case of the imaginary numbers, such as 3+2i, they provide an easy way to handle complex mathematical situations, such as the square roots of negative numbers (which will always be imaginary numbers). Imaginary numbers always consist of a real part multiplied by the imaginary unit i (such as 2i). The number 3+2i is known as a complex imaginary number since it has a real part (3) and an imaginary part (2i). One well known application of imaginary numbers is fractal geometry, which you can find quite a bit of in nature (like in conch shells).

Three more logarithmic scenarios that I think you should know

Before I go, I want to discuss three more logarithmic scenarios with you all, first with natural logs and then with logs that have a numerical base. Take a look at the code below:

> log(1)
[1] 0
> log(0)
[1] -Inf
> log(-12)
[1] NaN
Warning message:
In log(-12) : NaNs produced

In this example, I’m showing you three different natural logarithm scenarios that you should know-logs with arguments of 1, 0, and a negative number. Notice that a log with an argument of 1 yields 0, an log with an argument of 0 yields negative infinity and a log with a negative number yields an NaN (indicating that logs with negative arguments aren’t valid). Why might this be the case?

  • In the case of log(1), you get 0 because raising a number to the power of 0 always yields 1.
  • In the case of log(0), you get -Inf (negative infinity) because there is no possible power you can raise e to obtain 0.
  • In the case of log(-12), you get NaN (not a number) because logs only work with positive number arguments.

Now here are three scenarios with the same arguments, but using a base of 2 instead of e:

> log(1, base=2)
[1] 0
> log(0, base=2)
[1] -Inf
> log(-12, base=2)
[1] NaN
Warning message:
NaNs produced 

Notice how you get the same results here as you did for the natural logarithms.

Thanks for reading,

Michael

The Glorious Five-Year Plan Part Two

Hello everybody!

Michael here, and as I previously promised, here’s part 2 of my five-year blog anniversary post (after all, what fun it is to split the 5-year anniversary letter into two posts).

My Answer To Michael’s Five-Year Coding Challenge

Also, as I previously promised on the last post, I will discuss my solution for Michael’s Five-Year Coding Challenge. I know I said you can use any programming language you like for this challenge (as long as your code follows certain criteria outlined in my previous post https://michaelsprogrammingbytes.com/2023/06/13/the-glorious-five-year-plan-part-one/), but here’s my approach using Python!

secretCode = {'Z': 'A', 'Y': 'B', 'X': 'C', 'W': 'D', 'V': 'E', 'U': 'F', 'T': 'G', 'S': 'H', 'R': 'I', 'Q': 'J', 'P': 'K', 
              'O': 'L', 'N': 'M', 'M': 'N', 'L': 'O', 'K': 'P', 'J': 'Q', 'I': 'R', 'H': 'S', 'G': 'T', 'F': 'U', 'E': 'V', 
              'D': 'W', 'C': 'X', 'B': 'Y', 'A': 'Z'}
encodedString = """Gszmp blf gl zoo nb ivzwvih uli urev dlmwviufo bvzih. R dlfowm'g szev pvkg gsrh yolt ifmmrmt uli zh olmt zh R wrw drgslfg blfi dlmwviufo hfkklig.  slkv blf ovzimvw hlnvgsrmt zolmt gsv dzb gsvhv ozhg urev bvzih-zmw, svb, nzbyv blf tzrmvw z olev lu kiltiznnrmt (zmw kviszkh mvd qly hprooh) rm gsv kilxvhh. Gszg dlfow xvigzrmob nzpv nv z evib szkkb dirgvi. Sviv'h gl nzmb, nzmb nliv bvzih lu xlwrmt gltvgsvi! Nrxszvo"""
punctuation = ['\'', '.', '!', '(', ')', '-']
decodedString = ''

for e in encodedString:
    for k, v in secretCode.items():
        if e == k:
            decodedString += v
        elif e.lower() == k.lower():
            decodedString += v.lower()
                   
    if e == ' ':
        decodedString += ' '
    elif e in punctuation:
        decodedString += e

print(decodedString)

As you can see, I solved this coding challenge with 20 simple lines of Python code (not including the output). How did I accomplish this? Here are 10 things I kept in mind when solving my own puzzle:

  1. I used the secretCode dictionary to set my reverse substitution cipher (HINT: even though there are both uppercase and lowercase letters, you won’t need two separate dictionaries for the cipher-I’ll explain why).
  2. I created a list containing all of the punctuation in this message-you’ll see why it’s important later. The list is aptly named punctuation.
  3. I used the decodedString variable to store the decoded message-I personally thought it was more convinient than figuring out how to replace all the characters in encodedString one by one.
  4. I created a nested for-loop to iterate both through the characters in encodedString and the items (that is, both keys and values) in the secretCode dictionary.
  5. In the nested loop where I iterate through the items of the secretCode dictionary (for k, v in secretCode.items()), I check if each character in the encodedString string equals the current corresponding key in the secretCode dictionary and if so, I append the corresponding value to the decodedString string.
  6. Remember how I said that even though my secret message contains both upper and lower-case letters you won’t need to create two dictionaries? Well, the statement elif e.lower() == k.lower() handles this scenario in just two lines of code by checking to see if the lowercase version of the character in encodedString equals the lowercase version of the corresponding key and if so, I append the lowercase corresponding value to the decodedString string.
  7. How would I handle spaces and punctuation in the encodedString? Well, in the case of spaces, if a character in encodedString is a space, I append it to the decodedString just as I did with the letters.
  8. The same logic applies for any punctuation in encodedString, but this time, I check to see if a character equals any element in the punctuation list that I mentioned in item #2.
  9. One thing I kept in mind when checking for spaces and punctuation in encodedString-I kept the if-elif statement pair OUTSIDE of the dictionary loop because if I placed those statements inside that loop, I would’ve ended up with a ton of spaces and punctuation. However, placing this code outside of the dictionary loop ensures that I end up with the correct amount of spaces and punctuation.
  10. Last but not least, I printed the decodedString message. Without further ado, here it is:
Thank you to all my readers for five wonderful years. I wouldn't have kept this blog running for as long as I did without your wonderful support.  hope you learned something along the way these last five years-and hey maybe you gained a love of programming (and perhaps new job skills) in the process. That would certainly make me a very happy writer. Here's to many many more years of coding together! Michael

Trust me, dear readers, I mean every word of this. I wouldn’t kept this blog running as long as I did without you!

One More Note

I know I’ve emphasized this over these last two posts, but thank you, thank you, thank you loyal readers for reading my blog for the last five years. Hopefully you’ll keep coming back for more, because boy do I have several more years of great coding content I can provide (and you bet it’ll still be good when I hit year 10 and beyond)!

However, there is someone I wanted to acknowledge on this five-year anniversary. Rather, a furry friend I’d like to mention.

His name is Simba (or Orange Boy) and he is my fluffy orange cat who certainly helped me during the blog’s early days (and yes, he was there when I wrote the welcome post). Here he is on the night of September 17, 2018, likely looking over me as I wrote another post (or searching for a post college job, as I was in the thick of job hunting in fall 2018). He is certainly a good fluffy boy!

Also, here’s Simba with Marbles, his sister (the brown cat aka Pretty Girl), who accompanied me during writing every now and then:

The kitties on Christmas morning 2017-pre blog days.

To many more years of developing together,

Michael