Spaces:
Running
Running
Signs on the film, draw tool, My search off by default, example films and Radiopaedia links (part 2)
2e6a666 verified Download frontend/src/read/stroke.test.ts from yashwrites/blindspot: direct link, hf CLI and curl.
- Browser
- Download file 3.18 kB
-
https://huggingface.co/spaces/yashwrites/blindspot/resolve/main/frontend/src/read/stroke.test.ts
- Command line
-
hf download hf://spaces/yashwrites/blindspot/frontend/src/read/stroke.test.ts
-
curl -L -o stroke.test.ts https://huggingface.co/spaces/yashwrites/blindspot/resolve/main/frontend/src/read/stroke.test.ts
3.18 kB
| import { describe, expect, it } from 'vitest'; | |
| import { finishStroke, MAX_POINTS, polygonArea, polygonCentroid, rdp, shiftPolygon, simplifyStroke, strokeExtent, type Pt } from './stroke'; | |
| const circle = (cx: number, cy: number, r: number, n: number): Pt[] => | |
| Array.from({ length: n }, (_, i) => [cx + r * Math.cos((2 * Math.PI * i) / n), cy + r * Math.sin((2 * Math.PI * i) / n)]); | |
| describe('stroke simplification', () => { | |
| it('keeps a straight line as its two ends', () => { | |
| const pts: Pt[] = Array.from({ length: 50 }, (_, i) => [i * 2, i * 2 + (i % 2) * 0.1]); | |
| expect(rdp(pts, 0.5)).toEqual([pts[0], pts[49]]); | |
| }); | |
| it('simplifies a dense trace to at most 120 points and keeps its shape', () => { | |
| const dense = circle(300, 300, 120, 2000); | |
| const out = simplifyStroke(dense); | |
| expect(out.length).toBeLessThanOrEqual(MAX_POINTS); | |
| expect(out.length).toBeGreaterThanOrEqual(12); | |
| // Area within 3 % of the true circle, centroid within a pixel. | |
| expect(Math.abs(Math.abs(polygonArea(out)) - Math.PI * 120 * 120) / (Math.PI * 120 * 120)).toBeLessThan(0.03); | |
| const [cx, cy] = polygonCentroid(out); | |
| expect(Math.abs(cx - 300)).toBeLessThan(1); | |
| expect(Math.abs(cy - 300)).toBeLessThan(1); | |
| }); | |
| it('drops duplicate points and a closing point equal to the first', () => { | |
| expect(simplifyStroke([[0, 0], [0, 0], [10, 0], [10, 10], [10, 10], [0, 10], [0, 0]])).toEqual([[0, 0], [10, 0], [10, 10], [0, 10]]); | |
| }); | |
| }); | |
| describe('polygon geometry', () => { | |
| it('computes the area centroid of a square and a triangle', () => { | |
| expect(polygonCentroid([[0, 0], [10, 0], [10, 10], [0, 10]])).toEqual([5, 5]); | |
| const [tx, ty] = polygonCentroid([[0, 0], [30, 0], [0, 30]]); | |
| expect(tx).toBeCloseTo(10); | |
| expect(ty).toBeCloseTo(10); | |
| }); | |
| it('falls back to the vertex mean for a degenerate polygon', () => { | |
| expect(polygonCentroid([[0, 0], [10, 0], [20, 0]])).toEqual([10, 0]); | |
| }); | |
| it('shifts every vertex', () => { | |
| expect(shiftPolygon([[1, 1], [2, 2]], 3, -1)).toEqual([[4, 0], [5, 1]]); | |
| }); | |
| it('measures the extent of a stroke', () => { | |
| expect(strokeExtent([[2, 3], [5, 9]])).toBe(6); | |
| expect(strokeExtent([])).toBe(0); | |
| }); | |
| }); | |
| describe('finishStroke', () => { | |
| it('turns a tiny stroke (< 8 screen px) into a point mark at its start', () => { | |
| const r = finishStroke([[100, 100], [102, 101], [103, 103]], 1); | |
| expect(r).toEqual({ kind: 'point', x: 100, y: 100 }); | |
| // The rule is in screen px: the same stroke zoomed 4× is an outline-sized gesture, but with no area it is still a point. | |
| expect(finishStroke([[100, 100], [103, 100], [106, 100]], 4)?.kind).toBe('point'); | |
| }); | |
| it('closes a traced shape into a polygon with x, y at its centroid', () => { | |
| const r = finishStroke(circle(200, 150, 40, 300), 1); | |
| expect(r?.kind).toBe('outline'); | |
| if (r?.kind !== 'outline') return; | |
| expect(r.polygon.length).toBeGreaterThanOrEqual(3); | |
| expect(r.polygon.length).toBeLessThanOrEqual(MAX_POINTS); | |
| expect(r.x).toBeCloseTo(200, 0); | |
| expect(r.y).toBeCloseTo(150, 0); | |
| }); | |
| it('returns null for no points', () => { | |
| expect(finishStroke([], 1)).toBeNull(); | |
| }); | |
| }); | |