finish ex3
52
ex3.py
@ -1,13 +1,14 @@
|
||||
import os.path
|
||||
from glob import glob
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageStat
|
||||
|
||||
|
||||
class ImageStandardizer:
|
||||
def __init__(self, input_dir: str) -> None:
|
||||
super().__init__()
|
||||
|
||||
# todo scan for .jpeg files
|
||||
|
||||
# scan input dir for files
|
||||
files = glob(input_dir + '/**/*.jpg', recursive=True)
|
||||
if len(files) == 0:
|
||||
@ -19,8 +20,47 @@ class ImageStandardizer:
|
||||
# sort filenames
|
||||
files.sort()
|
||||
|
||||
def analyze_images(self) -> None:
|
||||
pass
|
||||
self.files = files
|
||||
self.mean = None
|
||||
self.std = None
|
||||
|
||||
def get_standardized_images(self) -> None:
|
||||
pass
|
||||
def analyze_images(self) -> (np.array, np.array):
|
||||
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
@ -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!!")
|
7
unittest/solutions/unittest_input_0/filenames.txt
Normal 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
|
BIN
unittest/solutions/unittest_input_0/images.pkl
Normal file
BIN
unittest/solutions/unittest_input_0/mean_and_std.pkl
Normal file
9
unittest/solutions/unittest_input_1/filenames.txt
Normal 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
|
BIN
unittest/solutions/unittest_input_1/images.pkl
Normal file
BIN
unittest/solutions/unittest_input_1/mean_and_std.pkl
Normal file
BIN
unittest/unittest_input_0/00.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
unittest/unittest_input_0/01.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
unittest/unittest_input_0/02.jpg
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
unittest/unittest_input_0/04.jpg
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
unittest/unittest_input_0/05.jpg
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
unittest/unittest_input_0/subfolder/06.jpg
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
unittest/unittest_input_0/subfolder/07.jpg
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
unittest/unittest_input_1/08.jpg
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
unittest/unittest_input_1/09.jpg
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
unittest/unittest_input_1/10.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
unittest/unittest_input_1/11.jpg
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
unittest/unittest_input_1/12.jpg
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
unittest/unittest_input_1/13.jpg
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
unittest/unittest_input_1/14.jpg
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
unittest/unittest_input_1/subfolder/15.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
unittest/unittest_input_1/subfolder/subsubfolder/03.webp
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
unittest/unittest_input_1/subfolder/subsubfolder/16.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
unittest/unittest_input_1/subfolder/subsubfolder/17.jpg
Normal file
After Width: | Height: | Size: 73 KiB |