bus_analyzer: Also determine time of day

pull/367/head
Mike Lang 1 year ago
parent 78c053000e
commit 01f93a798a

@ -245,16 +245,22 @@ def read_frame(frames, prototypes_path="./prototypes", verbose=False):
value, score, digits = recognize_odometer(prototypes, frame) value, score, digits = recognize_odometer(prototypes, frame)
if verbose: if verbose:
for guess, score, all_scores in digits: for guess, _score, all_scores in digits:
print("Digit = {} with score {}".format(guess, score)) print("Digit = {} with score {}".format(guess, _score))
print("{}: odo {} with score {}".format(filename, value, score)) print("{}: odo {} with score {}".format(filename, value, score))
value, score, digits = recognize_clock(prototypes, frame) value, score, digits = recognize_clock(prototypes, frame)
if verbose: if verbose:
for guess, score, all_scores in digits: for guess, _score, all_scores in digits:
print("Digit = {} with score {}".format(guess, score)) print("Digit = {} with score {}".format(guess, _score))
print("{}: clock {} with score {}".format(filename, value, score)) print("{}: clock {} with score {}".format(filename, value, score))
value, score, all_scores = recognize_time_of_day(frame)
if verbose:
for color, _score in all_scores:
print("{}: {}".format(color, _score))
print("{}: time-of-day {} with score {}".format(filename, value, score))
@cli @cli
def create_prototype(output, *images): def create_prototype(output, *images):
@ -281,9 +287,32 @@ def get_frame(*segments):
print(filename) print(filename)
def compare_colors(color_a, color_b):
MAX_ERROR_SQUARED = 255**2 * len(color_a)
return 1 - float(sum((a - b)**2 for a, b in zip(color_a, color_b))) / MAX_ERROR_SQUARED
def recognize_time_of_day(frame):
"""Returns time of day, score, all scores."""
COLORS = {
"day": (89, 236, 239),
"dusk": (199, 162, 205),
"night": (1, 1, 1),
"dawn": (51, 59, 142),
}
sample = frame.getpixel((177, 255))
scores = [
(tod, compare_colors(sample, color))
for tod, color in COLORS.items()
]
best, score = max(scores, key=lambda t: t[1])
return best, score, scores
def extract_segment(prototypes, segment): def extract_segment(prototypes, segment):
ODO_SCORE_THRESHOLD = 0.01 ODO_SCORE_THRESHOLD = 0.01
CLOCK_SCORE_THRESHOLD = 0.01 CLOCK_SCORE_THRESHOLD = 0.01
TOD_SCORE_THRESHOLD = 0.9
frame_data = b"".join(extract_frame([segment], segment.start)) frame_data = b"".join(extract_frame([segment], segment.start))
frame = Image.open(BytesIO(frame_data)) frame = Image.open(BytesIO(frame_data))
odometer, score, _ = recognize_odometer(prototypes, frame) odometer, score, _ = recognize_odometer(prototypes, frame)
@ -292,7 +321,10 @@ def extract_segment(prototypes, segment):
clock, score, _ = recognize_clock(prototypes, frame) clock, score, _ = recognize_clock(prototypes, frame)
if score < CLOCK_SCORE_THRESHOLD: if score < CLOCK_SCORE_THRESHOLD:
clock = None clock = None
return odometer, clock tod, score, _ = recognize_time_of_day(frame)
if score < TOD_SCORE_THRESHOLD:
tod = None
return odometer, clock, tod
if __name__ == '__main__': if __name__ == '__main__':

@ -25,8 +25,8 @@ def do_extract_segment(*segment_paths, prototypes_path="./prototypes"):
prototypes = load_prototypes(prototypes_path) prototypes = load_prototypes(prototypes_path)
for segment_path in segment_paths: for segment_path in segment_paths:
segment_info = parse_segment_path(segment_path) segment_info = parse_segment_path(segment_path)
odometer, clock = extract_segment(prototypes, segment_info) odometer, clock, tod = extract_segment(prototypes, segment_info)
print(f"{segment_path} {odometer} {clock}") print(f"{segment_path} {odometer} {clock} {tod}")
@cli @cli
@ -75,7 +75,7 @@ def compare_segments(dbconnect, base_dir='.', prototypes_path="./prototypes", si
for old_odometer, segment in selected: for old_odometer, segment in selected:
path = os.path.join(base_dir, segment) path = os.path.join(base_dir, segment)
segment_info = parse_segment_path(path) segment_info = parse_segment_path(path)
odometer, clock = extract_segment(prototypes, segment_info) odometer, clock, tod = extract_segment(prototypes, segment_info)
results.append((segment, old_odometer, odometer)) results.append((segment, old_odometer, odometer))
matching = 0 matching = 0
@ -112,24 +112,25 @@ def analyze_segment(conn, prototypes, segment_path, check_segment_name=None):
assert segment_name == check_segment_name assert segment_name == check_segment_name
try: try:
odometer, clock = extract_segment(prototypes, segment_info) odometer, clock, tod = extract_segment(prototypes, segment_info)
except Exception: except Exception:
logging.warning(f"Failed to extract segment {segment_path!r}", exc_info=True) logging.warning(f"Failed to extract segment {segment_path!r}", exc_info=True)
odometer = None odometer = None
error = traceback.format_exc() error = traceback.format_exc()
else: else:
logging.info(f"Got odometer = {odometer}, clock = {clock} for segment {segment_path!r}") logging.info(f"Got odometer = {odometer}, clock = {clock}, time of day = {tod} for segment {segment_path!r}")
error = None error = None
database.query( database.query(
conn, conn,
""" """
INSERT INTO bus_data (channel, timestamp, segment, error, odometer, clock) INSERT INTO bus_data (channel, timestamp, segment, error, odometer, clock, timeofday)
VALUES (%(channel)s, %(timestamp)s, %(segment)s, %(error)s, %(odometer)s, %(clock)s) VALUES (%(channel)s, %(timestamp)s, %(segment)s, %(error)s, %(odometer)s, %(clock)s, %(timeofday)s)
ON CONFLICT (channel, timestamp, segment) DO UPDATE ON CONFLICT (channel, timestamp, segment) DO UPDATE
SET error = %(error)s, SET error = %(error)s,
odometer = %(odometer)s, odometer = %(odometer)s,
clock = %(clock)s clock = %(clock)s,
timeofday = %(timeofday)s
""", """,
channel=segment_info.channel, channel=segment_info.channel,
timestamp=segment_info.start, timestamp=segment_info.start,
@ -137,6 +138,7 @@ def analyze_segment(conn, prototypes, segment_path, check_segment_name=None):
error=error, error=error,
odometer=odometer, odometer=odometer,
clock=clock, clock=clock,
timeofday=tod,
) )

@ -157,6 +157,7 @@ CREATE TABLE playlists (
-- be determined. -- be determined.
-- The odometer column is in miles. The game shows the odometer to the 1/10th mile precision. -- The odometer column is in miles. The game shows the odometer to the 1/10th mile precision.
-- The clock is in minutes since 00:00, in 12h time. -- The clock is in minutes since 00:00, in 12h time.
-- The time of day is one of "day", "dusk", "night", or "dawn"
-- The segment may be NULL, which indicates a manually-inserted value. -- The segment may be NULL, which indicates a manually-inserted value.
-- The primary key serves two purposes: -- The primary key serves two purposes:
-- It provides an index on channel, followed by a range index on timestamp -- It provides an index on channel, followed by a range index on timestamp
@ -170,6 +171,7 @@ CREATE TABLE bus_data (
error TEXT, error TEXT,
odometer DOUBLE PRECISION, odometer DOUBLE PRECISION,
clock INTEGER, clock INTEGER,
timeofday TEXT,
PRIMARY KEY (channel, timestamp, segment) PRIMARY KEY (channel, timestamp, segment)
); );
EOSQL EOSQL

Loading…
Cancel
Save