Box & Center Annotation Studio — คู่มือผู้ใช้
Unified Box & Center Annotator · schema v1.x · browser-local SPA
🏠 เปิดแอป

1. ภาพรวมและขอบเขต

Box & Center Annotation Studio (ชื่อในสเปก: Unified Box & Center Annotator) เป็น single-page app ที่รันในเบราว์เซอร์ล้วน ๆ สำหรับ annotate ภาพด้วย bounding box และ center point / aiming guide ผลลัพธ์คือไฟล์ canonical เพียงไฟล์เดียวคือ annotations.json ที่เป็น single source of truth สำหรับ pipeline detection และ pose ปลายทาง

ไม่มี backend, ไม่มี bundler, ไม่มี runtime network dependency — ทุกอย่างทำงานในเบราว์เซอร์ ตัวแอปเองมี Content-Security-Policy ที่จำกัด origin ไว้ที่ตัวเองเท่านั้น

ข้อควรระวังทางคลินิก

เครื่องมือนี้เป็น research workflow tool ไม่ใช่ diagnostic device label ฝั่งภาพเป็นข้อมูลเชิงตำแหน่ง (positional) เว้นแต่จะได้รับการตรวจสอบยืนยันอิสระ และ dataset ต้องผ่านการ de-identify ก่อน annotate — แอปนี้ไม่ใช่เครื่องมือ de-identification และไม่ทำการตีความเชิงวินิจฉัยใด ๆ

หลักการออกแบบที่ควรรู้ก่อนใช้

หลักการความหมายในทางปฏิบัติ
Canonical single source of truthมีแค่ annotations.json ไฟล์เดียวที่ถือเป็นจริง legacy boxes.json/center.json ใช้เป็น input migrate เท่านั้น ไม่ใช่ source คู่ขนาน
Explicit over inferredสถานะ negative / not_visible / skipped เกิดจากการกระทำของผู้ใช้เท่านั้น ระบบไม่เดาให้
No silent coordinate rewriteEXIF orientation ≠ 1 หรือมิติภาพไม่ตรง จะ เตือน เสมอ ไม่หมุน/ไม่เขียนพิกัดทับเงียบ ๆ
Separate task tracksงาน box และงาน center มีสถานะ แยกกันคนละชุด ต่อภาพ

2. การเข้าใช้งาน (GitHub Pages / Local)

เนื่องจากแอปนี้เป็น Single-page application แบบ 100% Client-side จึงสามารถใช้งานได้ 2 รูปแบบหลัก:

2.1 ใช้งานผ่าน GitHub Pages (Static Hosting)

หากนำโค้ดขึ้น GitHub และเปิดใช้งาน GitHub Pages คุณสามารถเข้า URL ของโปรเจกต์ (เช่น https://<username>.github.io/<repo>/) เพื่อใช้งานได้ทันที โดย File System Access API สำหรับเซฟไฟล์ตรงเข้าเครื่องจะทำงานได้สมบูรณ์เนื่องจากทำงานบน HTTPS

2.2 ใช้งานแบบ Local (Docker)

สำหรับการรันในเครื่อง โปรเจกต์นี้ใช้ Docker ในการ serve ไฟล์ โดยไม่ต้องติดตั้ง Python หรือ Node.js (ใช้ python:3.9-slim สำหรับ serve ไฟล์ และ annotator-dev:latest สำหรับรันเทสต์)

รันแอป (serve)

รันจาก project root แล้วเปิด http://localhost:8080 — คำสั่ง copy/paste ได้เลย (PowerShell):

PowerShell — serve ที่ port 8080
docker run --rm `
  -p 8080:8080 `
  -v "${PWD}:/workspace:ro" `
  -w /workspace `
  python:3.9-slim `
  python -m http.server 8080 --bind 0.0.0.0
ต้องเปิดผ่าน http:// หรือ https:// ไม่ใช่ file://

ES modules และ File System Access API ต้องการ secure/local origin ที่สม่ำเสมอ การดับเบิลคลิกเปิด index.html แบบ file:// จะทำให้แอปไม่ทำงาน

ความหมายของแต่ละ argument

Argumentค่าในตัวอย่างความหมาย
--rm—ลบ container ทิ้งอัตโนมัติเมื่อหยุด
-p8080:8080map พอร์ต host:container (เปลี่ยนตัวซ้ายได้ถ้าพอร์ตชน)
-v${PWD}:/workspace:romount โฟลเดอร์ปัจจุบันเข้า container แบบ read-only (:ro)
-w/workspaceworking directory ภายใน container
imagepython:3.9-slimimage ที่ใช้ serve (ไม่ใช่ dependency ของแอป)
command--bind 0.0.0.0ให้ http.server รับ connection จากนอก container ได้

Build test image (ทำครั้งเดียว)

PowerShell — build annotator-dev:latest
docker build -f Dockerfile.dev -t annotator-dev:latest .

รันเทสต์

ใช้ Node built-in runner (node --test) กับ ES modules — ไม่มี bundler และไม่มี test dependency ภายนอก:

PowerShell — รันทุก suite
docker run --rm -v "${PWD}:/workspace" -w /workspace annotator-dev:latest node --test

รัน suite เดียว:

PowerShell — รัน suite เดียว
docker run --rm -v "${PWD}:/workspace" -w /workspace annotator-dev:latest node --test tests/downstream.test.js

2.3 รองรับเบราว์เซอร์ใด

ออกแบบสำหรับเบราว์เซอร์ตระกูล Chromium (Chrome / Edge) ที่รองรับ File System Access API เพื่อ save แบบ in-place เบราว์เซอร์อื่นใช้ได้ผ่าน path สำรอง folder picker + download เน้น desktop-first (≥ 1024 px) — บนจอแคบ grid จะยุบเหลือคอลัมน์เดียว รองรับ touch ผ่าน Pointer Events แต่การ annotate แม่นยำบน touch ไม่ใช่เป้าหมายของรุ่นนี้

3. เริ่มต้นใช้งานใน 5 นาที

วงจรการทำงานหลักมี 4 ขั้น: Open → Annotate → Validate → Save

เปิดโฟลเดอร์ภาพ

กด Open Folder เลือกโฟลเดอร์ที่มีภาพ .png/.jpg/.jpeg หากมี annotations.json อยู่แล้วจะถูก validate และโหลดเข้ามา ถ้าเจอ legacy boxes.json/center.json จะแสดง migration preview ก่อน merge

เลือก tool แล้ววาด

กด B เพื่อวาด box, C เพื่อวาง center, V เพื่อเลือก/แก้ ทำงานเร็ว ๆ บน grid หรือเปิด Focus เพื่อ zoom/pan แก้ละเอียด ทั้งสองมุมมองใช้ store เดียวกัน

กำหนดสถานะงาน

ตั้งสถานะ box task และ center task แยกกัน — completed เมื่อทำเสร็จ, negative/not_visible เมื่อไม่มีเป้าหมาย, skipped เมื่อขอข้าม

Validate แล้ว Save

กด Validate ตรวจทั้ง dataset → error จะ block การ save, warning save ผ่านได้หลังยืนยัน จากนั้นกด Save เพื่อเขียน annotations.json กลับเข้าโฟลเดอร์

4. หน้าจอและเครื่องมือ

4.1 Header toolbar (แถวบนสุด)

ปุ่มหน้าที่
Open Folderเปิดโฟลเดอร์ภาพ (directory handle) หรือ fallback เป็น folder picker
Importนำเข้าไฟล์ annotations เพื่อ merge (เปิด preview ก่อน)
Save primaryเขียน canonical กลับโฟลเดอร์ หรือ download fallback
Validateรัน full-dataset validation แล้วเปิดรายงาน
Autosave (checkbox)opt-in checkpoint ลง IndexedDB ในเบราว์เซอร์นี้ (default ปิด)
Toggle Themeสลับ light/dark (จำค่าใน localStorage)

4.2 Secondary toolbar (แถวรอง)

ประกอบด้วยกลุ่มควบคุม: Tool (Select/Box/Center/Pan), Class + ปุ่ม Manage Classes, Guide (โผล่เฉพาะตอนใช้ Center tool), Filter, pagination + Rows/Columns, และกลุ่ม Batch

ตัวเลือก Filter

Filterแสดงภาพที่...
Allทั้งหมด
Box pendingbox task ยัง pending
Center pendingcenter task ยัง pending
Any pendingมี task ใด task หนึ่งยัง pending
Completedเสร็จทั้งสอง task
Negative/Not visibleมีสถานะ negative หรือ not_visible
Orphanมี record แต่หาไฟล์ภาพไม่เจอ
Invalidมี validation issue
Grid layout

Rows และ Columns ปรับได้ 1–8 (default 2×2) ค่าถูกจำใน localStorage (app-grid-layout) เพดานจำนวน tile ต่อหน้าคือ MAX_GRID_TILES = 64

กลุ่ม Batch

Batch Box… / Batch Center… ตั้งสถานะ task ให้ภาพทุกใบ ที่ตรง filter ปัจจุบัน พร้อมกัน (เลือกได้เฉพาะ pending / negative หรือ not_visible / skipped — completed ตั้งแบบ batch ไม่ได้เพราะต้องมี item) ส่วน Clean Orphans ลบ record ที่หาไฟล์ภาพไม่เจอออกจาก dataset

4.3 Status bar

แถบสถานะแสดง: Folder (path), Images (จำนวนรวม), Box Progress และ Center Progress (เสร็จ/ทั้งหมด), Orphans, Issues (badge) และ badge Unsaved Changes เมื่อมีการแก้ที่ยังไม่ save

5. เปิดโฟลเดอร์ & Image identity

เมื่อเปิดโฟลเดอร์ แอปจะอ่านไฟล์นามสกุล .png, .jpg, .jpeg (case-insensitive) จากทั้งโฟลเดอร์และ subfolder ทุกชั้น

Image identity = normalized path สัมพัทธ์กับ root ของโฟลเดอร์ ดังนั้น basename เดียวกันที่อยู่คนละ subfolder จะ ไม่ชนกัน — เช่น a/img.jpg กับ b/img.jpg เป็นคนละ image_id
ทำไมใช้ relative path เป็น identity

การใช้ path สัมพัทธ์ที่ normalize แล้วทำให้ identity เป็น deterministic และพกพาข้ามเครื่องได้ โดยไม่ผูกกับ absolute path ของ host และกันการชนกันของ basename ซ้ำในต่าง subfolder ซึ่งเป็นปัญหาคลาสสิกของ dataset ที่จัดเป็นโฟลเดอร์ย่อย

ลำดับการค้นหาไฟล์ annotation

  1. ถ้าพบ annotations.json → parse + validate แล้วโหลด
  2. ถ้าไม่พบ แต่เจอ boxes.json และ/หรือ center.json → สร้าง migration preview (modal Import) ให้ยืนยันก่อน merge
  3. ภาพที่มี record แต่ไม่พบไฟล์จริงในรอบนี้ จะถูกทำเครื่องหมายเป็น orphan

6. การ Annotate

6.1 Tools

Toolคีย์หน้าที่
SelectVเลือก / ย้าย / resize / แก้ object
BoxBวาด bounding box
CenterCวาง center point พร้อม guide
PanH หรือกดค้าง Spaceเลื่อนมุมมอง

6.2 พิกัด (coordinate contract)

พิกัดเป็น integer pixel บน raster ที่ผู้ใช้เห็นจริง (oriented) origin ที่มุมซ้ายบน

  • Box = x, y, width, height (มุมซ้ายบน + ขนาดบวก) ขั้นต่ำ MIN_BOX_SIZE = 3 px และ clamp ให้อยู่ในขอบภาพ
  • Center = x, y
EXIF orientation

ภาพที่มี EXIF orientation ≠ 1 จะ เตือนเท่านั้น (WARN_EXIF_ORIENTATION) แอปไม่หมุนอัตโนมัติและไม่เขียนพิกัดทับเงียบ ๆ ส่วนกรณีมิติที่เก็บไว้กับมิติที่ decode ได้ไม่ตรงกัน จะถูกยกขึ้นมาให้ตัดสินอย่างชัดเจน (ดูหัวข้อ Dimension mismatch)

6.3 สถานะของแต่ละ task

งาน box และ center มีชุดสถานะแยกกัน และสถานะที่บ่งบอก "ไม่มี/ข้าม" ต้องมี item เป็นศูนย์เสมอ:

Taskสถานะที่ใช้ได้
Box task pending completed negative skipped
Center task pending completed not_visible skipped
กฎความสอดคล้องของสถานะ

completed ต้องมี item อย่างน้อย 1 ชิ้น ขณะที่ pending / negative / not_visible / skipped ต้องมี item เป็น 0 มิฉะนั้นจะได้ error ERR_STATUS_MISMATCH

6.4 Undo / Redo

Ctrl/Cmd+Z undo และ Ctrl/Cmd+Y redo ครอบคลุม add/move/resize/delete, เปลี่ยนสถานะ, guide, class, batch, copy/paste และ import merge (เป็น 1 command อะตอมมิก ไม่ว่าจะกระทบกี่ภาพก็ตาม) รองรับอย่างน้อย 100 ขั้น

6.5 Manage Classes & Expected Counts

ปุ่ม Manage Classes เปิด modal สำหรับ เพิ่ม/ลบ/เปลี่ยนชื่อ/จัดลำดับ class ของ box และ center และตั้ง Expected Counts Policy (min/max ของจำนวน box และ center) ค่า default คือ box {min:1, max:2} และ center {min:1, max:1} — ใช้ตรวจตอน task เป็น completed ถ้าจำนวนหลุดช่วงจะได้ ERR_EXPECTED_COUNT

ทำไม class index มาจาก id ไม่ใช่ลำดับ array

downstream อ้าง class ผ่าน label → name → id โดย id มาจาก classes.boxes[].id ไม่ใช่ตำแหน่งใน array ดังนั้นการจัดลำดับ class ใหม่ใน UI จึงไม่ทำให้ class index ของ output เพี้ยน

7. Aiming guides

center ทุกจุดเก็บ guide ไว้เสมอ crosshair/midpoint จะอยู่ตรง center.x/y เป๊ะ ขนาด guide เป็น native pixel และ ไม่เปลี่ยนตาม zoom

Guideพารามิเตอร์ที่เก็บขนาด defaultขั้นต่ำ (hit-safe)
point_crosshair———
circle_crosshairradius_pxmax(12, round(0.10 × min(w, h)))MIN_CIRCLE_RADIUS = 12
horizontal_spanspan_width_pxmax(24, round(0.25 × w))MIN_SPAN_WIDTH = 24
Guide เป็น metadata เพื่อการ construct/review เท่านั้น

endpoint ของ horizontal_span คำนวณจาก center.x ± span_width_px / 2 และ ไม่ถูก serialize guide ไม่เปลี่ยนความหมายของ center label และ ไม่กระทบ output ปลายทาง (pose ใช้แค่ center.x/y) ถ้าขนาด guide ต่ำกว่าขั้นต่ำจะได้ warning WARN_GUIDE_SIZE ไม่ใช่ error

8. Focus editor

เปิด Focus จาก tile เพื่อแก้แบบ zoom/pan ละเอียด มี loupe (แว่นขยาย) ช่วยเล็งพิกัด sidebar ขวาแบ่งเป็นสองส่วน: Workflow Actions และ Selection Details

Workflow Actions

ปุ่ม Previous Image, Next Incomplete (primary), Next Image, Copy Annotations, Paste Annotations และ dropdown ตั้งสถานะ Box Task / Center Task โดยตรง

Selection Details

เมื่อเลือก object หนึ่ง จะแก้ค่าได้ละเอียดผ่านช่อง input:

  • X, Y และ (box) W, H — พิกัด/ขนาดเป็น integer pixel
  • Class — เปลี่ยน class ของ object
  • Guide (center) — point/circle/horizontal_span พร้อมช่อง Radius (min 12) หรือ Span (min 24)
  • Link Box (center) — ผูก center เข้ากับ box ในภาพเดียวกัน (ถ้าผูกแล้ว center ต้องอยู่ในกรอบ box นั้น)

9. Import & merge

ปุ่ม Import เลือกไฟล์ JSON มา merge ระบบจะแสดง preview จำนวนภาพ/box/center, class ใหม่ที่จะถูกสร้าง และรายการภาพที่ขัดแย้ง

นโยบายแก้ conflict

นโยบายผล
keep_currentเก็บของเดิม (ข้าม conflict ที่ import เข้ามา)
use_importedใช้ของที่ import (เขียนทับ annotation เดิม)

เลือกได้ รายภาพ หรือใช้ Apply-to-all mirror ค่าลงทุกภาพพร้อมกัน การกด Cancel ต้องไม่มีผลต่อ state ที่ใช้งานอยู่

Import merge เป็น 1 command อะตอมมิก

ไม่ว่า merge จะกระทบกี่ภาพ จะนับเป็น undoable command เดียว — กด undo ครั้งเดียวย้อนทั้งชุดได้ และ merge รักษา orphan record / จัดการ duplicate ID โดยไม่ mutate object input

10. Validate & error codes

Validate ตรวจทั้ง dataset (root, class, path, asset, expected-count, geometry, status ฯลฯ) ผลแบ่งเป็น 2 ระดับ: error และ warning คลิกแต่ละ issue ในรายงานเพื่อกระโดดไปยังภาพที่เกี่ยวข้องได้

error = block การ save แบบ canonical ทั้งหมด ต้องแก้ก่อน  ·  warning = save ผ่านได้ แต่ต้องกดยืนยันรับทราบก่อน

10.1 Error codes (block save)

Codeความหมาย
ERR_STRUCTURALโครงสร้าง/ชนิดข้อมูล/timestamp/metadata contract ไม่ถูกต้อง
ERR_DUPLICATE_IDannotation id ซ้ำ หรือ key ของ image map ไม่ตรง image_id
ERR_INVALID_GEOMETRYพิกัด/ขนาดไม่ใช่ finite integer หรือ width/height ไม่เป็นบวก
ERR_GEOMETRY_BOUNDSbox หรือ center หลุดขอบภาพ
ERR_STATUS_MISMATCHcompleted แต่ไม่มี item / สถานะ "ไม่มี" แต่กลับมี item
ERR_INVALID_ASSOCIATIONbox_id ผิดชนิด, ชี้ box ที่ไม่มี, หรือ center อยู่นอก box ที่ผูก
ERR_INVALID_GUIDEguide ไม่มี type, point_crosshair มีพารามิเตอร์ขนาด, หรือ size ไม่ใช่ integer บวก
ERR_EXPECTED_COUNTจำนวน box/center หลุดช่วง expected counts ตอนสถานะ completed
ERR_MISSING_COORDINATEพิกัดที่จำเป็นหายไป
ERR_CLASS_MISMATCHlabel ไม่ตรงกับ class ที่นิยามไว้
ERR_CLASS_DEFINITIONนิยาม class ผิด (id/name ซ้ำ, ชนิดผิด, หรือ counter ≤ id สูงสุด)
ERR_INVALID_PATHrelative_path / image map key ไม่ใช่ normalized relative path หรือ file_name ไม่ตรง basename
ERR_DIMENSION_MISMATCHมิติที่เก็บไว้กับมิติที่ decode ได้ต่างกัน
ERR_DECODE_FAILUREdecode ภาพไม่สำเร็จ

10.2 Warning codes (save ผ่านได้หลังยืนยัน)

Codeความหมาย
WARN_UNKNOWN_GUIDEguide type ไม่รู้จัก (นอกเหนือ 3 ชนิดมาตรฐาน)
WARN_GUIDE_SIZEขนาด guide ต่ำกว่าขั้นต่ำ hit-safe ของ UI
WARN_GUIDE_BOUNDSguide ยื่นเลยขอบภาพ
WARN_ORPHAN_RECORDมี record แต่หาไฟล์ภาพไม่เจอ
WARN_MISSING_RECORDมีไฟล์ภาพแต่ไม่มี canonical record
WARN_EXIF_ORIENTATIONEXIF orientation ≠ 1 (พิกัดไม่ถูกหมุน)

11. Save / Download / Autosave

11.1 ลำดับการ Save

  1. ถ้ามี error > 0 → block และเปิดรายงาน validation
  2. ถ้ามีแต่ warning → ถามยืนยัน "Save anyway?" ก่อน
  3. ถ้ามี writable directory handle → เขียน annotations.json กลับโฟลเดอร์
  4. ถ้าไม่มี handle → fallback เป็น Blob download
Dirty state ล้างเมื่อเขียนสำเร็จเท่านั้น

สถานะ Unsaved Changes จะหายก็ต่อเมื่อเขียน canonical ลงดิสก์สำเร็จจริง ๆ การ download เป็นแค่ checkpoint — ไม่ยืนยันว่าไฟล์ไปถึงโฟลเดอร์ dataset แล้ว ระบบจึงคง dirty state ไว้โดยตั้งใจ (และถ้าเขียน in-place ล้มเหลว จะ fallback เป็น download โดยไม่ทิ้งการแก้)

11.2 Autosave & Recovery

Autosave เป็น opt-in (default ปิด) checkpoint metadata ของ annotation ลง IndexedDB ผูกกับ identity ของ dataset (hash ของ dataset.name + dataset.created_at) เมื่อเปิด dataset เดิม อีกครั้งและพบ checkpoint ที่ใหม่กว่าไฟล์ที่เพิ่งโหลด จะมี dialog ให้เลือก:

ตัวเลือกผล
Recoverเอา checkpoint มาแทนที่ annotation ที่โหลด (ไม่เขียนไฟล์ ต้อง Save เองภายหลัง)
Keep loaded fileคงไฟล์ที่โหลดไว้ ไม่แตะ checkpoint
Discard checkpointลบ checkpoint ทิ้ง
Autosave คือ safety net ไม่ใช่ตัวแทน Save

Autosave เก็บใน IndexedDB ของเบราว์เซอร์นี้เครื่องนี้เท่านั้น และ ไม่เก็บ image bytes อย่าใช้แทนการ Save จริงลงไฟล์ canonical

12. Dimension mismatch

ถ้าภาพที่โหลดมีมิติจริง (decoded) ไม่ตรงกับมิติที่เก็บไว้ใน annotation (และ task ไม่ใช่ pending) แอปจะเปิด dialog ให้เลือกวิธีแก้อย่างชัดเจน 3 ทาง:

ปุ่มผล
Rescale Bounding Boxes primaryปรับ scale พิกัด box ให้เข้ากับมิติใหม่
Replace Image Assetใช้ไฟล์ภาพใหม่แทน (คงพิกัดเดิม)
Remove Annotation dangerลบ record ของภาพนี้ออก
ทำไมต้องให้ผู้ใช้ตัดสิน

มิติไม่ตรงอาจมาจากภาพถูกแทนที่, ถูก resize, หรือ EXIF orientation — แต่ละกรณีมีวิธีแก้ที่ถูกต้องต่างกัน แอปจึงไม่เดา rescale เงียบ ๆ แต่ยกขึ้นมาเป็นการตัดสิน rescale / replace / remove อย่างเปิดเผย สอดคล้องกับหลัก "no silent coordinate rewrite"

13. Keyboard shortcuts

หมายเหตุ

shortcut จะถูกเพิกเฉยเมื่อ focus อยู่ในช่อง INPUT / SELECT / TEXTAREA (เพื่อไม่ขัดการพิมพ์)

คีย์การทำงาน
V / B / C / Hเลือก tool Select / Box / Center / Pan
กดค้าง Spacepan ชั่วคราว
Ctrl/Cmd+Zundo
Ctrl/Cmd+Y หรือ Ctrl/Cmd+Shift+Zredo
Ctrl/Cmd+Ccopy annotation ของภาพที่ active
Ctrl/Cmd+Vpaste annotation ไปยังภาพที่ active
[ / ]ภาพก่อนหน้า / ถัดไป (ตาม filter)
Alt+Nไปภาพ incomplete ถัดไป
Arrownudge object ที่เลือก 1 px
Shift+Arrownudge object ที่เลือก 10 px
Delete / Backspaceลบ object ที่เลือก
Escยกเลิกการเลือก object หรือปิด Focus editor

14. Downstream output (YOLO)

โมดูล js/downstream.js อ่าน canonical file โดยตรงเพื่อแปลงเป็น label YOLO (ไม่ใช่ปุ่มใน UI แต่เป็นส่วนของ contract สำหรับสาย data/ML) ค่าทุกตัว normalize ด้วยมิติภาพ ที่ precision 6 ตำแหน่ง

14.1 Detection

convertCanonicalToDetection — emit 1 บรรทัดต่อ box รูปแบบ "<class_id> <xc> <yc> <w> <h>" เฉพาะภาพที่ box task เป็น completed หรือ negative โดยภาพ negative ได้ label set ว่าง (ไม่มี box) class index มาจาก classes.boxes[].id ผ่าน label → name → id

14.2 Pose

convertCanonicalToPose — emit center เป็น keypoint เดียวบน full-image box รูปแบบ "<class_id> 0.5 0.5 1 1 <kx> <ky> 2" เฉพาะ center task ที่ completed

Pose ต้องมี center พอดี 1 จุด

ภาพที่ center task เป็น completed แต่มีจำนวน center ≠ 1 จะถูก reject พร้อม error ชัดเจน output ใช้แค่ center.x/y — guide type/พารามิเตอร์ไม่มีผลต่อผลลัพธ์

หมายเหตุ: adapter ทรง legacy (convertLegacyBoxesToDetection, convertLegacyCenterToPose) มีไว้เพื่อ regression parity เท่านั้น legacy file ไม่เคยเป็น source คู่ขนาน

15. โครงสร้าง annotations.json

ไฟล์ถูก serialize แบบ deterministic (natural sort, indent 2 ช่อง, timestamp เป็น UTC ...Z) ทำให้ parse แล้ว re-serialize ได้ผลเหมือนเดิมแบบ byte-identical ตัวอย่างย่อ (จาก fixture จริง):

annotations.json (ตัวอย่างย่อ)
{
  "schema_version": "1.0.0",
  "app": { "name": "box-center-annotator", "version": "0.1.0" },
  "dataset": {
    "name": "test",
    "center_guide_default": "point_crosshair",
    "expected_counts": { "boxes": { "min": 1, "max": 2 }, "centers": { "min": 1, "max": 3 } },
    "created_at": "2026-06-27T10:00:00Z",
    "updated_at": "2026-06-27T10:00:00Z",
    "coordinate_frame": "oriented_image_pixels",
    "orientation_policy": "browser_from_image"
  },
  "classes": {
    "boxes":   [ { "id": 0, "name": "Target" } ],
    "centers": [ { "id": 0, "name": "Center" } ]
  },
  "images": {
    "completed.jpg": {
      "image_id": "completed.jpg",
      "file_name": "completed.jpg",
      "relative_path": "completed.jpg",
      "image_width": 100,
      "image_height": 100,
      "box_task": {
        "status": "completed",
        "items": [ { "id": "box_1", "label": "Target", "x": 10, "y": 10, "width": 20, "height": 20 } ],
        "last_updated": "2026-06-27T10:00:00Z"
      },
      "center_task": {
        "status": "completed",
        "items": [ {
          "id": "center_1", "label": "Center", "x": 20, "y": 20,
          "box_id": "box_1", "guide": { "type": "point_crosshair" }
        } ],
        "last_updated": "2026-06-27T10:00:00Z"
      }
    }
  }
}
Contract ที่ตายตัว

coordinate_frame ต้องเป็น "oriented_image_pixels" และ orientation_policy ต้องเป็น "browser_from_image" เสมอ ส่วน class_id_counters ต้องมากกว่า id ของทุก class ที่ active — มิฉะนั้นจะได้ ERR_STRUCTURAL / ERR_CLASS_DEFINITION

16. Privacy & data boundary

  • ไม่มี analytics, telemetry, remote font, CDN หรือ external API — CSP meta tag จำกัด origin ไว้ที่ตัวแอป
  • ภาพอยู่ใน memory ของเบราว์เซอร์และ ไม่เคยถูกฝังลงใน annotations.json
  • localStorage เก็บเฉพาะ UI preference เท่านั้น: app-theme, app-grid-layout, app-autosave-enabled — ไม่มีชื่อไฟล์/พิกัด/ข้อมูลภาพ
  • Autosave (opt-in) เก็บ metadata (รวมชื่อไฟล์และพิกัด) ใน IndexedDB เท่านั้น ผูกกับ dataset identity ไม่มี image bytes / object URL / file handle และมีคำสั่งล้างได้
เตือนซ้ำเรื่อง de-identification

แอปนี้ ไม่ทำ de-identification — dataset ต้องถูก de-identify ก่อนนำเข้ามา annotate เสมอ

17. ค่า default ของระบบ

สกัดจาก js/config.js และ js/geometry.js:

ค่าDefaultความหมาย
SUPPORTED_EXTENSIONSjpg, jpeg, pngนามสกุลภาพที่อ่าน (case-insensitive)
MIN_BOX_SIZE3ขนาด box ขั้นต่ำ (px)
HIT_SLOP_DISPLAY_PX6ระยะ tolerance ของการคลิกโดน object
DEFAULT_GRID_ROWS × COLS2 × 2เลย์เอาต์ grid เริ่มต้น
MAX_GRID_TILES64เพดาน tile ต่อหน้า
DEFAULT_EXPECTED_COUNTSbox {1,2}, center {1,1}ช่วงจำนวนที่คาดหวังตอน completed
MIN_GUIDE_SIZE12ขนาด guide ขั้นต่ำทั่วไป
MIN_CIRCLE_RADIUS12รัศมีขั้นต่ำของ circle_crosshair
MIN_SPAN_WIDTH24ความกว้างขั้นต่ำของ horizontal_span

18. ข้อจำกัดที่ทราบ

  • การ save in-place ต้องใช้เบราว์เซอร์ Chromium ที่รองรับ File System Access API — ที่อื่นต้องใช้ download fallback แล้วย้ายไฟล์เข้าโฟลเดอร์ dataset เอง
  • การ download สำเร็จไม่ได้ยืนยันว่าไฟล์ถูกวางถูกที่ — dirty state คงอยู่จนกว่าจะเขียน canonical จริงสำเร็จ
  • การ annotate แม่นยำบน touch อยู่นอกขอบเขตของรุ่นนี้
  • แอปไม่ทำ de-identification และไม่ตีความเชิงคลินิก/วินิจฉัยใด ๆ

19. Glossary

คำความหมาย
Canonical fileannotations.json ไฟล์เดียวที่เป็น single source of truth
Image identity / image_idnormalized path สัมพัทธ์กับ root ของโฟลเดอร์ ใช้เป็นกุญแจระบุภาพ
Orphan recordrecord ที่มีอยู่ใน dataset แต่หาไฟล์ภาพจริงไม่เจอในรอบโหลดนี้
Aiming guidemetadata รูปทรงช่วยเล็ง (point/circle/horizontal_span) ที่ผูกกับ center จุดหนึ่ง
Taskหน่วยงาน annotation ต่อภาพ มี 2 track แยกกัน: box_task และ center_task
Directory handleobject จาก File System Access API ที่ให้สิทธิ์เขียนไฟล์กลับโฟลเดอร์โดยตรง
Dirty stateสถานะที่มีการแก้ค้างยังไม่ถูกเขียนลง canonical (แสดงเป็น Unsaved Changes)
Checkpointสำเนา metadata จาก autosave (IndexedDB) หรือไฟล์ download ที่ยังไม่ยืนยันตำแหน่ง
Migrationการแปลง legacy boxes.json/center.json เป็น canonical schema
Detection / Pose labeloutput ทรง YOLO ที่ downstream.js สร้างจาก canonical file