205 lines
9.2 KiB
Python
205 lines
9.2 KiB
Python
"""
|
|
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!!")
|