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
- Displaying images in Python Jupyter notebook
- 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.
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 File type defines the compression format used to represent the images. It dictates how the pixels of the image itself are compressed. Metadata of bit depth (how many bits are used to describe each pixel), dimensions/ size, storage, and more attributes of each image can be seen through properties. JPEG is "joint photgraphers experts group." PNG is "portable network graphics." Both are compression formats used for represnting digital images.
-
Size, height and width, number of pixels Pixels are the tiny subunits the makeup an image. Each pixel can have a certain depth or take up an amount of storage, which differs from image to image depending on resolution/definition. Pixels have colors which can be represented by 32 bits of RGB values.
-
Visual perception, lossy compression JPEGs are compressed using lossy compression, where highly detailed images with many colors are made into smaller files by permenantly deleting some information from the file. Every time an image is stored and edited, some data is lost. PNGs are compressed using lossless compression techniques, where no data is lost and quality remains the same no matter how many times you edit or save the image. This is why PNG file sizes are bigger comapared to JPEG. Fun fact: JPEGs don't support images with transparent backgorunds, but PNGs do.
Displaying images in Python Jupyter notebook
Python Libraries and Concepts used for Jupyter and Files/Directories
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? What are the command you use in Windows terminal to access files? To change to a specific file or folder path in the powershell, you would use cd ~generaldirectory/specificlocation. To open a file, you can also type the name of the program you want to open with and filename. Example:Notepad txtYou can use the dir command to list contents of a folder.
-
What are some of the major differences? In Windows, files are stored using a directories and folders organizational system, while in Linux there is a tree/ branches system. Using a Windows storage system means more options for compatibility, but Linux systems are more secure and efficient.
Provide what you observed, struggled with, or leaned while playing with this code.
-
Why is path a big deal when working with images? Paths provide the exact location of the file, which is very imporatant when accessing them. In this case, if the path is not accurate, then the image will not be properly accessed.
-
How does the meta data source and label relate to Unit 5 topics? Meta data source tells us the source and data of the file, like device used to take photo and who took it. This relates to computing inequity, because not everyone will have the same access to more expensive and better-quality devices. For example, someone from a lower socioeconomic class who can't afford more storage will likely use the jpeg format rather than png, but will lose data by doing so and therefore be at a disadvantage.
-
Look up IPython, describe why this is interesting in Jupyter Notebooks for both Pandas and Images? Pandas are libraries used when working with data sets. They help to clean, organize, sort, and manipulate data. Ipython is a python interpreter. It is useful in a Jupyter notebook as a kernel for code to be viewed and read in. You can use pandas to describe types of operation and image manipulation in IPython. The interactive nature of IPython makes it interesting to describe images in jupyter notebooks.
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"}
]
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 base 2, hexadecimal uses base 16, as compared to base 64. Base 64 encodes data in blocks of 3 bytes, or 24 bits, and it is mainly used for representing binary data in a textual format.
- Translate first 3 letters of your name to Base64. JIY =
- Ascii: J I Y = 74 73 89
- 8-bit Binary: 0100 1010 | 0100 1001 | 0101 1001
- Joint bits: 010010100100100101011001
- Groups of 6 bits: 010010 100100 100101 011001
- Convert to base64: S k l Z
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?
On streaming platforms like Youtube and Netflix, if there is a delay in internet connection. In my experience, the video is paused during this lag, and the audio still plays. Even if the internet goes away, sometimes the video still plays for a few seconds.
-
From your consumer experience, what effects have you experienced from buffering?
When I have seen delays in the start of the content, the frames of the screen keep on moving even after a connection issue occurs, which was nnoying. -
How do these effects apply to images? When loading images, as buffering is happening, an incomplete picture with few pixels displays, but as the image loads, the image becomes more clear and all the proper pixels show up.
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? Yes; each function has a series of steps. The program first builds an image library, scales it to 320 width, then converts each image to HTML, then to gray scale HTML.
-
Describe Grey Scale algorithm in English or Pseudo code? The grey scale algorithm averages out the rgb values of all the pixels, to convert to a gray scale number.
-
Describe scale image? What is before and after on pixels in three images? To scale the image, the width of each is changed, percentage wise, to 320 pixels. The height is also changed to maintain the aspect ratio and not distort the image. Lassen volcanoe:width and height before was 2792 and 2094, after it is 320 and 240Green square: width and height before was 16 and 16, after it is 320 and 320 Clouds impression: width and height before was 320 and 234, after it is the same since width is already 320
- Is scale image a type of compression? If so, line it up with College Board terms described? Yes; if the image width is greater than 320 pixels before it is scaled, it must be compressed and hence loses pixels. This is a type of lossy compression, since overall the image loses data.
Challenges: There is no optimization when scaling the image up. In the scaling function, if an image is too smaller (less than the 320 pixels stated), then it will appear pixelated and not so clear once the size is increased.
def scale_image(width, height):
baseWidth = 320
scalePercent = (baseWidth/float(width))
scaleHeight = int((float(height)*float(scalePercent)))
scale = (baseWidth, scaleHeight)
print(scaleHeight)
width = 16
height = 16
scale_image(width, height)
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
# this function is about image data. It retrieves metadata information about the images in the dictionary images.
# If the images dictionary is empty, it builds its own
# there is another element called filename that builds a path for each image, specified in the if statement
# the function
# 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
# the below function scales images, for resizing purposes. it is limited to 320 pixels wide.
# 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)
# purpose: transferring through http
# 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'])
# above is how the image is displayed in html
# 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
Imperitive
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
class Image_Data:
def __init__(self, source, label, file, path, baseWidth=320):
self._source = source # variables with self prefix become part of the object,
self._label = label
self._file = file
self._filename = path / file # file with path
self._baseWidth = baseWidth
# Open image and scale to needs
self._img = pilImage.open(self._filename)
self._format = self._img.format
self._mode = self._img.mode
self._originalSize = self.img.size
self.scale_image()
self._html = self.image_to_html(self._img)
self._html_grey = self.image_to_html_grey()
@property
def source(self):
return self._source
@property
def label(self):
return self._label
@property
def file(self):
return self._file
@property
def filename(self):
return self._filename
@property
def img(self):
return self._img
@property
def format(self):
return self._format
@property
def mode(self):
return self._mode
@property
def originalSize(self):
return self._originalSize
@property
def size(self):
return self._img.size
@property
def html(self):
return self._html
@property
def html_grey(self):
return self._html_grey
# Large image scaled to baseWidth of 320
def scale_image(self):
scalePercent = (self._baseWidth/float(self._img.size[0]))
scaleHeight = int((float(self._img.size[1])*float(scalePercent)))
scale = (self._baseWidth, scaleHeight)
self._img = self._img.resize(scale)
# PIL image converted to base64
def image_to_html(self, img):
with BytesIO() as buffer:
img.save(buffer, self._format)
return '<img src="data:image/png;base64,%s">' % base64.b64encode(buffer.getvalue()).decode()
# Create Grey Scale Base64 representation of Image
def image_to_html_grey(self):
img_grey = self._img
numpy = np.array(self._img.getdata()) # PIL image to numpy array
grey_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 numpy:
# 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:
grey_data.append((average, average, average, pixel[3])) # PNG format
else:
grey_data.append((average, average, average))
# end for loop for pixels
img_grey.putdata(grey_data)
return self.image_to_html(img_grey)
# prepares a series of images, provides expectation for required contents
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"}
]
return path, images
# turns data into objects
def image_objects():
id_Objects = []
path, images = image_data()
for image in images:
id_Objects.append(Image_Data(source=image['source'],
label=image['label'],
file=image['file'],
path=path,
))
return id_Objects
# Jupyter Notebook Visualization of Images
if __name__ == "__main__":
for ido in image_objects(): # ido is an Imaged Data Object
print("---- meta data -----")
print(ido.label)
print(ido.source)
print(ido.file)
print(ido.format)
print(ido.mode)
print("Original size: ", ido.originalSize)
print("Scaled size: ", ido.size)
print("-- scaled image --")
display(HTML(ido.html))
print("--- grey image ---")
display(HTML(ido.html_grey))
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.
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.
Hack 1: Lossy vs Lossless Images
The first image below is a highly detailed image of an eye. As I learned through research, many photographers prefer jpeg images due to their ability to store high quality data and info. However, jpeg images tend to use lossy compression, and especially with an image as detailed as an eye, it is very probable that details are lost. The second image below is a monotone image. It has very few details, solid colors, and is not a large size at all.
from PIL import Image
import io
import IPython.display as display
# Load the image
img = Image.open('images/lossyeye.jpg')
# Resize the image
width, height = img.size
new_width = 200
new_height = height // 4
resized_img = img.resize((new_width, new_height))
# Display the resized image in Jupyter notebook
img_bytes = io.BytesIO()
resized_img.save(img_bytes, format='JPEG')
display.display(display.Image(data=img_bytes.getvalue()))
from PIL import Image
import io
import IPython.display as display
# Load the image
img = Image.open('images/losslesseye.jpg')
# Resize the image
width, height = img.size
new_width = 200
new_height = height // 4
resized_img = img.resize((new_width, new_height))
# Display the resized image in Jupyter notebook
img_bytes = io.BytesIO()
resized_img.save(img_bytes, format='JPEG')
display.display(display.Image(data=img_bytes.getvalue()))
import numpy as np
from PIL import Image
import io
import IPython.display as display
# Load the image
img = Image.open('images/losslesseye.jpg')
# Resize the image
width, height = img.size
new_width = 200
new_height = height // 4
resized_img = img.resize((new_width, new_height))
# Create a purple scale version of the image by manipulating pixels directly
purplescale_img = resized_img.copy()
pixels = purplescale_img.load()
for i in range(purplescale_img.size[0]): # width
for j in range(purplescale_img.size[1]): # height
r, g, b = pixels[i, j]
pixels[i, j] = (r, 0, b) # changed to purple scale
# Display the blue scaled image
img_bytes = io.BytesIO()
purplescale_img.save(img_bytes, format='JPEG')
display.display(display.Image(data=img_bytes.getvalue()))
Pixel Image Manipulation
I did some research on how to blur an image, because this may be a feature I incorporate in my project later on. The way that the scipy library creates a function to blur images is that it creates a "Gaussian" filter effect, which is a common visual effect used in photography. The math behind blurring is very complicated, and involves polar coordinates. An array is outputted with random integers, which are then converted. Then, another function plots these coordinates. There is a radius of the pixels, and there is a variable called sigma which is the standard deviation. This sigma value returns the pixel size in howmuchever the specified amount is, to create the blur.
import scipy.misc
from scipy import ndimage
import matplotlib.pyplot as plt
face = Image.open('images/lossyeye.jpg')
blurred_face = ndimage.gaussian_filter(face, sigma=3)
very_blurred = ndimage.gaussian_filter(face, sigma=9)
local_mean = ndimage.uniform_filter(face, size=11) # change sigma to create more blur
plt.figure(figsize=(9, 3))
plt.subplot(131)
plt.imshow(blurred_face, cmap=plt.cm.gray)
plt.axis('off')
plt.subplot(132)
plt.imshow(very_blurred, cmap=plt.cm.gray)
plt.axis('off')
plt.subplot(133)
plt.imshow(local_mean, cmap=plt.cm.gray)
plt.axis('off')
plt.subplots_adjust(wspace=0, hspace=0., top=0.99, bottom=0.01,
left=0.01, right=0.99)
plt.show()