Skip to content
Snippets Groups Projects
Commit b7182336 authored by Florian Burger's avatar Florian Burger
Browse files

added folder structure and first day exercises

parents
No related branches found
No related tags found
No related merge requests found
exercises/Day-1/PTBcolor.bmp

2.96 MiB

import struct
from PIL import Image
import numpy as np
class Bitmap(object):
def __init__(self, file_name: str = None):
pass
def _read_header(self, fp):
# reads the bitmap header information, should read the height and width into instance attributes
# You may get rid of all the unnecessary information stored there
pass
def _read_pixel_array(self, fp):
# reads the bitmap pixel data into an instance attribute KEEP data structur list(list(list))) as is, see below
pass
def get_pixel(self, x: int, y: int):
# returns a pixel in the form [r,g,b] = [int, int, int] at position (x,y)
pass
def set_pixel(self, x: int, y: int, rgb_index: int, value):
# sets a color of pixel at (x,y) to input value
pass
if __name__ == "__main__":
# Refactor the following code between the markers into a "Bitmap" Class using the skeleton above
# **************************** REFACTOR START **********************************************
# here we read the bitmap
bmp = open("./PTBcolor.bmp", 'rb')
print('Type:', bmp.read(2).decode())
print('Size: %s' % struct.unpack('I', bmp.read(4)))
print('Reserved 1: %s' % struct.unpack('H', bmp.read(2)))
print('Reserved 2: %s' % struct.unpack('H', bmp.read(2)))
# This is the offset to the pixel array
print('Offset: %s' % struct.unpack('I', bmp.read(4)))
print('DIB Header Size: %s' % struct.unpack('I', bmp.read(4)))
print('Width: %s' % struct.unpack('I', bmp.read(4)))
print('Height: %s' % struct.unpack('I', bmp.read(4)))
## Seek the offset position of the pixel array
## This is above output "Offset"
bmp.seek(54)
# We need to read pixels in as rows to later swap the order
# since BMP stores pixels starting at the bottom left.
bmp_pixel_array = []
row = []
pixel_index = 0
# this reads the pixels
# NOTE the hard coded image sizes (width=1300 and height=795)
while True:
if pixel_index == 1300:
pixel_index = 0
bmp_pixel_array.insert(0, row)
if len(row) != 1300:
raise Exception("Row length is not 1300 but " + str(len(row)))
row = []
pixel_index += 1
r_string = bmp.read(1)
g_string = bmp.read(1)
b_string = bmp.read(1)
if len(r_string) == 0:
# This is expected to happen when we've read everything.
if len(bmp_pixel_array) != 795:
print("Warning!!! Read to the end of the file at the correct sub-pixel (red) but we've not read 795 rows!")
break
if len(g_string) == 0:
print("Warning!!! Got 0 length string for green. Breaking.")
break
if len(b_string) == 0:
print("Warning!!! Got 0 length string for blue. Breaking.")
break
r = ord(r_string)
g = ord(g_string)
b = ord(b_string)
row.append([b, g, r])
bmp.close()
## **************************** REFACTOR END **********************************************
# CHECK CODE: You should not see any "Mismatch at index pair ..." lines
bmp = Bitmap("./PTBcolor.bmp")
print("Dims of bitmap: ({},{})".format(bmp.width, bmp.height))
# Test the naive implementation against the PIL library version
# this is for comparison
pil_img = Image.open("./PTBcolor.bmp")
pil_img_arr = np.array(pil_img)
for i in range(795):
for j in range(1300):
for rgb in range(3):
pil_pixel = pil_img.getpixel((j, -i))[rgb]
my_pixel = int(bmp_pixel_array[-i][j][rgb])
if pil_pixel != my_pixel:
print("Mismatch at index pair (x,y,c) = ({0}, {1}, {2}) : {3} != {4}".format(
i, j, rgb, pil_pixel, my_pixel))
import struct
from PIL import Image
import numpy as np
import abc
from collections import namedtuple
# TASK 1
# Use below very simple interface of a pixel in the Bitmap class
Pixel = namedtuple('Pixel', ['b', 'g', 'r'])
# TASK 2
# Complete below Interface (Trait) and use it (implement it) in Bitmap
class Addressable2D(object, metaclass=<EDIT HERE>):
@<EDIT HERE>
def get_at(self, x: int, y: int):
pass
@<EDIT HERE>
def set_at(self, x: int, y: int, data):
pass
@<EDIT HERE>
def get_width(self):
pass
@<EDIT HERE>
def get_height(self):
pass
class Bitmap(object):
def __init__(self, file_name: str = None):
self.width = None
self.height = None
self.bmp_file = file_name
self._pixel_array = None
if file_name is not None:
with open(self.bmp_file, 'rb') as file_handle:
self._read_header(file_handle)
self._read_pixel_array(file_handle)
def _read_header(self, fp):
# we do not need all the information here, just the x and y dimensions
# read 18 bytes to nirvana
_ = fp.read(18)
self.width = int(struct.unpack('I', fp.read(4))[0])
self.height = int(struct.unpack('I', fp.read(4))[0])
# set the file pointer to the beginning of the pixel array
fp.seek(54)
def _read_pixel_array(self, fp):
self._pixel_array = []
row = []
pixel_index = 0
# this reads the pixels
while True:
if pixel_index == self.width:
pixel_index = 0
self._pixel_array.insert(0, row)
if len(row) != self.width:
raise Exception("Row length is not {}} but ".format(self.width))
row = []
pixel_index += 1
r_string = fp.read(1)
g_string = fp.read(1)
b_string = fp.read(1)
if len(r_string) == 0:
# This is expected to happen when we've read everything.
if len(self._pixel_array) != self.height:
print(
"Warning!!! Read to the end of the file at the correct sub-pixel "
"(red) but we've not read {} rows but {}!".format(self.height, len(self._pixel_array)))
break
if len(g_string) == 0:
print("Warning!!! Got 0 length string for green. Breaking.")
break
if len(b_string) == 0:
print("Warning!!! Got 0 length string for blue. Breaking.")
break
r = ord(r_string)
g = ord(g_string)
b = ord(b_string)
# NOTE the order blue, green, red
row.append([b, g, r]) # <EDIT HERE>
def get_pixel(self, x: int, y: int):
return self._pixel_array[y][x]
def set_pixel_value(self, x: int, y: int, rgb_index: int, value):
self._pixel_array[y][x][rgb_index] = value
def set_pixel(self, x: int, y: int, pixel):
self._pixel_array[y][x] = pixel
if __name__ == "__main__":
# CHECK CODE: You should not see any "Mismatch at index pair ..." lines
bmp = Bitmap("./PTBcolor.bmp")
print("Dims of bitmap: ({},{})".format(bmp.width, bmp.height))
# Test the naive implementation against the PIL library version
# this is for comparison
pil_img = Image.open("./PTBcolor.bmp")
pil_img_arr = np.array(pil_img)
for i in range(795):
for j in range(1300):
for rgb in range(3):
pil_pixel = pil_img.getpixel((j, -i))[rgb]
my_pixel = bmp.get_pixel(j, -i)[rgb]
if pil_pixel != my_pixel:
print("Mismatch at index pair (x,y,c) = ({0}, {1}, {2}) : {3} != {4}".format(
i, j, rgb, pil_pixel, my_pixel))
import struct
from PIL import Image
import numpy as np
import copy
import typing
import abc
from collections import namedtuple
# here we define a better Pixel data structure
Pixel = namedtuple('Pixel', ['b', 'g', 'r'])
class Addressable2D(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def get_at(self, x: int, y: int):
pass
@abc.abstractmethod
def set_at(self, x: int, y: int, data):
pass
@abc.abstractmethod
def get_width(self):
pass
@abc.abstractmethod
def get_height(self):
pass
class Bitmap(Addressable2D):
def __init__(self, file_name: str = None):
self.width = None
self.height = None
self.bmp_file = file_name
self._pixel_array = None
if file_name is not None:
with open(self.bmp_file, 'rb') as file_handle:
self._read_header(file_handle)
self._read_pixel_array(file_handle)
def _read_header(self, fp):
# we do not need all the information here, just the x and y dimensions
# read 18 bytes to nirvana
_ = fp.read(18)
self.width = int(struct.unpack('I', fp.read(4))[0])
self.height = int(struct.unpack('I', fp.read(4))[0])
# set the file pointer to the beginning of the pixel array
fp.seek(54)
def _read_pixel_array(self, fp):
self._pixel_array = []
row = []
pixel_index = 0
# this reads the pixels
while True:
if pixel_index == self.width:
pixel_index = 0
self._pixel_array.insert(0, row)
if len(row) != self.width:
raise Exception("Row length is not {}} but ".format(self.width))
row = []
pixel_index += 1
r_string = fp.read(1)
g_string = fp.read(1)
b_string = fp.read(1)
if len(r_string) == 0:
# This is expected to happen when we've read everything.
if len(self._pixel_array) != self.height:
print(
"Warning!!! Read to the end of the file at the correct sub-pixel "
"(red) but we've not read {} rows but {}!".format(self.height, len(self._pixel_array)))
break
if len(g_string) == 0:
print("Warning!!! Got 0 length string for green. Breaking.")
break
if len(b_string) == 0:
print("Warning!!! Got 0 length string for blue. Breaking.")
break
r = ord(r_string)
g = ord(g_string)
b = ord(b_string)
row.append(Pixel(b, g, r))
def get_pixel(self, x: int, y: int) -> Pixel:
return self._pixel_array[y][x]
def set_pixel_value(self, x: int, y: int, rgb_index: int, value):
self._pixel_array[y][x][rgb_index] = value
def set_pixel(self, x: int, y: int, pixel: Pixel):
self._pixel_array[y][x] = pixel
def _init_pixel_array(self, width: int, height: int, color: typing.List = None):
assert color is not None and (isinstance(color, list) and len(color) == 3),\
"Invalid color array passed to _init_pixel_array, should contain 3 colors [r,g,b]. Passed {}".format(color)
if color is None:
use_color = [255, 255, 255]
else:
use_color = color
self.width = width
self. height = height
self._pixel_array = []
for j in range(height):
row = []
for i in range(width):
# NOTE: We need to copy the *list*, otherwise we store the same reference in all places!
c = copy.deepcopy(use_color)
row.append(c)
self._pixel_array.insert(0, row)
@classmethod
def from_dims(cls, width: int, height: int):
this_bmp = cls(file_name=None)
this_bmp._init_pixel_array(width, height, color=[255, 255, 255])
return this_bmp
@classmethod
def from_other(cls, other: 'Bitmap'):
this_bmp = copy.deepcopy(other)
return this_bmp
def get_at(self, x: int, y: int):
return self.get_pixel(x, y)
def set_at(self, x: int, y: int, pixel):
self.set_pixel(x, y, pixel)
def get_width(self):
return self.width
def get_height(self):
return self.height
## <EDIT HERE> Write an interface for a Filter use ABC!
class FilterTrait(object):
pass
## <EDIT HERE> Use above interface here
class DirectNeighborSmoother(<EDIT HERE>):
def filter(self, bmp_out: Addressable2D, bmp_in: Addressable2D):
height = bmp_in.get_height()
width = bmp_in.get_width()
if bmp_out.get_height() != height:
raise RuntimeError("Error in filter: input bitmaps differ in height")
if bmp_out.get_width() != width:
raise RuntimeError("Error in filter: input bitmaps differ in width")
for i_y in range(1, height - 1):
for i_x in range(1, width - 1):
new_value = [0 for _ in range(3)]
for rgb in range(3):
val = <EDIT HERE> # we simply take the arithmetic mean of the point and its 8 direct neighbors
new_value[rgb] = round(val)
bmp_out.set_at(i_x, i_y, Pixel(*new_value))
smoother = DirectNeighborSmoother()
if __name__ == "__main__":
bmp = Bitmap("PTBcolor.bmp")
print("Dims of bitmap: ({},{})".format(bmp.width, bmp.height))
# Test the naive implementation against the PIL library version
# this is for comparison
pil_img = Image.open("PTBcolor.bmp")
pil_img_arr = np.array(pil_img)
for i in range(bmp.width):
for j in range(bmp.height):
for rgb in range(3):
pil_pixel = pil_img.getpixel((i, -j))[rgb]
my_pixel = int(bmp.get_pixel(i, -j)[rgb])
if pil_pixel != my_pixel:
print("Mismatch at index pair (x,y,c) = ({0}, {1}, {2}) : {3} != {4}".format(
i, j, rgb, pil_pixel, my_pixel))
# smooth the image by averaging over the pixel and its direct 8 neighbors
out_bitmap = Bitmap.from_dims(width=bmp.width, height=bmp.height)
<EDIT HERE> # call the filter here
# save this image (we use PIL here, otherwise we have to understand the bitmap format much deeper)
img_out = Image.new('RGB', (out_bitmap.width, out_bitmap.height), 'black')
pixels = img_out.load()
for i in range(0, out_bitmap.width):
for j in range(0, out_bitmap.height):
(r, g, b) = out_bitmap.get_pixel(i, j)
pixels[i, j] = (int(r), int(g), int(b))
img_out.save('PTBcolor_smoothed.bmp')
import random
# This example has been adapted from https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html
class ShapeFactory:
factories = {}
@staticmethod
def add_factory(id, shape_factory):
ShapeFactory.factories[id] = shape_factory
@staticmethod
def create_shape(id):
if id not in ShapeFactory.factories.keys():
ShapeFactory.add_factory(id, eval(id + '.Factory()')) # <## MARK1 ##>
return ShapeFactory.factories[id].create()
class Shape(object):
pass
class Circle(Shape):
def draw(self):
print("Circle.draw")
def erase(self):
print("Circle.erase")
class Factory:
def create(self):
return Circle()
class Square(Shape):
def draw(self):
print("Square.draw")
def erase(self):
print("Square.erase")
class Factory:
def create(self):
return Square()
# what follows is the client code, we want to randomly create shape type objects
def shape_name_gen(n):
types = Shape.__subclasses__()
for i in range(n):
yield random.choice(types).__name__ # NOTE: This is the convention we have chosen, cf. above <## MARK1 ##>
shapes = [ShapeFactory.create_shape(i)
for i in shape_name_gen(5)]
for shape in shapes:
shape.draw()
shape.erase()
class OddIterator:
"""Iterator returning only odd integer numbers up to a certain max value N"""
def __init__(self, N):
self._N = N
self.num = 1
def __iter__(self):
return self
def __next__(self):
num = self.num
self.num += 2
if num <= self._N:
return num
else:
raise StopIteration
odds = OddIterator(5)
print(next(odds))
print(list(odds))
# print(next(iter(odds))) # NOTE: this will throw
odds2 = OddIterator(10)
for item in iter(odds2):
print(item)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment