forked from 1b-agent/fieldcraft-upstream
demo video: caption each stage with the MCP tool it calls
A band under the window names the tool behind each stage (open, mesh, batch, check_model, solve, show_results). caption.py draws the bars in the standard library from the interface's Inter SemiBold (OFL): a minimal TrueType glyf reader, a non-zero scanline rasteriser and a zlib PNG writer, unit-tested with python3 -m unittest. ffmpeg pads each picture and composites the bar with its overlay filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
5665cc3d8c
commit
30812250d3
5 files changed
+544
-18
No files matched your search
@@ -0,0 +1,119 @@
|
||||
"""Tests of the caption pictures: `python3 -m unittest` in tools/demo-video."""
|
||||
|
||||
import struct
|
||||
import unittest
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
import caption
|
||||
|
||||
FONT = Path(__file__).resolve().parents[2] / "crates" / "ui-egui" / "fonts" / "Inter-SemiBold.ttf"
|
||||
|
||||
|
||||
def decode(data):
|
||||
"""Width, height and RGB rows of a PNG written by `caption.png` (filter 0, one IDAT)."""
|
||||
assert data[:8] == b"\x89PNG\r\n\x1a\n"
|
||||
at, chunks = 8, {}
|
||||
while at < len(data):
|
||||
(length,) = struct.unpack_from(">I", data, at)
|
||||
tag, body = data[at + 4:at + 8], data[at + 8:at + 8 + length]
|
||||
(crc,) = struct.unpack_from(">I", data, at + 8 + length)
|
||||
assert crc == zlib.crc32(tag + body) & 0xFFFFFFFF, tag
|
||||
chunks[tag] = body
|
||||
at += 12 + length
|
||||
width, height, depth, kind = struct.unpack_from(">IIBB", chunks[b"IHDR"])
|
||||
assert (depth, kind) == (8, 2)
|
||||
raw = zlib.decompress(chunks[b"IDAT"])
|
||||
stride = 1 + 3 * width
|
||||
rows = [raw[y * stride + 1:(y + 1) * stride] for y in range(height)]
|
||||
assert all(raw[y * stride] == 0 for y in range(height))
|
||||
return width, height, rows
|
||||
|
||||
|
||||
class Fill(unittest.TestCase):
|
||||
def test_square_covers_its_pixels_and_partial_edges(self):
|
||||
cover = caption.fill([[(1, 1), (3.5, 1), (3.5, 3), (1, 3)]], 5, 4)
|
||||
at = lambda x, y: cover[y * 5 + x]
|
||||
self.assertAlmostEqual(at(1, 1), 1.0)
|
||||
self.assertAlmostEqual(at(2, 2), 1.0)
|
||||
self.assertAlmostEqual(at(3, 1), 0.5)
|
||||
self.assertAlmostEqual(at(0, 1), 0.0)
|
||||
self.assertAlmostEqual(at(4, 2), 0.0)
|
||||
self.assertAlmostEqual(at(1, 0), 0.0)
|
||||
self.assertAlmostEqual(sum(cover), 2.5 * 2)
|
||||
|
||||
def test_nonzero_winding_fills_overlaps_once_and_leaves_holes(self):
|
||||
outer = [(0, 0), (6, 0), (6, 6), (0, 6)]
|
||||
hole = [(2, 2), (2, 4), (4, 4), (4, 2)] # opposite direction
|
||||
cover = caption.fill([outer, hole], 6, 6)
|
||||
self.assertAlmostEqual(cover[3 * 6 + 3], 0.0)
|
||||
self.assertAlmostEqual(cover[1 * 6 + 1], 1.0)
|
||||
twice = caption.fill([outer, outer], 6, 6)
|
||||
self.assertAlmostEqual(max(twice), 1.0)
|
||||
|
||||
def test_shapes_outside_the_picture_are_clipped(self):
|
||||
cover = caption.fill([[(-5, -5), (20, -5), (20, 20), (-5, 20)]], 3, 2)
|
||||
self.assertEqual(cover, [1.0] * 6)
|
||||
|
||||
|
||||
class Png(unittest.TestCase):
|
||||
def test_round_trip(self):
|
||||
canvas = caption.Canvas(3, 2, (0, 0, 0))
|
||||
canvas.paint([[(1, 1), (2, 1), (2, 2), (1, 2)]], (255, 0, 0))
|
||||
canvas.paint([[(2, 1), (2.5, 1), (2.5, 2), (2, 2)]], (0, 0, 255)) # half a pixel
|
||||
width, height, rows = decode(canvas.png())
|
||||
self.assertEqual((width, height), (3, 2))
|
||||
self.assertEqual(rows[0], bytes(9))
|
||||
self.assertEqual(rows[1], bytes((0, 0, 0, 255, 0, 0, 0, 0, 128)))
|
||||
|
||||
|
||||
class Font(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.font = caption.Font.load(FONT)
|
||||
|
||||
def test_metrics(self):
|
||||
self.assertGreater(self.font.units_per_em, 0)
|
||||
self.assertGreater(self.font.advance(self.font.glyph("m")), self.font.advance(self.font.glyph("i")))
|
||||
self.assertEqual(self.font.contours(self.font.glyph(" ")), [])
|
||||
|
||||
def test_outlines_have_their_holes(self):
|
||||
size = 100
|
||||
polygons, _ = caption.text_polygons(self.font, "o", size, 0, size)
|
||||
self.assertEqual(len(polygons), 2)
|
||||
cover = caption.fill(polygons, size, size)
|
||||
xs = [p[0] for p in polygons[0]]
|
||||
ys = [p[1] for p in polygons[0]]
|
||||
cx, cy = int((min(xs) + max(xs)) / 2), int((min(ys) + max(ys)) / 2)
|
||||
self.assertAlmostEqual(cover[cy * size + cx], 0.0) # the counter
|
||||
self.assertGreater(cover[cy * size + int(min(xs)) + 3], 0.99) # the stroke
|
||||
|
||||
def test_composite_glyphs(self):
|
||||
# An accented letter is a base glyph plus a mark placed by an offset.
|
||||
e = self.font.contours(self.font.glyph("e"))
|
||||
e_acute = self.font.contours(self.font.glyph("é"))
|
||||
self.assertGreater(len(e_acute), len(e))
|
||||
self.assertGreater(max(y for c in e_acute for _, y in c), max(y for c in e for _, y in c))
|
||||
|
||||
def test_hostile_data_is_refused(self):
|
||||
with self.assertRaises(caption.FontError):
|
||||
caption.Font(b"")
|
||||
with self.assertRaises(caption.FontError):
|
||||
self.font.contours(self.font.num_glyphs)
|
||||
|
||||
|
||||
class CaptionBar(unittest.TestCase):
|
||||
def test_text_is_centred_on_the_bar(self):
|
||||
font = caption.Font.load(FONT)
|
||||
width, height, rows = decode(caption.caption_bar(font, (400, 48), "solve", "static", font_size=20))
|
||||
self.assertEqual((width, height), (400, 48))
|
||||
bar = bytes(caption.BAR)
|
||||
text = [(x, y) for y, row in enumerate(rows) for x in range(width) if row[3 * x:3 * x + 3] != bar]
|
||||
xs, ys = [p[0] for p in text], [p[1] for p in text]
|
||||
self.assertLessEqual(abs((min(xs) + max(xs)) / 2 - 200), 1.5)
|
||||
self.assertLessEqual(abs((min(ys) + max(ys)) / 2 - 24), 1.5)
|
||||
self.assertIn(bytes(caption.TOOL), {row[3 * x:3 * x + 3] for row in rows for x in range(width)})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user