fix: assume the largest PPU anchor table when the .dxnn has no layout

This commit is contained in:
aahmetdurmaz 2026-09-10 20:26:00 +03:00
parent 1388305439
commit 238073f943
2 changed files with 50 additions and 47 deletions

View File

@ -77,9 +77,6 @@ PPU_ANCHORS_BY_SCALES = {
}
PPU_MAX_STRIDE = 32
# the largest scale count we have a table for; a count below this with no
# table of its own is under-evidenced rather than disproven, so it still
# gets the biggest table as a best guess
PPU_MAX_KNOWN_SCALES = max(PPU_ANCHORS_BY_SCALES)
# .dxnn: "DXNN", a 4-byte LE version, then a JSON index padded to
@ -627,15 +624,18 @@ def ppu_scale_count_lower_bound(records: np.ndarray, width: int, height: int) ->
def resolve_scale_count(
records: np.ndarray, scale_count: int | None, width: int, height: int
) -> int:
"""`scale_count` if given, else the fewest scales this frame alone
proves, defaulting to 3 when the frame has no records to read."""
"""`scale_count` if given, else PPU_MAX_KNOWN_SCALES unless this frame
proves more; a frame showing only the finer layers near the origin is
what a bigger head looks like too, so it never argues for fewer."""
if scale_count is not None:
return scale_count
if len(records) == 0:
return 3
return PPU_MAX_KNOWN_SCALES
return ppu_scale_count_lower_bound(records, width, height)
return max(
PPU_MAX_KNOWN_SCALES, ppu_scale_count_lower_bound(records, width, height)
)
def ppu_grid_regression_geometry(
@ -1047,10 +1047,11 @@ class DeepxDetector(DetectionApi):
if layout is None:
logger.warning(
"Could not read the PPU head layout from %s; inferring its "
"scale count from the detections instead, which can misplace "
"boxes until a record proves every scale",
"Could not read the PPU head layout from %s; assuming a "
"%d-scale head, so a head with fewer scales will decode to "
"misplaced boxes",
model_path,
PPU_MAX_KNOWN_SCALES,
)
return None
@ -1182,7 +1183,9 @@ class DeepxDetector(DetectionApi):
def decode_ppu(self, outputs: list[np.ndarray]) -> np.ndarray:
"""Decode PPU records. The head layout read from the model fixes
the kind, scale count and grid decode from the first frame; without
it, each is settled from the records and kept once proven."""
it, the kind and grid decode are settled from the records and kept
once proven, and the scale count is PPU_MAX_KNOWN_SCALES unless a
record proves more (a partial frame never argues for fewer)."""
records = ppu_records(outputs)
boxes = None
if records is not None and len(records):
@ -1201,6 +1204,7 @@ class DeepxDetector(DetectionApi):
if boxes is not None:
if self.ppu_layout is None:
self.ppu_scale_count = max(
PPU_MAX_KNOWN_SCALES,
self.ppu_scale_count,
ppu_scale_count_lower_bound(records, self.width, self.height),
)

View File

@ -362,17 +362,16 @@ class TestDeepxPpuDecode(unittest.TestCase):
self.assertAlmostEqual(detections[0][4], 0.137188, places=5)
self.assertAlmostEqual(detections[0][5], 0.125750, places=5)
def test_a_grid_cell_past_the_coarser_layout_proves_a_third_scale(self):
"""A layer-0 cell at column 40 of a 640 input needs stride 8, so the
head has three scales even though no layer-2 record has shown up;
the layer-1 record in the same frame must then read against the
three-scale table, not the two-scale one its layer index alone
would suggest."""
def test_a_frame_of_only_the_finer_layers_still_reads_as_three_scales(self):
"""Layers 0 and 1 near the origin look the same from a two-scale
head and from the finer two thirds of a three-scale one, so with no
scale_count given the frame reads against the three-scale table,
never the two-scale one its highest layer index alone suggests."""
detections = decode_ppu(
[
build_ppu_records(
build_ppu_record((0.5, 0.5, 0.4, 0.4), grid=(3, 4, 0, 1)),
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 40, 1, 0)),
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 9, 1, 0)),
)
],
640,
@ -388,6 +387,10 @@ class TestDeepxPpuDecode(unittest.TestCase):
self.assertAlmostEqual(layer_one[0][2], 0.057, places=5)
self.assertAlmostEqual(layer_one[0][4], 0.118, places=5)
self.assertAlmostEqual(layer_one[0][5], 0.1275, places=5)
# layer 0 of 3 is stride 8 and box 1 is the 16x30 anchor
layer_zero = detections[np.isclose(detections[:, 3], 0.116750)]
self.assertEqual(len(layer_zero), 1)
self.assertAlmostEqual(layer_zero[0][2], 0.045313, places=5)
def test_scale_count_lower_bound_reads_layer_and_grid_position(self):
"""Every record proves at least one scale past its layer, and a
@ -1221,12 +1224,11 @@ class TestDeepxDetectorLayout(unittest.TestCase):
self.assertEqual(second[0][0], 3)
self.assertTrue(detector.ppu_anchor_free)
def test_ppu_scale_count_is_kept_once_a_coarser_layer_is_seen(self):
"""A frame that only shows layer 0 cannot tell a two-scale head from
a three-scale one and falls back to three; once a frame proves
layer 1 exists, two scales is the best-evidenced count and every
later layer-0-only frame must use the two-scale table, until some
record proves a third scale."""
def test_ppu_scale_count_without_a_layout_never_drops_below_three(self):
"""With no layout to read, a three-scale head whose first frames
only hold layers 0 and 1 near the origin must not be read as a
two-scale head: those records are exactly what a three-scale head
emits too, so every frame keeps the three-scale table."""
detector, _ = self._detector(
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
)
@ -1236,31 +1238,29 @@ class TestDeepxDetectorLayout(unittest.TestCase):
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 9, 1, 0))
]
first = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
# layer 0 read against the three-scale default: stride 8, anchor 16x30
# layer 0 of 3: stride 8, anchor 16x30
self.assertAlmostEqual(first[0][3], 0.116750, places=5)
self.assertEqual(detector.ppu_scale_count, 3)
detector.session.run.return_value = [
build_ppu_record((0.5, 0.5, 0.4, 0.4), grid=(3, 4, 0, 1))
]
second = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
# layer 1 of 2 is stride 32 and box 0 is the 81x82 anchor
self.assertAlmostEqual(second[0][3], 0.1845, places=5)
self.assertEqual(detector.ppu_scale_count, 2)
# layer 1 of 3: stride 16, anchor 30x61, not layer 1 of 2
self.assertAlmostEqual(second[0][3], 0.0975, places=5)
self.assertEqual(detector.ppu_scale_count, 3)
detector.session.run.return_value = [
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 9, 1, 0))
]
third = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
# the same layer-0 record as the first frame, now read against the
# two-scale table proven by the second frame: stride 16, anchor 23x27
self.assertAlmostEqual(third[0][3], 0.236031, places=5)
self.assertAlmostEqual(third[0][3], 0.116750, places=5)
def test_ppu_scale_count_grows_when_a_grid_cell_proves_a_third_scale(self):
"""A three-scale head whose early frames only ever hold layers 0
and 1 near the origin reads as two scales; a later layer-0 record
at column 40 of 640 needs stride 8, which proves the third scale
without a layer-2 record ever appearing, and every frame after
that must read against the three-scale table."""
def test_ppu_scale_count_grows_when_a_grid_cell_proves_a_fourth_scale(self):
"""A layer-0 record at column 80 of 640 needs stride 4, which proves
a fourth scale with no layer-3 record ever appearing; there is no
table for that, so it is reported once and every frame after that
stays unplaced rather than reading against the three-scale table."""
detector, _ = self._detector(
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
)
@ -1269,20 +1269,19 @@ class TestDeepxDetectorLayout(unittest.TestCase):
layer_one = build_ppu_record((0.5, 0.5, 0.4, 0.4), grid=(3, 4, 0, 1))
detector.session.run.return_value = [layer_one]
first = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
# read as layer 1 of 2: stride 32, anchor 81x82
self.assertAlmostEqual(first[0][3], 0.1845, places=5)
self.assertEqual(detector.ppu_scale_count, 2)
self.assertAlmostEqual(first[0][3], 0.0975, places=5)
detector.session.run.return_value = [
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 40, 1, 0))
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 80, 1, 0))
]
detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
self.assertEqual(detector.ppu_scale_count, 3)
with self.assertLogs("frigate.detectors.plugins.deepx", level="ERROR") as logs:
second = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
detector.session.run.return_value = [layer_one]
third = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
detector.session.run.return_value = [layer_one]
third = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
# the same record, now layer 1 of 3: stride 16, anchor 30x61
self.assertAlmostEqual(third[0][3], 0.0975, places=5)
self.assertEqual(detector.ppu_scale_count, 4)
self.assertTrue(np.all(second == 0) and np.all(third == 0))
self.assertEqual(len([r for r in logs.records if r.levelname == "ERROR"]), 1)
def _ppu_detector_for(self, ppu, layers):
"""A PPU detector loaded from a model file that describes its head."""