Unit 2.2 Data Compression, Images
Lab will perform alterations on images, manipulate RGB values, and reduce the number of pixels. College Board requires you to learn about Lossy and Lossless compression.
- Enumerate "Data" Big Idea from College Board
- Image Files and Size
- Python Libraries and Concepts used for Jupyter and Files/Directories
- Reading and Encoding Images (2 implementations follow)
- Data Structures, Imperative Programming Style, and working with Images
- Data Structures and OOP
- Additionally, review all the imports in these three demos. Create a definition of their purpose, specifically these ...
- Hacks
Enumerate "Data" Big Idea from College Board
Some of the big ideas and vocab that you observe, talk about it with a partner ...
- "Data compression is the reduction of the number of bits needed to represent data"
- "Data compression is used to save transmission time and storage space."
- "lossy data can reduce data but the original data is not recovered"
- "lossless data lets you restore and recover"
The Image Lab Project contains a plethora of College Board Unit 2 data concepts. Working with Images provides many opportunities for compression and analyzing size.
Image Files and Size
Here are some Images Files. Download these files, load them into
images
directory under _notebooks in your Blog. - Clouds Impression
Describe some of the meta data and considerations when managing Image files. Describe how these relate to Data Compression ...
- File Type, PNG and JPG are two types used in this lab
- Size, height and width, number of pixels
- Visual perception, lossy compression
Python Libraries and Concepts used for Jupyter and Files/Directories
Introduction to displaying images in Jupyter notebook
IPython
Support visualization of data in Jupyter notebooks. Visualization is specific to View, for the web visualization needs to be converted to HTML.
pathlib
File paths are different on Windows versus Mac and Linux. This can cause problems in a project as you work and deploy on different Operating Systems (OS's), pathlib is a solution to this problem.
- What are commands you use in terminal to access files? The "ls" command is used to display a list of all files in the current directory, while the "cd" command is used to navigate into directories. To read the contents of a file, the "cat" command can be used, and to edit a file, "sudo nano" can be used.
-
What are the command you use in Windows terminal to access files? The "dir" command is used to view the list of files and folders contained within a directory. To open a file, you can simply type the name of the file and press the ENTER key.
-
What are some of the major differences? The biggest difference lies in the use of different commands to open and access files, such as "ls" vs "dir". These commands are specific to different operating systems - "ls" is used in Unix-based systems like Linux and macOS, while "dir" is used in Windows. Additionally, the commands for opening and accessing files also differ between operating systems.
Provide what you observed, struggled with, or leaned while playing with this code.
-
Why is path a big deal when working with images? Images are generally more accessible and easier to use compared to other file types. They can be easily viewed, shared, and manipulated using a variety of software tools.
-
How does the meta data source and label relate to Unit 5 topics? Metadata can have both beneficial and harmful effects, depending on how it is used. On the one hand, metadata can provide valuable information about a file, such as the date it was created, who created it, and its file size. This can be useful for organizing and searching for files, as well as for verifying the authenticity of a file. However, metadata can also pose a privacy risk, as it can contain sensitive information that could be used to track or identify individuals. For example, photos taken with a smartphone often contain location data in their metadata, which could reveal where a person has been. Therefore, it is important to be aware of the potential risks and benefits of metadata, and to use it responsibly.
- Look up IPython, describe why this is interesting in Jupyter Notebooks for both Pandas and Images? IPython's inclusion of interactive shells allows for greater interaction between the notebook and the user, making it easier to work with tools such as Pandas and images. This enhanced interactivity provides a more user-friendly experience and enables users to quickly explore and manipulate data. Additionally, IPython provides a range of features that are not available in traditional Python shells, such as tab completion and command history. All of these benefits combine to make IPython a more powerful and efficient tool for data analysis and exploration.
from IPython.display import Image, display
from pathlib import Path # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
# prepares a series of images
def image_data(path=Path("images/"), images=None): # path of static images is defaulted
if images is None: # default image
images = [
{'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
{'source': "Peter Carolin", 'label': "Lassen Volcano", 'file': "lassen-volcano.jpg"},
{'source': "Internet", 'label': "Smiley Face", 'file': "SmileyFace.png"}
]
for image in images:
# File to open
image['filename'] = path / image['file'] # file with path
return images
def image_display(images):
for image in images:
display(Image(filename=image['filename']))
# Run this as standalone tester to see sample data printed in Jupyter terminal
if __name__ == "__main__":
# print parameter supplied image
green_square = image_data(images=[{'source': "Internet", 'label': "Green Square", 'file': "green-square-16.png"}])
image_display(green_square)
# display default images from image_data()
default_images = image_data()
image_display(default_images)
Reading and Encoding Images (2 implementations follow)
PIL (Python Image Library)
Pillow or PIL provides the ability to work with images in Python. Geeks for Geeks shows some ideas on working with images.
base64
Image formats (JPG, PNG) are often called *Binary File formats, it is difficult to pass these over HTTP. Thus, base64 converts binary encoded data (8-bit, ASCII/Unicode) into a text encoded scheme (24 bits, 6-bit Base64 digits). Thus base64 is used to transport and embed binary images into textual assets such as HTML and CSS.- How is Base64 similar or different to Binary and Hexadecimal? Binary uses the numbers 0 and 1, Hexadecimal uses the numbers 0-9 and the lowercase letters a-f. Meanwhile, base64 uses the numbers 0-9, the lowercase letters a-z, the uppercase letters A-Z, and the characters + and /
- Translate first 3 letters of your name to Base64. Soh = U29o
numpy
Numpy is described as "The fundamental package for scientific computing with Python". In the Image Lab, a Numpy array is created from the image data in order to simplify access and change to the RGB values of the pixels, converting pixels to grey scale.
io, BytesIO
Input and Output (I/O) is a fundamental of all Computer Programming. Input/output (I/O) buffering is a technique used to optimize I/O operations. In large quantities of data, how many frames of input the server currently has queued is the buffer. In this example, there is a very large picture that lags.
- Where have you been a consumer of buffering?
One time, during a very stormy weather, I was watching YouTube and the video I was watching started buffering. It paused and there was a loading animation. This was due to the large amount of data that was unable to be displayed due to the slow download speeds.
-
From your consumer experience, what effects have you experienced from buffering?
A lot of times, the video I'm watching doesn't load, and sometimes the game I'm playing freezes me in place until I regain connection. -
How do these effects apply to images? When there are large images or very graphic images, buffering tends to occur more.
Data Structures, Imperative Programming Style, and working with Images
Introduction to creating meta data and manipulating images. Look at each procedure and explain the the purpose and results of this program. Add any insights or challenges as you explored this program.
- Does this code seem like a series of steps are being performed? The code slightly seems like a series of steps. There are multiple functions being performed in order and there are many operations done within each function.
-
Describe Grey Scale algorithm in English or Pseudo code? The algorithm first receives a scaled size of the image, and data is retrieved from the image. The amount of pixels retreived is then averaged and appended to the data. The image data is then returned.
-
Describe scale image? What is before and after on pixels in three images? The scale image scales the height and width of the image to a certain amount. Some images are scaled to a bigger size while others are shrunk.
-
Is scale image a type of compression? If so, line it up with College Board terms described? Scale is a type of compression - lossless compression. Original data is not being permanently removed, and instead, the file size is reduced by removing metadata - therefore, lossless.
from IPython.display import HTML, display
from pathlib import Path # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
from PIL import Image as pilImage # as pilImage is used to avoid conflicts
from io import BytesIO
import base64
import numpy as np
# prepares a series of images
def image_data(path=Path("images/"), images=None): # path of static images is defaulted
if images is None: # default image
images = [
{'source': "Internet", 'label': "Green Square", 'file': "green-square-16.png"},
{'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
{'source': "Peter Carolin", 'label': "Lassen Volcano", 'file': "lassen-volcano.jpg"}
]
for image in images:
# File to open
image['filename'] = path / image['file'] # file with path
return images
# Large image scaled to baseWidth of 320
def scale_image(img):
baseWidth = 320
scalePercent = (baseWidth/float(img.size[0]))
scaleHeight = int((float(img.size[1])*float(scalePercent)))
scale = (baseWidth, scaleHeight)
return img.resize(scale)
# PIL image converted to base64
def image_to_base64(img, format):
with BytesIO() as buffer:
img.save(buffer, format)
return base64.b64encode(buffer.getvalue()).decode()
# Set Properties of Image, Scale, and convert to Base64
def image_management(image): # path of static images is defaulted
# Image open return PIL image object
img = pilImage.open(image['filename'])
# Python Image Library operations
image['format'] = img.format
image['mode'] = img.mode
image['size'] = img.size
# Scale the Image
img = scale_image(img)
image['pil'] = img
image['scaled_size'] = img.size
# Scaled HTML
image['html'] = '<img src="data:image/png;base64,%s">' % image_to_base64(image['pil'], image['format'])
# Create Grey Scale Base64 representation of Image
def image_management_add_html_grey(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['gray_data'] = [] # key/value for data converted to gray scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create gray scale of image, ref: https://www.geeksforgeeks.org/convert-a-numpy-array-to-an-image/
average = (pixel[0] + pixel[1] + pixel[2]) // 3 # average pixel values and use // for integer division
if len(pixel) > 3:
image['gray_data'].append((average, average, average, pixel[3])) # PNG format
else:
image['gray_data'].append((average, average, average))
# end for loop for pixels
img.putdata(image['gray_data'])
image['html_grey'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Jupyter Notebook Visualization of Images
if __name__ == "__main__":
# Use numpy to concatenate two arrays
images = image_data()
# Display meta data, scaled view, and grey scale for each image
for image in images:
image_management(image)
print("---- meta data -----")
print(image['label'])
print(image['source'])
print(image['format'])
print(image['mode'])
print("Original size: ", image['size'])
print("Scaled size: ", image['scaled_size'])
print("-- original image --")
display(HTML(image['html']))
print("--- grey image ----")
image_management_add_html_grey(image)
display(HTML(image['html_grey']))
print()
Data Structures and OOP
Most data structures classes require Object Oriented Programming (OOP). Since this class is lined up with a College Course, OOP will be talked about often. Functionality in remainder of this Blog is the same as the prior implementation. Highlight some of the key difference you see between imperative and oop styles.
- Read imperative and object-oriented programming on Wikipedia
- Consider how data is organized in two examples, in relations to procedures
- Look at Parameters in Imperative and Self in OOP
Additionally, review all the imports in these three demos. Create a definition of their purpose, specifically these ...
- PIL
- numpy
- base64
from IPython.display import HTML, display
from pathlib import Path # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
from PIL import Image as pilImage # as pilImage is used to avoid conflicts
from io import BytesIO
import base64
import numpy as np
# prepares a series of images
def image_data(path=Path("images/"), images=None): # path of static images is defaulted
if images is None: # default image
images = [
{'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
{'source': "VectorStock", 'label': "Smiley Face", 'file': "SmileyFace.png"}
]
for image in images:
# File to open
image['filename'] = path / image['file'] # file with path
return images
# Large image scaled to baseWidth of 320
def scale_image(img):
baseWidth = 320
scalePercent = (baseWidth/float(img.size[0]))
scaleHeight = int((float(img.size[1])*float(scalePercent)))
scale = (baseWidth, scaleHeight)
return img.resize(scale)
# PIL image converted to base64
def image_to_base64(img, format):
with BytesIO() as buffer:
img.save(buffer, format)
return base64.b64encode(buffer.getvalue()).decode()
# Set Properties of Image, Scale, and convert to Base64
def image_management(image): # path of static images is defaulted
# Image open return PIL image object
img = pilImage.open(image['filename'])
# Python Image Library operations
image['format'] = img.format
image['mode'] = img.mode
image['size'] = img.size
# Scale the Image
img = scale_image(img)
image['pil'] = img
image['scaled_size'] = img.size
# Scaled HTML
image['html'] = '<img src="data:image/png;base64,%s">' % image_to_base64(image['pil'], image['format'])
# Create redscale Base64 image version
def image_management_add_html_red(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['red_data'] = [] # key/value for data converted to red scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create red scale of image
redval = pixel[0] # only preserving the red val
if len(pixel) > 3:
image['red_data'].append((redval, 0, 0, pixel[3])) # PNG format
else:
image['red_data'].append((redval, 0, 0))
# end for loop for pixels
img.putdata(image['red_data'])
image['html_red'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Create greenscale Base64 image version
def image_management_add_html_green(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['green_data'] = [] # key/value for data converted to red scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create green scale of image
greenval = pixel[1] # only preserving the green val
if len(pixel) > 3:
image['green_data'].append((0, greenval, 0, pixel[3])) # PNG format
else:
image['green_data'].append((0, greenval, 0))
# end for loop for pixels
img.putdata(image['green_data'])
image['html_green'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Create bluescale Base64 image version
def image_management_add_html_blue(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['blue_data'] = [] # key/value for data converted to red scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create blue scale of image
blueval = pixel[2] # only preserving the blue val
if len(pixel) > 3:
image['blue_data'].append((0, 0, blueval, pixel[3])) # PNG format
else:
image['blue_data'].append((0, 0, blueval))
# end for loop for pixels
img.putdata(image['blue_data'])
image['html_blue'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Jupyter Notebook Visualization of Images
if __name__ == "__main__":
# Use numpy to concatenate two arrays
images = image_data()
# Display meta data, scaled view, and grey scale for each image
for image in images:
redcopy = image
greencopy = image
bluecopy = image
image_management(image)
print("-- original image --")
display(HTML(image['html']))
print("--- red image ----")
image_management(redcopy)
image_management_add_html_red(redcopy)
display(HTML(redcopy['html_red']))
print("--- green image ----")
image_management(greencopy)
image_management_add_html_green(greencopy)
display(HTML(greencopy['html_green']))
print("--- blue image ----")
image_management(bluecopy)
image_management_add_html_blue(bluecopy)
display(HTML(bluecopy['html_blue']))
print()
Hacks
Early Seed award
- Add this Blog to you own Blogging site.
- In the Blog add a Happy Face image.
- Have Happy Face Image open when Tech Talk starts, running on localhost. Don't tell anyone. Show to Teacher. > AP Prep
- In the Blog add notes and observations on each code cell that request an answer.
- In blog add College Board practice problems for 2.3 - Choose 2 images, one that will more likely result in lossy data compression and one that is more likely to result in lossless data compression. Explain.
This image should be lossy
This image should be lossless
If an image is very simple, it would more likely be lossy compressed, since nothing of importance will be lost.
When it comes to non-images, videos are often more lossy compressed because the image data takes up a lot of space and individual frames being compressed may not come off as too obvious. Usually files in .zip or .7z format are lossless compressed, while things such as MPEG and MP3 files are more often lossy compressed.
Project Addition
- If your project has images in it, try to implement an image change that has a purpose. (Ex. An item that has been sold out could become gray scale)
Pick a programming paradigm and solve some of the following ...
- Numpy, manipulating pixels. As opposed to Grey Scale treatment, pick a couple of other types like red scale, green scale, or blue scale. We want you to be manipulating pixels in the image.
- Binary and Hexadecimal reports. Convert and produce pixels in binary and Hexadecimal and display.
- Compression and Sizing of images. Look for insights into compression Lossy and Lossless. Look at PIL library and see if there are other things that can be done.
- There are many effects you can do as well with PIL. Blur the image or write Meta Data on screen, aka Title, Author and Image size.
from IPython.display import HTML, display
from pathlib import Path # https://medium.com/@ageitgey/python-3-quick-tip-the-easy-way-to-deal-with-file-paths-on-windows-mac-and-linux-11a072b58d5f
from PIL import Image as pilImage # as pilImage is used to avoid conflicts
from io import BytesIO
import base64
import numpy as np
# prepares a series of images
def image_data(path=Path("images/"), images=None): # path of static images is defaulted
if images is None: # default image
images = [
{'source': "Peter Carolin", 'label': "Clouds Impression", 'file': "clouds-impression.png"},
{'source': "VectorStock", 'label': "Smiley Face", 'file': "SmileyFace.png"}
]
for image in images:
# File to open
image['filename'] = path / image['file'] # file with path
return images
# Large image scaled to baseWidth of 320
def scale_image(img):
baseWidth = 320
scalePercent = (baseWidth/float(img.size[0]))
scaleHeight = int((float(img.size[1])*float(scalePercent)))
scale = (baseWidth, scaleHeight)
return img.resize(scale)
# PIL image converted to base64
def image_to_base64(img, format):
with BytesIO() as buffer:
img.save(buffer, format)
return base64.b64encode(buffer.getvalue()).decode()
# Set Properties of Image, Scale, and convert to Base64
def image_management(image): # path of static images is defaulted
# Image open return PIL image object
img = pilImage.open(image['filename'])
# Python Image Library operations
image['format'] = img.format
image['mode'] = img.mode
image['size'] = img.size
# Scale the Image
img = scale_image(img)
image['pil'] = img
image['scaled_size'] = img.size
# Scaled HTML
image['html'] = '<img src="data:image/png;base64,%s">' % image_to_base64(image['pil'], image['format'])
# Create redscale Base64 image version
def image_management_add_html_red(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['red_data'] = [] # key/value for data converted to red scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create red scale of image
redval = pixel[0] # only preserving the red val
if len(pixel) > 3:
image['red_data'].append((redval, 0, 0, pixel[3])) # PNG format
else:
image['red_data'].append((redval, 0, 0))
# end for loop for pixels
img.putdata(image['red_data'])
image['html_red'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Create greenscale Base64 image version
def image_management_add_html_green(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['green_data'] = [] # key/value for data converted to red scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create green scale of image
greenval = pixel[1] # only preserving the green val
if len(pixel) > 3:
image['green_data'].append((0, greenval, 0, pixel[3])) # PNG format
else:
image['green_data'].append((0, greenval, 0))
# end for loop for pixels
img.putdata(image['green_data'])
image['html_green'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Create bluescale Base64 image version
def image_management_add_html_blue(image):
# Image open return PIL image object
img = image['pil']
format = image['format']
img_data = img.getdata() # Reference https://www.geeksforgeeks.org/python-pil-image-getdata/
image['data'] = np.array(img_data) # PIL image to numpy array
image['blue_data'] = [] # key/value for data converted to red scale
# 'data' is a list of RGB data, the list is traversed and hex and binary lists are calculated and formatted
for pixel in image['data']:
# create blue scale of image
blueval = pixel[2] # only preserving the blue val
if len(pixel) > 3:
image['blue_data'].append((0, 0, blueval, pixel[3])) # PNG format
else:
image['blue_data'].append((0, 0, blueval))
# end for loop for pixels
img.putdata(image['blue_data'])
image['html_blue'] = '<img src="data:image/png;base64,%s">' % image_to_base64(img, format)
# Jupyter Notebook Visualization of Images
if __name__ == "__main__":
# Use numpy to concatenate two arrays
images = image_data()
# Display meta data, scaled view, and grey scale for each image
for image in images:
redcopy = image
greencopy = image
bluecopy = image
image_management(image)
print("-- original image --")
display(HTML(image['html']))
print("--- red image ----")
image_management(redcopy)
image_management_add_html_red(redcopy)
display(HTML(redcopy['html_red']))
print("--- green image ----")
image_management(greencopy)
image_management_add_html_green(greencopy)
display(HTML(greencopy['html_green']))
print("--- blue image ----")
image_management(bluecopy)
image_management_add_html_blue(bluecopy)
display(HTML(bluecopy['html_blue']))
print()
from PIL import Image, ImageDraw, ImageFont
# get an image
with Image.open("images/SmileyFace.png").convert("RGBA") as base:
# make a blank image for the text, initialized to transparent text color
txt = Image.new("RGBA", base.size, (255, 255, 255, 0))
# get a drawing context
d = ImageDraw.Draw(txt)
# draw text, full opacity
d.text((100, 150), "There is text on my smiley face!", fill=(255, 255, 255, 255))
out = Image.alpha_composite(base, txt)
display(out)