finish ex3

This commit is contained in:
lukas-heiligenbrunner 2022-04-29 12:16:01 +02:00
parent 4dbe6bfd7a
commit 4fade673f7
26 changed files with 266 additions and 6 deletions

52
ex3.py
View File

@ -1,13 +1,14 @@
import os.path import os.path
from glob import glob from glob import glob
import numpy as np
from PIL import Image, ImageStat
class ImageStandardizer: class ImageStandardizer:
def __init__(self, input_dir: str) -> None: def __init__(self, input_dir: str) -> None:
super().__init__() super().__init__()
# todo scan for .jpeg files
# scan input dir for files # scan input dir for files
files = glob(input_dir + '/**/*.jpg', recursive=True) files = glob(input_dir + '/**/*.jpg', recursive=True)
if len(files) == 0: if len(files) == 0:
@ -19,8 +20,47 @@ class ImageStandardizer:
# sort filenames # sort filenames
files.sort() files.sort()
def analyze_images(self) -> None: self.files = files
pass self.mean = None
self.std = None
def get_standardized_images(self) -> None: def analyze_images(self) -> (np.array, np.array):
pass mymean = np.zeros((3,), dtype=np.float64)
mystd = np.zeros((3,), dtype=np.float64)
for file in self.files:
img = Image.open(file)
stats = ImageStat.Stat(img)
mymean += stats.mean
mystd += stats.stddev
del img
mymean /= len(self.files)
mystd /= len(self.files)
self.mean = mymean
self.std = mystd
return mymean, mystd
def get_standardized_images(self):
if self.mean is None or self.std is None:
raise ValueError
for file in self.files:
img = Image.open(file)
arr = np.asarray(img.getdata(), dtype=np.float32).reshape(
(img.height, img.width, 3)) # and reshape into 3channel rgb image
# standardize image
arr = (arr - self.mean) / self.std
yield np.array(arr, dtype=np.float32)
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
std = ImageStandardizer(input_dir='unittest/unittest_input_0')
print(std.analyze_images())
for i in std.get_standardized_images():
print(i)

204
ex3_unittest.py Normal file
View File

@ -0,0 +1,204 @@
"""
Author -- Michael Widrich, Andreas Schörgenhumer
Contact -- schoergenhumer@ml.jku.at
Date -- 02.03.2022
###############################################################################
The following copyright statement applies to all code within this file.
Copyright statement:
This material, no matter whether in printed or electronic form,
may be used for personal and non-commercial educational use only.
Any reproduction of this manuscript, no matter whether as a whole or in parts,
no matter whether in printed or in electronic form, requires explicit prior
acceptance of the authors.
###############################################################################
Images taken from: https://pixabay.com/
"""
import gzip
import os
import signal
import sys
from glob import glob
from types import GeneratorType
import dill as pkl
import numpy as np
def print_outs(outs, line_token="-"):
print(line_token * 40)
print(outs, end="" if isinstance(outs, str) and outs.endswith("\n") else "\n")
print(line_token * 40)
time_given = int(15)
check_for_timeout = hasattr(signal, "SIGALRM")
if check_for_timeout:
def handler(signum, frame):
raise TimeoutError(f"Timeout after {time_given}sec")
signal.signal(signal.SIGALRM, handler)
ex_file = "ex3.py"
full_points = 15
points = full_points
python = sys.executable
inputs = sorted(glob(os.path.join("unittest", "unittest_input_*"), recursive=True))
if not len(inputs):
raise FileNotFoundError("Could not find unittest_input_* files")
for test_i, input_folder in enumerate(inputs):
comment = ""
fcall = ""
with open(os.devnull, "w") as null:
# sys.stdout = null
try:
if check_for_timeout:
signal.alarm(time_given)
from ex3 import ImageStandardizer
signal.alarm(0)
else:
from ex3 import ImageStandardizer
proper_import = True
except Exception as e:
errs = e
points -= full_points / len(inputs)
proper_import = False
finally:
sys.stdout.flush()
sys.stdout = sys.__stdout__
if proper_import:
with open(os.devnull, "w") as null:
# sys.stdout = null
try:
if check_for_timeout:
signal.alarm(time_given)
# check constructor
instance = ImageStandardizer(input_dir=input_folder)
fcall = f"ImageStandardizer(input_dir='{input_folder}')"
signal.alarm(0)
else:
# check constructor
instance = ImageStandardizer(input_dir=input_folder)
fcall = f"ImageStandardizer(input_dir='{input_folder}')"
errs = ""
# check correct file names + sorting
input_basename = os.path.basename(input_folder)
with open(os.path.join("unittest", "solutions", input_basename, f"filenames.txt"), "r") as f:
# must replace the separator that was used when creating the solution files
files_sol = f.read().replace("\\", os.path.sep).splitlines()
# for simplicity's sake, only compare relative paths here
common = os.path.commonprefix(instance.files)
rel_instance_files = [os.path.join(input_folder, f[len(common):]) for f in instance.files]
if not hasattr(instance, "files"):
points -= full_points / len(inputs) / 3
comment += f"Attributes 'files' missing.\n"
elif rel_instance_files != files_sol:
points -= full_points / len(inputs) / 3
comment += f"Attribute 'files' should be {files_sol} but is {instance.files} (see directory 'solutions').\n"
elif len(instance.files) != len(files_sol):
points -= full_points / len(inputs) / 3
comment += f"Number of files should be {len(files_sol)} but is {len(instance.files)} (see directory 'solutions').\n"
# check if class has method analyze_images
method = "analyze_images"
if not hasattr(instance, method):
comment += f"Method '{method}' missing.\n"
points -= full_points / len(inputs) / 3
else:
# check for correct data types
stats = instance.analyze_images()
if (type(stats) is not tuple) or (len(stats) != 2):
points -= full_points / len(inputs) / 3
comment += f"Incorrect return value of method '{method}' (should be tuple of length 2).\n"
else:
with open(os.path.join("unittest", "solutions", input_basename, f"mean_and_std.pkl"),
"rb") as fh:
data = pkl.load(fh)
m = data["mean"]
s = data["std"]
if not (isinstance(stats[0], np.ndarray) and isinstance(stats[1], np.ndarray) and
stats[0].dtype == np.float64 and stats[1].dtype == np.float64 and
stats[0].shape == (3,) and stats[1].shape == (3,)):
points -= full_points / len(inputs) / 3
comment += f"Incorrect return data type of method '{method}' (tuple entries should be np.ndarray of dtype np.float64 and shape (3,)).\n"
else:
if not np.isclose(stats[0], m, atol=0).all():
points -= full_points / len(inputs) / 6
comment += f"Mean should be {m} but is {stats[0]} (see directory 'solutions').\n"
if not np.isclose(stats[1], s, atol=0).all():
points -= full_points / len(inputs) / 6
comment += f"Std should be {s} but is {stats[1]} (see directory 'solutions').\n"
# check if class has method get_standardized_images
method = "get_standardized_images"
if not hasattr(instance, method):
comment += f"Method '{method}' missing.\n"
points -= full_points / len(inputs) / 3
# check for correct data types
elif not isinstance(instance.get_standardized_images(), GeneratorType):
points -= full_points / len(inputs) / 3
comment += f"'{method}' is not a generator.\n"
else:
# Read correct image solutions
with gzip.open(os.path.join("unittest", "solutions", input_basename, "images.pkl"), "rb") as fh:
ims_sol = pkl.load(file=fh)
# Get image submissions
ims_sub = list(instance.get_standardized_images())
if not len(ims_sub) == len(ims_sol):
points -= full_points / len(inputs) / 3
comment += f"{len(ims_sol)} image arrays should have been returned but got {len(ims_sub)}.\n"
elif any([im_sub.dtype.num != np.dtype(np.float32).num for im_sub in ims_sub]):
points -= full_points / len(inputs) / 3
comment += f"Returned image arrays should have datatype np.float32 but at least one array isn't.\n"
else:
equal = [np.all(np.isclose(im_sub, im_sol, atol=0)) for im_sub, im_sol in zip(ims_sub, ims_sol)]
if not all(equal):
points -= full_points / len(inputs) / 3
comment += f"Returned images {list(np.where(np.logical_not(equal))[0])} do not match solution (see images.pkl files for solution).\n"
except Exception as e:
errs = e
points -= full_points / len(inputs)
finally:
sys.stdout.flush()
sys.stdout = sys.__stdout__
print()
print_outs(f"Test {test_i}", line_token="#")
print("Function call:")
print_outs(fcall)
if errs:
print(f"Some unexpected errors occurred:")
print_outs(f"{type(errs).__name__}: {errs}")
else:
print("Notes:")
print_outs("No issues found" if comment == "" else comment)
# due to floating point calculations it could happen that we get -0 here
if points < 0:
assert abs(points) < 1e-7, f"points were {points} < 0: error when subtracting points?"
points = abs(points)
print(f"Current points: {points:.2f}")
print(f"\nEstimated points upon submission: {points:.2f} (out of {full_points:.2f})")
print(f"This is only an estimate, see 'Instructions for submitting homework' in Moodle "
f"for common mistakes that can still lead to 0 points.")
if not check_for_timeout:
print("\n!!Warning: Had to switch to Windows compatibility version and did not check for timeouts!!")

View File

@ -0,0 +1,7 @@
unittest\unittest_input_0\00.jpg
unittest\unittest_input_0\01.jpg
unittest\unittest_input_0\02.jpg
unittest\unittest_input_0\04.jpg
unittest\unittest_input_0\05.jpg
unittest\unittest_input_0\subfolder\06.jpg
unittest\unittest_input_0\subfolder\07.jpg

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,9 @@
unittest\unittest_input_1\08.jpg
unittest\unittest_input_1\09.jpg
unittest\unittest_input_1\11.jpg
unittest\unittest_input_1\12.jpg
unittest\unittest_input_1\13.jpg
unittest\unittest_input_1\14.jpg
unittest\unittest_input_1\subfolder\15.jpg
unittest\unittest_input_1\subfolder\subsubfolder\16.jpg
unittest\unittest_input_1\subfolder\subsubfolder\17.jpg

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB