saving-willy-dev / src /input /input_observation.py
rmm
chore: cleaning up InputObservation (added type hints)
8f958ed
raw
history blame
9.7 kB
import hashlib
from input.input_validator import generate_random_md5
# autogenerated class to hold the input data
class InputObservation:
"""
A class to hold an input observation and associated metadata
Attributes:
image (ndarray):
The image associated with the observation.
latitude (float):
The latitude where the observation was made.
longitude (float):
The longitude where the observation was made.
author_email (str):
The email of the author of the observation.
date (Any): # FIXME: specify the type
The date when the observation was made.
time (Any): # FIXME: specify the type
The time when the observation was made.
date_option (datetime.date):
Additional date option for the observation.
time_option (datetime.time):
Additional time option for the observation.
uploaded_file (UploadedFile):
The uploaded file associated with the observation.
image_md5 (str):
The MD5 hash of the image associated with the observation.
Methods:
__str__():
Returns a string representation of the observation.
__repr__():
Returns a string representation of the observation.
__eq__(other):
Checks if two observations are equal.
__ne__(other):
Checks if two observations are not equal.
show_diff(other):
Shows the differences between two observations.
to_dict():
Converts the observation to a dictionary.
from_dict(data):
Creates an observation from a dictionary.
from_input(input):
Creates an observation from another input observation.
"""
_inst_count = 0
def __init__(
self, image:ndarray=None, latitude:float=None, longitude:float=None,
author_email:str=None, date=None, time=None, date_option:datetime.date=None,
time_option:datetime.time=None,
uploaded_file:UploadedFile=None, image_md5:str=None):
self.image = image
self.latitude = latitude
self.longitude = longitude
self.author_email = author_email
self.date = date
self.time = time
self.date_option = date_option
self.time_option = time_option
self.uploaded_file = uploaded_file
self.image_md5 = image_md5
self._top_predictions = []
InputObservation._inst_count += 1
self._inst_id = InputObservation._inst_count
#self.assign_image_md5()
def set_top_predictions(self, top_predictions:list):
self._top_predictions = top_predictions
# add a method to get the top predictions (property?)
@property
def top_predictions(self):
return self._top_predictions
# add a method to assign the image_md5 only once
def assign_image_md5(self):
raise DeprecationWarning("This method is deprecated. hash is a required constructor argument.")
if not self.image_md5:
self.image_md5 = hashlib.md5(self.uploaded_file.read()).hexdigest() if self.uploaded_file else generate_random_md5()
# new comment / hybj hunk
self._cprint(f"[D] Assigned image md5: {self.image_md5} for {self.uploaded_file}")
def _cprint(self, msg:str, color:str=OKGREEN):
"""Print colored message"""
print(f"{color}{msg}{ENDC}")
def __str__(self):
_im_str = "None" if self.image is None else f"image dims: {self.image.shape}"
return (
f"Observation: {_im_str}, {self.latitude}, {self.longitude}, "
f"{self.author_email}, {self.date}, {self.time}, {self.date_option}, "
f"{self.time_option}, {self.uploaded_file}, {self.image_md5}"
)
def __repr__(self):
_im_str = "None" if self.image is None else f"image dims: {self.image.shape}"
return (
f"Observation: "
f"Image: {_im_str}, "
f"Latitude: {self.latitude}, "
f"Longitude: {self.longitude}, "
f"Author Email: {self.author_email}, "
f"Date: {self.date}, "
f"Time: {self.time}, "
f"Date Option: {self.date_option}, "
f"Time Option: {self.time_option}, "
f"Uploaded Filename: {self.uploaded_file}"
f"Image MD5 hash: {self.image_md5}"
)
def __eq__(self, other):
# TODO: ensure this covers all the attributes (some have been added?)
# - except inst_id which is unique
_image_equality = False
if self.image is None or other.image is None:
_image_equality = other.image == self.image
else: # maybe strong assumption: both are correctly ndarray.. should I test types intead?
_image_equality = (self.image == other.image).all()
equality = (
#self.image == other.image and
_image_equality and
self.latitude == other.latitude and
self.longitude == other.longitude and
self.author_email == other.author_email and
self.date == other.date and self.time == other.time and
self.date_option == other.date_option and
# temporarily skip time_option, it is followed by the clock and that is always differnt
#self.time_option == other.time_option and
self.uploaded_file == other.uploaded_file and
self.image_md5 == other.image_md5
)
return equality
# define a function show_diff(other) that shows the differences between two observations
# only highlight the differences, if element is the same don't show it
# have a summary at the top that shows if the observations are the same or not
def show_diff(self, other):
"""Show the differences between two observations"""
differences = []
if self.image is None or other.image is None:
if other.image != self.image:
differences.append(f" Image is different. (types mismatch: {type(self.image)} vs {type(other.image)})")
else:
if (self.image != other.image).any():
cnt = (self.image != other.image).sum()
differences.append(f" Image is different: {cnt} different pixels.")
if self.latitude != other.latitude:
differences.append(f" Latitude is different. (self: {self.latitude}, other: {other.latitude})")
if self.longitude != other.longitude:
differences.append(f" Longitude is different. (self: {self.longitude}, other: {other.longitude})")
if self.author_email != other.author_email:
differences.append(f" Author email is different. (self: {self.author_email}, other: {other.author_email})")
if self.date != other.date:
differences.append(f" Date is different. (self: {self.date}, other: {other.date})")
if self.time != other.time:
differences.append(f" Time is different. (self: {self.time}, other: {other.time})")
if self.date_option != other.date_option:
differences.append(f" Date option is different. (self: {self.date_option}, other: {other.date_option})")
if self.time_option != other.time_option:
differences.append(f" Time option is different. (self: {self.time_option}, other: {other.time_option})")
if self.uploaded_file != other.uploaded_file:
differences.append(" Uploaded filename is different.")
if self.image_md5 != other.image_md5:
differences.append(" Image MD5 hash is different.")
if differences:
print(f"Observations have {len(differences)} differences:")
for diff in differences:
print(diff)
else:
print("Observations are the same.")
def __ne__(self, other):
return not self.__eq__(other)
def to_dict(self):
return {
#"image": self.image,
"image_filename": self.uploaded_file.name if self.uploaded_file else None,
"image_md5": self.image_md5,
#"image_md5": hashlib.md5(self.uploaded_file.read()).hexdigest() if self.uploaded_file else generate_random_md5(),
"latitude": self.latitude,
"longitude": self.longitude,
"author_email": self.author_email,
"date": self.date,
"time": self.time,
"date_option": str(self.date_option),
"time_option": str(self.time_option),
"uploaded_file": self.uploaded_file
}
@classmethod
def from_dict(cls, data):
return cls(
image=data.get("image"),
latitude=data.get("latitude"),
longitude=data.get("longitude"),
author_email=data.get("author_email"),
date=data.get("date"),
time=data.get("time"),
date_option=data.get("date_option"),
time_option=data.get("time_option"),
uploaded_file=data.get("uploaded_file"),
image_hash=data.get("image_md5")
)
@classmethod
def from_input(cls, input):
return cls(
image=input.image,
latitude=input.latitude,
longitude=input.longitude,
author_email=input.author_email,
date=input.date,
time=input.time,
date_option=input.date_option,
time_option=input.time_option,
uploaded_file=input.uploaded_file,
image_hash=input.image_hash
)