feat: selection Coordinate and weighted distance
This commit is contained in:
@@ -1 +1,37 @@
|
||||
"""Nearest-match selection of a catalog record for a knob coordinate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
from hef.catalog import Record
|
||||
|
||||
SELECTOR_MODES = frozenset({"none", "audio", "video", "av"})
|
||||
CONTENT_MODES = frozenset({"audio", "video", "av"})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Coordinate:
|
||||
left: int
|
||||
right: int
|
||||
dark: int
|
||||
light: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Weights:
|
||||
brain: float = 1.0
|
||||
mood: float = 1.0
|
||||
|
||||
|
||||
def distance(a: Coordinate, b: Coordinate, weights: Weights = Weights()) -> float:
|
||||
"""Weighted Euclidean distance; brain plane and mood plane weighted separately."""
|
||||
brain_sq = (a.left - b.left) ** 2 + (a.right - b.right) ** 2
|
||||
mood_sq = (a.dark - b.dark) ** 2 + (a.light - b.light) ** 2
|
||||
return math.sqrt(weights.brain * brain_sq + weights.mood * mood_sq)
|
||||
|
||||
|
||||
def record_coordinate(record: Record) -> Coordinate:
|
||||
"""The (left, right, dark, light) coordinate of a catalog record."""
|
||||
return Coordinate(record.left, record.right, record.dark, record.light)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from hef.catalog import Record
|
||||
from hef.selection import Coordinate, Weights, distance, record_coordinate
|
||||
|
||||
|
||||
def make_record(**overrides):
|
||||
base = dict(
|
||||
id="r",
|
||||
title="t",
|
||||
source_url="u",
|
||||
source_archive="internet_archive",
|
||||
license="public_domain",
|
||||
mode="video",
|
||||
left=0,
|
||||
right=0,
|
||||
dark=0,
|
||||
light=0,
|
||||
duration_s=600,
|
||||
file_path="/media/r.mp4",
|
||||
)
|
||||
base.update(overrides)
|
||||
return Record(**base)
|
||||
|
||||
|
||||
def test_distance_zero_for_same_point():
|
||||
c = Coordinate(2, 2, 2, 2)
|
||||
assert distance(c, c) == 0.0
|
||||
|
||||
|
||||
def test_distance_is_euclidean_by_default():
|
||||
a = Coordinate(0, 0, 0, 0)
|
||||
b = Coordinate(1, 1, 1, 1)
|
||||
assert distance(a, b) == pytest.approx(2.0) # sqrt(1+1+1+1)
|
||||
|
||||
|
||||
def test_weights_scale_planes_independently():
|
||||
a = Coordinate(0, 0, 0, 0)
|
||||
brain_only = Coordinate(1, 0, 0, 0)
|
||||
mood_only = Coordinate(0, 0, 1, 0)
|
||||
w = Weights(brain=4.0, mood=1.0)
|
||||
assert distance(a, brain_only, w) == pytest.approx(2.0) # sqrt(4*1)
|
||||
assert distance(a, mood_only, w) == pytest.approx(1.0) # sqrt(1*1)
|
||||
|
||||
|
||||
def test_record_coordinate_extracts_axes():
|
||||
record = make_record(left=1, right=2, dark=3, light=4)
|
||||
assert record_coordinate(record) == Coordinate(1, 2, 3, 4)
|
||||
Reference in New Issue
Block a user