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
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
JPG files use lossy compression, which means that some data is lost when the image is compressed and thus, results in a reduction in image quality. However, it also results in smaller file sizes. PNG files, on the other hand, use lossless compression, which means that no data is lost during compression. This results in larger file sizes but better image quality.
- Size, height and width, number of pixels
The size of an image file refers to the amount of storage space it takes up. The larger the file size, the more storage space it will take up. Larger files can cause slower performance. Height and width refer to the dimensions of the image. The height and width of an image determine its aspect ratio, which is the ratio of the width to the height. The number of pixels in an image is the total number of dots or points that make up the image. The more pixels an image has, the higher its resolution and the sharper and more detailed the image will appear. However, more pixels also mean a larger file size and slower performance.
- Visual perception, lossy compression
Visual perception is influenced by factors such as color, contrast, brightness, and detail. It's important to consider how the images will be viewed and ensure that they are optimized accordingly.Lossy compression is a method of reducing the file size of an image by discarding some of the data that makes up the image. This can result in a significant reduction in file size, but can also result in a loss of image quality.
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?
I have never used a macOS in my entire life.
- What are the command you use in Windows terminal to access files?
Some of these commands include:
- cd
: changing directory, navigating files
- ls
: listing directory, monitoring the contents in a directory
- mkdir
: create directory
- sudo
: execute commands
- exit
: ...exit
- code .
: open in vscode?
- What are some of the major differences?
From research, I have concluded that the only major difference (that I hadn't noticed before), is that forward slash is used in Windows and backslash is used in MacOS. For instance:
cd \mnt\c\Users\tmani # MacOS
cd /mnt/c/Users/tmani # Windows
Provide what you observed, struggled with, or leaned while playing with this code.
- Why is path a big deal when working with images?
Because it specifies the exact location of the image within the file system, allowing the software to locate, modify, and organize images correctly.
- How does the meta data source and label relate to Unit 5 topics?
In the context of safe computing, metadata and labels can play a significant role in protecting individuals and organizations from potential risks. For example, metadata can reveal sensitive information about an individual, such as their location, which can be used by malicious actors to track or target them. Therefore, it is important to consider what information is included in the metadata of images and other files and to remove any sensitive information that is not necessary.
- Look up IPython, describe why this is interesting in Jupyter Notebooks for both Pandas and Images?
IPython is relevant in Jupyter notebooks for both Pandas and images because it provides an interactive shell with powerful features like tab completion and object introspection, making data exploration and analysis more efficient and convenient. Additionally, IPython's integration with Matplotlib allows for easy and flexible visualization of data and images within the notebook environment.
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)
Notes and Observations #1
Defined functions: image_data() and image_display()
image_data():
Arguments:
path: default value is "images/"
images: default value is a list of dictionaries, with each dictionary containing information about an image such as its source, label, and file name.
Checks if images is None. If so, it sets it to a list of two dictionaries, each containing information about an image.
For each dictionary in images, it adds a new key-value pair filename which is the concatenation of the path argument and the file value of the dictionary.
It returns the list of dictionaries with the updated filename values.
The image_display():
- Takes in a list of dictionaries containing information about images in the same format as the output of image_data() function and uses the display() function from the IPython.display module to display each image using its filename value.
The if
__name__ == "__main__"
block of code calls image_data() function twice:With a single image specified
Without any arguments.
Then calls image_display() function with the output of image_data() in each case to display the corresponding 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?
Base64 is a way of encoding binary data (such as images) as text, while Binary and Hexadecimal are ways of representing data in their most basic form and a more compact form, respectively. All three systems are used extensively in managing text and image files.
- Translate first 3 letters of your name to Base64.
001100 000000 001101
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?
Two very common examples that come to mind are using keyboards and printers. The characters typed using a keyboard are temporarily stored in an input buffer until the computer is ready to process them. In addition, after sending a document to a printer, the computer will often temporarily store the document in an output buffer until the printer is ready to receive it, which allows the computer to send the data to the printer more quickly than the printer can process it, preventing delays or lost data.
- From your consumer experience, what effects have you experienced from buffering?
By reducing the likelihood of interruptions or delays, buffering can reduce frustration for users. For example, when watching a video online, buffering can prevent the video from pausing or stuttering, which can be very annoying. On the other hand, buffering can result in longer load times for some content, which can be frustrating for users. For example, when uploading or downloading large files, the buffering process can add additional time to the transfer process.
- How do these effects apply to images?
When loading images on a web page, buffering can ensure that the images are loaded smoothly and without delay, which can be very helpful for users who want to view a lot of images quickly. On the other hand, when loading large or complex images, the buffering process can add additional time to the image loading process.
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?
It looks like a number of models and procedures are defined and the procedures are executed using "if" statements and "for" loops.
- Describe Grey Scale algorithm in English or Pseudo code?
Get the pixel data from the image using the "getdata()" method.
For each pixel, compute the average value of the red, green, and blue components of the pixel.
If the pixel has an alpha component, create a new tuple with the average value repeated three times and the original alpha value.
If the pixel does not have an alpha component, create a new tuple with the average value repeated three times.
Replace the pixel data in the original image with the grayscale pixel data.
Generate HTML code to display the grayscale image using the "image_to_base64" function.
- Describe scale image? What is before and after on pixels in three images?
Scale image resizes an image to a specific width of 320 pixels while maintaining its aspect ratio. It does this by calculating the percentage scale that needs to be applied to the image's original height based on the ratio of the original width to the target width. Then it creates the new width and the scaled height and resizes the image using the resize() method from the PIL library. Before the function is applied, the images are opened and their metadata is stored, and after the function is applied, the images are encoded in base64 format and displayed in the Jupyter Notebook.
- Is scale image a type of compression? If so, line it up with College Board terms described?
No, this is not a type of compression, because the scaling function is a type of image resizing or scaling that does not involve any loss of information or reduction in the amount of data. Compression, on the other hand, involves reducing the amount of data in an image by removing some of the information, which may result in a loss of quality or detail.
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()
Notes and Observations #2
Main functions:
image_data(path=Path("images/"), images=None):
Takes a path argument to specify the directory where the image files are located, and an images argument to specify a list of dictionaries that describe the image files.
If images is not specified, will use a default list of image descriptions.
Returns a list of image dictionaries with additional filename properties that specify the full path to the image files.
scale_image(img):
Takes an image file img
Resizes it to a fixed width of 320 pixels, while maintaining the aspect ratio.
Returns the resized image.
image_to_base64(img, format):
Takes an image file img and a format string.
Converts the image file to a base64-encoded string that can be embedded in HTML code.
image_management(image):
Takes an image dictionary image and performs several operations on the image file specified by the filename property.
Reads the image file using the pilImage.open() function, extracts some metadata (format, mode, size), scales the image using the scale_image() function, and generates a base64-encoded HTML code to display the scaled image using the image_to_base64() function.
Adds the following properties to the image dictionary: format, mode, size, pil (the scaled image), scaled_size, and html (the HTML code to display the scaled image).
image_management_add_html_grey(image):
Takes an image dictionary image that has already been processed by the image_management() function and generates a base64-encoded HTML code to display a grayscale version of the image.
Extracts the pixel data from the pil property of the image dictionary, converts each pixel to grayscale, and generates a new list of pixel data in the gray_data property of the image dictionary.
Creates a new pilImage object with the grayscale pixel data, and generates a base64-encoded HTML code to display the grayscale image using the image_to_base64() function.
Adds the gray_data and html_grey properties to the image dictionary.
if __name__ == "__main__"
:Uses the above functions to:
Process a list of image files
Print some metadata about the images
Display the original and grayscale versions of the images in the Jupyter Notebook or web page using the display() and HTML() functions from the IPython.display module.
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
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()
Notes and Observations #3
Defines a class called Image_Data:
Represents an image object with various properties and methods.
Uses several external libraries, including IPython.display, pathlib, PIL, io, and numpy.
The Image_Data class has several properties:
Source, label, file, filename, img, format, mode, originalSize, size, html, and html_grey.
- They represent various attributes of the image, such as the image source, label, file name, format, size, and HTML representation.
Methods:
__init__
, scale_image, image_to_html, and image_to_html_grey:__init__
:Initializes the image object with the given parameters, including the source, label, file name, path, and base width.
Loads the image from the file and sets various properties of the image object.
scale_image:
- Scales the image to the specified base width while maintaining its aspect ratio.
image_to_html:
- Converts the image to an HTML representation that can be displayed in a web page.
image_to_html_grey:
- Converts the image to grayscale and returns its HTML representation.
image_data function:
Defines a list of image objects with their source, label, and file name.
Creates a list of Image_Data objects using the image data defined in image_data.
__main__
:- Displays the meta data, scaled image, and grayscale image of each image object using the display and HTML functions.
CB 2.2
The Binary Numbers and Data Compression quizzes were relatively easy, and I was able to complete both quizzes with little to no difficulty. However, there were some questions that were trickier than others. There were also a lot of high-quality notes.
Binary Numbers Quiz
Adding numbers in 4-bit representation
A certain programming language uses 4-bit binary sequences to represent nonnegative integers. For example, the binary sequence 0101 represents the corresponding decimal value 5. Using this programming language, a programmer attempts to add the decimal values 14 and 15 and assign the sum to the variable total. Which of the following best describes the result of this operation?
A: The correct sum of 29 will be assigned to the variable total.
B: An overflow error will occur because 4 bits is not large enough to represent either of the values 14 or 15.
C: An overflow error will occur because 4 bits is not large enough to represent 29, the sum of 14 and 15.
D: A round-off error will occur because the decimal values 14 and 15 are represented as approximations due to the fixed number of bits used to represent numbers.
Answer: An overflow error will occur because 4 bits is not large enough to represent 29, the sum of 14 and 15. The largest binary value that can be represented using 4 bits is 1111, which is equal to the decimal value 15. Since the sum is larger than the largest representable value, an overflow error will occur.
Data that can be represented with binary sequences
Which of the following are true statements about the data that can be represented using binary sequences?
I. Binary sequences can be used to represent strings of characters.
II. Binary sequences can be used to represent colors.
III. Binary sequences can be used to represent audio recordings.
A: I only
B: I and II only
C: II and III only
D: I, II, and III
Answer: I, II, and III. All digital data is represented at the lowest level as sequences of bits. Statement I is true because strings of characters can be represented by sequences of bits. Statement II is true because colors can be encoded as sequences of bits. Statement III is true because sequences of bits can be used to represent sound.
Data Compression
Compression algorithm for storing a data file
A user wants to save a data file on an online storage site. The user wants to reduce the size of the file, if possible, and wants to be able to completely restore the file to its original version. Which of the following actions best supports the user’s needs?
A: Compressing the file using a lossless compression algorithm before uploading it
B: Compressing the file using a lossy compression algorithm before uploading it
C: Compressing the file using both lossy and lossless compression algorithms before uploading it
D: Uploading the original file without using any compression algorithm
Answer: Compressing the file using a lossless compression algorithm before uploading it. Lossless compression algorithms allow for complete reconstruction of the original data and typically reduce the size of the data.
Lossy and Lossless images
The first image is a JPEG file, and the second image, which has a transparent background, is a PNG file. JPEG is more likely to result in lossy data compression because it uses a lossy compression algorithm, which means that it discards some of the original image data in order to achieve a smaller file size, but it also reduces the quality of the image. JPEG is best suited for photographic images with many colors and gradients, as the loss of data is less noticeable and the smaller file size is important for sharing and storage. PNG, on the other hand, is more likely to result in lossless data compression because it uses a lossless compression algorithm, which means that all of the original image data is preserved. PNG is best suited for images with solid colors and sharp lines, such as logos or graphics, as these types of images benefit from the preservation of all original image data.
from PIL import Image, ImageFilter, ImageDraw, ImageFont
original_image = Image.open("images/smiley.jpg")
blurred_image = original_image.filter(ImageFilter.GaussianBlur(radius=10))
new_size = (int(blurred_image.width * 1.5), int(blurred_image.height * 1.5))
blurred_image = blurred_image.resize(new_size)
draw = ImageDraw.Draw(blurred_image)
text = """Do not go gentle into that good night,
Old age should burn and rave at close of day;
Rage, rage against the dying of the light.
Though wise men at their end know dark is right,
Because their words had forked no lightning they
Do not go gentle into that good night.
Good men, the last wave by, crying how bright
Their frail deeds might have danced in a green bay,
Rage, rage against the dying of the light.
Wild men who caught and sang the sun in flight,
And learn, too late, they grieved it on its way,
Do not go gentle into that good night.
Grave men, near death, who see with blinding sight
Blind eyes could blaze like meteors and be gay,
Rage, rage against the dying of the light.
And you, my father, there on the sad height,
Curse, bless me now with your fierce tears, I pray.
Do not go gentle into that good night.
Rage, rage against the dying of the light."""
font_size = 17
font = ImageFont.truetype("cour.ttf", font_size)
text_size = draw.multiline_textsize(text, font=font)
x = (blurred_image.width - text_size[0]) / 2
y = (blurred_image.height - text_size[1]) / 2
draw.multiline_text((x, y), text, fill=(0, 0, 0), font=font, align='center')
original_image.show()
blurred_image.show()