Skip to content

Instantly share code, notes, and snippets.

@Xnuvers007
Last active March 26, 2026 21:53
Show Gist options
  • Select an option

  • Save Xnuvers007/2bed5b61b0a126504517b8569c420b53 to your computer and use it in GitHub Desktop.

Select an option

Save Xnuvers007/2bed5b61b0a126504517b8569c420b53 to your computer and use it in GitHub Desktop.
AI Self Driving
import cv2, json, os, copy, time, warnings, zipfile
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.gridspec import GridSpec
from PIL import Image, ImageDraw, ImageFont
from IPython.display import display, clear_output, HTML
import ipywidgets as widgets
from google.colab import files
from tqdm.notebook import tqdm
from ultralytics import YOLO
import glob
warnings.filterwarnings("ignore")
matplotlib.rcParams["font.family"] = "DejaVu Sans"
# ══════════════════════════════════════════════════════════════════
# PALETTE & LABEL MASTER
# ══════════════════════════════════════════════════════════════════
CATEGORY_META = {
"Kendaraan": {
"color": "#FF6B35", "icon": "[V]",
"labels": [
"mobil", "sepeda_motor", "bus", "truk", "sepeda",
"van", "minibus", "kendaraan_parkir",
"ambulans", "mobil_polisi", "mobil_pemadam",
],
},
"Manusia": {
"color": "#4ECDC4", "icon": "[P]",
"labels": [
"pejalan_kaki", "orang_menyeberang", "pengendara_sepeda",
"pengendara_motor", "orang_di_trotoar",
],
},
"Rambu & Lampu": {
"color": "#FFE66D", "icon": "[S]",
"labels": [
"rambu_lalu_lintas", "lampu_lalu_lintas", "rambu_stop",
"rambu_batas_kecepatan", "rambu_belok",
"lampu_merah", "lampu_kuning", "lampu_hijau",
],
},
"Struktur Jalan": {
"color": "#A8E6CF", "icon": "[R]",
"labels": [
"marka_jalan", "zebra_cross", "trotoar", "median_jalan",
"bahu_jalan", "persimpangan", "jalur_belok",
],
},
"Objek Sekitar": {
"color": "#C3B1E1", "icon": "[E]",
"labels": [
"pohon", "tiang_listrik", "tiang_lampu", "pagar",
"gedung", "halte_bus", "papan_iklan",
],
},
"Penghalang": {
"color": "#FF8B94", "icon": "[O]",
"labels": [
"kerucut_lalu_lintas", "barikade", "kendaraan_rusak",
"sampah_besar", "batu", "lubang_jalan",
],
},
"Permukaan Jalan": {
"color": "#85C1E9", "icon": "[G]",
"labels": [
"jalan_aspal", "trotoar_permukaan", "rumput",
"tanah", "area_parkir",
],
},
}
CATEGORY_COLORS = {k: v["color"] for k, v in CATEGORY_META.items()}
CATEGORY_LABELS = {k: v["labels"] for k, v in CATEGORY_META.items()}
ALL_LABELS = [lbl for meta in CATEGORY_META.values() for lbl in meta["labels"]]
LABEL2ID = {lbl: i for i, lbl in enumerate(ALL_LABELS)}
LABEL2CAT = {lbl: cat
for cat, meta in CATEGORY_META.items()
for lbl in meta["labels"]}
# ══════════════════════════════════════════════════════════════════
# MAPPING YOLO COCO → LABEL KUSTOM (lengkap 80 kelas COCO)
# ══════════════════════════════════════════════════════════════════
YOLO_MAP = {
# Kendaraan
"car": ("Kendaraan", "mobil"),
"motorcycle": ("Kendaraan", "sepeda_motor"),
"motorbike": ("Kendaraan", "sepeda_motor"),
"bus": ("Kendaraan", "bus"),
"truck": ("Kendaraan", "truk"),
"bicycle": ("Kendaraan", "sepeda"),
"van": ("Kendaraan", "van"),
"ambulance": ("Kendaraan", "ambulans"),
"fire truck": ("Kendaraan", "mobil_pemadam"),
"police car": ("Kendaraan", "mobil_polisi"),
"train": ("Kendaraan", "truk"),
"boat": ("Kendaraan", "kendaraan_parkir"),
# Manusia
"person": ("Manusia", "pejalan_kaki"),
"people": ("Manusia", "pejalan_kaki"),
# Rambu & Lampu
"traffic light": ("Rambu & Lampu", "lampu_lalu_lintas"),
"stop sign": ("Rambu & Lampu", "rambu_stop"),
"parking meter": ("Rambu & Lampu", "rambu_lalu_lintas"),
# Objek Sekitar
"bench": ("Objek Sekitar", "halte_bus"),
"potted plant": ("Objek Sekitar", "pohon"),
"tree": ("Objek Sekitar", "pohon"),
"fire hydrant": ("Objek Sekitar", "tiang_listrik"),
"umbrella": ("Objek Sekitar", "pagar"),
# Penghalang
"suitcase": ("Penghalang", "sampah_besar"),
"backpack": ("Penghalang", "sampah_besar"),
"sports ball": ("Penghalang", "batu"),
"bottle": ("Penghalang", "sampah_besar"),
"billboard": ("Objek Sekitar", "papan_iklan"),
}
# ══════════════════════════════════════════════════════════════════
# YOLOE TEXT PROMPTS: kelas yang ingin dideteksi secara eksplisit
# ══════════════════════════════════════════════════════════════════
YOLOE_PROMPTS = [
"person", "pedestrian", "motorcycle", "motorbike", "scooter",
"car", "sedan", "SUV", "pickup truck", "van", "minibus",
"bus", "truck", "ambulance", "police car", "fire truck",
"bicycle", "traffic light", "stop sign", "speed limit sign",
"road sign", "cone", "barrier", "pothole", "billboard"
]
# ══════════════════════════════════════════════════════════════════
# CELL 3 MODEL MANAGER (YOLO26 + YOLOE + SEG + POSE)
# ══════════════════════════════════════════════════════════════════
class ModelManager:
DETECT_MODELS = {
"YOLO26n — Nano (cepat, CPU ok)": "yolo26n.pt",
"YOLO26s — Small (seimbang)": "yolo26s.pt",
"YOLO26m — Medium (akurat)": "yolo26m.pt",
"YOLO26l — Large (sangat akurat)": "yolo26l.pt",
"YOLO26x — XLarge (maksimal)": "yolo26x.pt",
}
SEG_MODELS = {
"YOLO26l-seg — Segmentation Large": "yolo26l-seg.pt",
"YOLO26m-seg — Segmentation Medium": "yolo26m-seg.pt",
}
POSE_MODELS = {
"YOLO26l-pose — Pose Estimation": "yolo26l-pose.pt",
}
YOLOE_MODELS = {
"YOLOE-26l-seg — Text Prompt (akurat)": "yoloe-26l-seg.pt",
"YOLOE-26m-seg — Text Prompt (medium)": "yoloe-26m-seg.pt",
}
def __init__(self):
self.det_model = None
self.seg_model = None
self.pose_model = None
self.loaded = {} # {name: model}
# ── Load satu atau beberapa model sekaligus ──────────────────
def load(self, det_key: str, use_seg=False, use_pose=False,
use_yoloe=False, yoloe_key: str = None):
print("=" * 55)
# Detection model
pt = self.DETECT_MODELS.get(det_key, "yolo26l.pt")
print(f"[*] Loading detection: {pt}")
self.det_model = YOLO(pt)
print(f" OK — {len(self.det_model.names)} kelas COCO")
# Segmentation model
if use_seg:
seg_key = list(self.SEG_MODELS.keys())[0]
seg_pt = list(self.SEG_MODELS.values())[0]
print(f"[*] Loading segmentation: {seg_pt}")
self.seg_model = YOLO(seg_pt)
print(f" OK — instance segmentation siap")
# Pose model
if use_pose:
pose_pt = "yolo26l-pose.pt"
print(f"[*] Loading pose: {pose_pt}")
self.pose_model = YOLO(pose_pt)
print(f" OK — pose estimation siap")
# YOLOE text-prompt model
if use_yoloe and yoloe_key:
yoloe_pt = self.YOLOE_MODELS.get(yoloe_key,
list(self.YOLOE_MODELS.values())[0])
print(f"[*] Loading YOLOE text-prompt: {yoloe_pt}")
self.yoloe_model = YOLO(yoloe_pt)
self.yoloe_model.set_classes(YOLOE_PROMPTS)
print(f" OK — {len(YOLOE_PROMPTS)} prompt kelas set")
else:
self.yoloe_model = None
print("=" * 55 + "\n")
# ── Deteksi Utama ────────────────────────────────────────────
def detect_full(
self,
img_rgb : np.ndarray,
conf : float = 0.25,
iou : float = 0.45,
use_sahi : bool = False,
sahi_size : int = 640,
use_seg : bool = False,
use_pose : bool = False,
use_yoloe : bool = False,
end2end : bool = True, # YOLO26 dual-head: True = no NMS
) -> dict:
"""
Jalankan semua model aktif, kembalikan dict berisi:
detections : list annotation standar
masks : list np.ndarray (opsional, dari seg)
keypoints : list (opsional, dari pose)
yoloe_dets : list annotation dari YOLOE prompt
"""
result = {"detections": [], "masks": [], "keypoints": [],
"yoloe_dets": []}
# 1. Detection (YOLO26)
if self.det_model:
if use_sahi:
dets = self._detect_sahi(self.det_model, img_rgb,
conf, iou, sahi_size)
else:
dets = self._detect_standard(self.det_model, img_rgb,
conf, iou, end2end)
result["detections"] = dets
# 2. Segmentation
if use_seg and self.seg_model:
seg_out = self._detect_seg(img_rgb, conf, iou)
result["masks"] = seg_out["masks"]
# merge seg detections (dedup dengan IoU)
result["detections"] = self._merge_and_dedup(
result["detections"], seg_out["dets"], iou)
# 3. Pose
if use_pose and self.pose_model:
result["keypoints"] = self._detect_pose(img_rgb, conf)
# 4. YOLOE Text Prompt
if use_yoloe and self.yoloe_model:
yoloe_raw = self.yoloe_model.predict(
img_rgb, conf=conf, iou=iou, verbose=False)[0]
yoloe_dets = []
for box in yoloe_raw.boxes:
cls_name = self.yoloe_model.names[int(box.cls)].lower()
cat, lbl = YOLO_MAP.get(cls_name,
("Objek Sekitar", cls_name.replace(" ","_")))
x1,y1,x2,y2 = map(int, box.xyxy[0].tolist())
yoloe_dets.append({
"category": cat, "label": lbl,
"bbox": [x1,y1,x2,y2],
"confidence": round(float(box.conf[0]),3),
"source": "yoloe",
"yolo_class": cls_name,
})
result["yoloe_dets"] = yoloe_dets
# merge ke detections utama
result["detections"] = self._merge_and_dedup(
result["detections"], yoloe_dets, iou)
result["detections"] = self._refine_pedestrians(result["detections"])
return result
# ── Standard inference ───────────────────────────────────────
def _detect_standard(self, model, img_rgb, conf, iou, end2end=True):
kw = {} if end2end else {"end2end": False}
results = model(img_rgb, conf=conf, iou=iou,
verbose=False, **kw)[0]
return self._parse_boxes(model, results.boxes)
# ── Segmentation ─────────────────────────────────────────────
def _detect_seg(self, img_rgb, conf, iou):
results = self.seg_model(img_rgb, conf=conf, iou=iou,
verbose=False)[0]
dets, masks_out = [], []
if results.masks is not None:
for i, (box, mask) in enumerate(
zip(results.boxes, results.masks.data)):
cls_name = self.seg_model.names[int(box.cls)].lower()
cat, lbl = YOLO_MAP.get(cls_name,
("Objek Sekitar", cls_name.replace(" ","_")))
x1,y1,x2,y2 = map(int, box.xyxy[0].tolist())
dets.append({
"category": cat, "label": lbl,
"bbox": [x1,y1,x2,y2],
"confidence": round(float(box.conf[0]),3),
"source": "seg",
"yolo_class": cls_name,
"mask_idx": i,
})
m = mask.cpu().numpy()
# resize mask ke ukuran gambar asli
H, W = img_rgb.shape[:2]
m_resized = cv2.resize(m.astype(np.uint8),
(W, H),
interpolation=cv2.INTER_NEAREST)
masks_out.append(m_resized)
else:
dets = self._parse_boxes(self.seg_model, results.boxes)
return {"dets": dets, "masks": masks_out}
# ── Pose Estimation ──────────────────────────────────────────
def _detect_pose(self, img_rgb, conf):
results = self.pose_model(img_rgb, conf=conf, verbose=False)[0]
poses = []
if results.keypoints is not None:
for i, (box, kp) in enumerate(
zip(results.boxes, results.keypoints.data)):
x1,y1,x2,y2 = map(int, box.xyxy[0].tolist())
poses.append({
"bbox": [x1,y1,x2,y2],
"confidence": round(float(box.conf[0]),3),
"keypoints": kp.cpu().numpy().tolist(),
})
return poses
# ── SAHI Slicing ─────────────────────────────────────────────
def _detect_sahi(self, model, img_rgb, conf, iou, slice_size):
H, W = img_rgb.shape[:2]
overlap = slice_size // 4
stride = slice_size - overlap
raw = []
tile_count = 0
for y0 in range(0, H, stride):
for x0 in range(0, W, stride):
x1_ = min(x0+slice_size, W)
y1_ = min(y0+slice_size, H)
tile = img_rgb[y0:y1_, x0:x1_]
res = model(tile, conf=conf, iou=iou, verbose=False)[0]
tile_count += 1
for box in res.boxes:
bx1,by1,bx2,by2 = map(int, box.xyxy[0].tolist())
raw.append({
"cls": model.names[int(box.cls)].lower(),
"conf": float(box.conf[0]),
"bbox": [bx1+x0, by1+y0, bx2+x0, by2+y0],
})
print(f" SAHI: {tile_count} tiles -> {len(raw)} raw detections")
deduped = self._nms_raw(raw, iou)
print(f" After NMS: {len(deduped)} detections")
return self._convert_raw(deduped)
# ── NMS & Convert ────────────────────────────────────────────
@staticmethod
def _nms_raw(boxes, iou_thresh=0.45):
if not boxes: return []
boxes = sorted(boxes, key=lambda x: -x["conf"])
kept = []
for b in boxes:
if all(ModelManager._iou(b["bbox"],k["bbox"]) < iou_thresh
for k in kept):
kept.append(b)
return kept
@staticmethod
def _iou(a, b):
ix1=max(a[0],b[0]); iy1=max(a[1],b[1])
ix2=min(a[2],b[2]); iy2=min(a[3],b[3])
inter=max(0,ix2-ix1)*max(0,iy2-iy1)
ua=(a[2]-a[0])*(a[3]-a[1])+(b[2]-b[0])*(b[3]-b[1])-inter
return inter/ua if ua>0 else 0
@staticmethod
def _convert_raw(boxes):
dets = []
for b in boxes:
cat, lbl = YOLO_MAP.get(b["cls"],
("Objek Sekitar", b["cls"].replace(" ","_")))
dets.append({
"category": cat, "label": lbl,
"bbox": b["bbox"],
"confidence": round(b["conf"],3),
"source": "auto",
"yolo_class": b["cls"],
})
return dets
@staticmethod
def _parse_boxes(model, boxes_tensor):
dets = []
for box in boxes_tensor:
cls_name = model.names[int(box.cls)].lower()
cat, lbl = YOLO_MAP.get(cls_name,
("Objek Sekitar", cls_name.replace(" ","_")))
x1,y1,x2,y2 = map(int, box.xyxy[0].tolist())
dets.append({
"category": cat, "label": lbl,
"bbox": [x1,y1,x2,y2],
"confidence": round(float(box.conf[0]),3),
"source": "auto",
"yolo_class": cls_name,
})
return dets
@staticmethod
def _merge_and_dedup(base, extra, iou_thresh=0.5):
merged = list(base)
for e in extra:
duplicate = any(
ModelManager._iou(e["bbox"], b["bbox"]) > iou_thresh
and e["label"] == b["label"]
for b in merged
)
if not duplicate:
merged.append(e)
return merged
@staticmethod
def _refine_pedestrians(dets):
people = [d for d in dets if d["label"] == "pejalan_kaki"]
bikes = [d for d in dets if d["label"] in ["sepeda_motor", "sepeda"]]
for p in people:
for b in bikes:
px1, py1, px2, py2 = p["bbox"]
bx1, by1, bx2, by2 = b["bbox"]
# Ambil titik tengah bagian bawah orang (asumsi ini posisi kaki/pinggul)
p_bottom_cx = (px1 + px2) / 2
p_bottom_cy = py2
# Cek apakah kaki orang berada sejajar/di dalam batas horizontal motor
is_inside_x = bx1 <= p_bottom_cx <= bx2
# Cek apakah kaki orang berada di area ketinggian motor
# (toleransi tambahan 50 pixel ke bawah kalau kakinya menjuntai)
is_inside_y = by1 <= p_bottom_cy <= (by2 + 50)
# Hitung sedikit irisan (intersection) untuk jaga-jaga
ix1 = max(px1, bx1); iy1 = max(py1, by1)
ix2 = min(px2, bx2); iy2 = min(py2, by2)
inter_area = max(0, ix2 - ix1) * max(0, iy2 - iy1)
p_area = max(1, (px2 - px1) * (py2 - py1))
# Jika kaki orang masuk di area motor ATAU ada irisan area > 5%, ubah labelnya!
if (is_inside_x and is_inside_y) or (inter_area / p_area > 0.05):
p["label"] = "pengendara_motor" if b["label"] == "sepeda_motor" else "pengendara_sepeda"
p["category"] = "Manusia"
break # Selesai, lanjut ke orang berikutnya
return dets
model_manager = ModelManager()
# ══════════════════════════════════════════════════════════════════
# CELL 4 ANNOTATION MANAGER
# ══════════════════════════════════════════════════════════════════
class AnnotationManager:
def __init__(self):
self.data : dict = {} # {fname: {annotations:[], masks:[], kps:[]}}
self.images: dict = {} # {fname: np.ndarray RGB}
def add_image(self, fname, img_rgb):
if fname not in self.data:
self.data[fname] = {
"image_path": fname,
"width": img_rgb.shape[1],
"height": img_rgb.shape[0],
"annotations": [],
"masks": [],
"keypoints": [],
}
self.images[fname] = img_rgb
def add_annotations(self, fname, anns, masks=None, keypoints=None):
self.data[fname]["annotations"].extend(anns)
if masks:
self.data[fname]["masks"].extend(masks)
if keypoints:
self.data[fname]["keypoints"].extend(keypoints)
def add_one(self, fname, ann):
self.data[fname]["annotations"].append(ann)
def remove(self, fname, idx):
a = self.data[fname]["annotations"]
if 0 <= idx < len(a):
a.pop(idx)
def clear_auto(self, fname):
self.data[fname]["annotations"] = [
a for a in self.data[fname]["annotations"]
if a.get("source") == "manual"]
self.data[fname]["masks"] = []
self.data[fname]["keypoints"] = []
def get_anns(self, fname):
return self.data.get(fname, {}).get("annotations", [])
def get_masks(self, fname):
return self.data.get(fname, {}).get("masks", [])
def get_poses(self, fname):
return self.data.get(fname, {}).get("keypoints", [])
def total(self):
return sum(len(v["annotations"]) for v in self.data.values())
# ── Export JSON ──────────────────────────────────────────────
def export_json(self, path="annotations.json"):
out = []
for fname, info in self.data.items():
for ann in info["annotations"]:
row = {k: v for k, v in ann.items()
if k not in ("mask_idx", "yolo_class")}
row["image"] = fname
out.append(row)
with open(path, "w") as f:
json.dump(out, f, indent=2, ensure_ascii=False)
print(f"[JSON ] -> {path} ({len(out)} objek)")
return path
# ── Export YOLO TXT ──────────────────────────────────────────
def export_yolo(self, out_dir="yolo_dataset"):
img_dir = os.path.join(out_dir, "images")
lbl_dir = os.path.join(out_dir, "labels")
os.makedirs(img_dir, exist_ok=True)
os.makedirs(lbl_dir, exist_ok=True)
# classes.txt
with open(os.path.join(out_dir,"classes.txt"),"w") as f:
f.write("\n".join(ALL_LABELS))
# data.yaml
with open(os.path.join(out_dir,"data.yaml"),"w") as f:
f.write(f"path: {os.path.abspath(out_dir)}\n")
f.write("train: images\nval: images\n")
f.write(f"nc: {len(ALL_LABELS)}\n")
f.write(f"names: {ALL_LABELS}\n")
count = 0
for fname, info in self.data.items():
W, H = info.get("width",1), info.get("height",1)
base = os.path.splitext(os.path.basename(fname))[0]
# salin gambar
if os.path.exists(fname):
import shutil
shutil.copy(fname, os.path.join(img_dir, os.path.basename(fname)))
# label txt
with open(os.path.join(lbl_dir, base+".txt"),"w") as f:
for ann in info["annotations"]:
x1,y1,x2,y2 = ann["bbox"]
cx=(x1+x2)/(2*W); cy=(y1+y2)/(2*H)
bw=(x2-x1)/W; bh=(y2-y1)/H
lid = LABEL2ID.get(ann["label"],0)
f.write(f"{lid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")
count += 1
print(f"[YOLO ] -> {out_dir}/ ({count} anotasi, {len(ALL_LABELS)} kelas)")
return out_dir
# ── Export COCO JSON ─────────────────────────────────────────
def export_coco(self, path="coco_annotations.json"):
categories = [{"id": i+1, "name": l, "supercategory":
LABEL2CAT.get(l,"object")}
for i,l in enumerate(ALL_LABELS)]
label2id = {l: i+1 for i,l in enumerate(ALL_LABELS)}
images_list, anns_list = [], []
ann_id = 1
for img_id, (fname, info) in enumerate(self.data.items(), 1):
W = info.get("width",0); H = info.get("height",0)
images_list.append({
"id": img_id, "file_name": os.path.basename(fname),
"width": W, "height": H
})
for ann in info["annotations"]:
x1,y1,x2,y2 = ann["bbox"]
w,h = x2-x1, y2-y1
anns_list.append({
"id": ann_id, "image_id": img_id,
"category_id": label2id.get(ann["label"],1),
"bbox": [x1,y1,w,h], "area": w*h,
"iscrowd": 0,
"score": ann.get("confidence",1.0),
"source": ann.get("source","manual"),
})
ann_id += 1
coco = {"info": {"description": "AI Self-Driving Dataset",
"version": "1.0"},
"images": images_list,
"annotations": anns_list,
"categories": categories}
with open(path,"w") as f:
json.dump(coco, f, indent=2, ensure_ascii=False)
print(f"[COCO ] -> {path} ({len(anns_list)} anotasi)")
return path
# ── Export Segmentation Mask PNG ─────────────────────────────
def export_masks(self, out_dir="seg_masks"):
os.makedirs(out_dir, exist_ok=True)
count = 0
for fname, info in self.data.items():
masks = info.get("masks",[])
if not masks: continue
base = os.path.splitext(os.path.basename(fname))[0]
H = info.get("height",1)
W = info.get("width",1)
# combined mask (255 = objek)
combined = np.zeros((H,W), dtype=np.uint8)
for m in masks:
combined = np.maximum(combined,
(m*255).astype(np.uint8) if m.max()<=1 else m)
cv2.imwrite(os.path.join(out_dir, base+"_mask.png"), combined)
count += 1
print(f"[MASK ] -> {out_dir}/ ({count} mask files)")
return out_dir
# ── Export Semua ke ZIP ──────────────────────────────────────
def export_zip(self, path="annotation_export.zip"):
j = self.export_json("_tmp_annotations.json")
c = self.export_coco("_tmp_coco.json")
y = self.export_yolo("_tmp_yolo")
with zipfile.ZipFile(path,"w") as zf:
zf.write(j, "annotations.json")
zf.write(c, "coco_annotations.json")
for root,_,fls in os.walk(y):
for fn in fls:
fp = os.path.join(root,fn)
zf.write(fp, os.path.relpath(fp, "_tmp_yolo"))
print(f"[ZIP ] -> {path}")
return path
ann_manager = AnnotationManager()
# ══════════════════════════════════════════════════════════════════
# CELL 5 VISUALISASI
# ══════════════════════════════════════════════════════════════════
# ── Skeleton COCO 17 keypoints ───────────────────────────────────
POSE_SKELETON = [
(0,1),(0,2),(1,3),(2,4), # kepala
(5,6),(5,7),(7,9),(6,8),(8,10), # bahu-tangan
(5,11),(6,12),(11,12), # torso
(11,13),(13,15),(12,14),(14,16), # kaki
]
POSE_COLORS = [(255,100,100),(100,255,100),(100,100,255),
(255,255,100),(255,100,255),(100,255,255)]
def draw_full(
img_rgb : np.ndarray,
anns : list,
masks : list = None,
poses : list = None,
alpha : float = 0.18,
show_conf: bool = True,
show_cat : bool = True,
) -> np.ndarray:
"""Gambar BBox + Mask overlay + Skeleton pada satu gambar."""
out = img_rgb.copy()
# ── Mask overlay (segmentation) ──────────────────────────────
if masks:
for i, ann in enumerate(anns):
mid = ann.get("mask_idx", i)
if mid < len(masks):
m = masks[mid]
cat = ann.get("category","Kendaraan")
hex_c = CATEGORY_COLORS.get(cat,"#FFFFFF")
r,g,b = int(hex_c[1:3],16),int(hex_c[3:5],16),int(hex_c[5:7],16)
colored = np.zeros_like(out)
colored[m > 0] = (r,g,b)
out = cv2.addWeighted(out, 1-alpha*1.2, colored, alpha*1.2, 0)
# ── BBox ─────────────────────────────────────────────────────
for ann in anns:
x1,y1,x2,y2 = ann["bbox"]
cat = ann.get("category","Objek Sekitar")
label = ann.get("label","?")
conf = ann.get("confidence",1.0)
src = ann.get("source","manual")
hex_c = CATEGORY_COLORS.get(cat,"#FFFFFF")
r,g,b = int(hex_c[1:3],16),int(hex_c[3:5],16),int(hex_c[5:7],16)
bgr = (b,g,r)
# isi semi-transparan
ov = out.copy()
cv2.rectangle(ov,(x1,y1),(x2,y2),bgr,-1)
out = cv2.addWeighted(out,1-alpha,ov,alpha,0)
# border (tebal berbeda per source)
thick = 3 if src=="manual" else 2 if src=="seg" else 1
cv2.rectangle(out,(x1,y1),(x2,y2),bgr,thick)
# label tag
parts = []
if show_cat: parts.append(CATEGORY_META.get(cat,{}).get("icon",""))
parts.append(label)
if show_conf and src!="manual":
parts.append(f"{conf:.0%}")
tag = " ".join(p for p in parts if p)
(tw,th),_ = cv2.getTextSize(tag, cv2.FONT_HERSHEY_SIMPLEX, 0.42, 1)
ty = max(y1-2, th+6)
cv2.rectangle(out,(x1,ty-th-5),(x1+tw+6,ty+1),bgr,-1)
cv2.putText(out, tag, (x1+3,ty-2),
cv2.FONT_HERSHEY_SIMPLEX, 0.42, (255,255,255), 1,
cv2.LINE_AA)
# ── Pose skeleton ────────────────────────────────────────────
if poses:
for pose_data in poses:
kps = np.array(pose_data["keypoints"])
# joints
for j, (kx,ky,kv) in enumerate(kps):
if kv > 0.3:
col = POSE_COLORS[j % len(POSE_COLORS)]
cv2.circle(out,(int(kx),int(ky)),3,col,-1,cv2.LINE_AA)
# skeleton
for (a,b_) in POSE_SKELETON:
if a<len(kps) and b_<len(kps):
(ax,ay,av),(bx,by,bv) = kps[a], kps[b_]
if av>0.3 and bv>0.3:
cv2.line(out,(int(ax),int(ay)),(int(bx),int(by)),
(200,200,0),1,cv2.LINE_AA)
return out
def show_result(fname, img_rgb, anns, masks=None, poses=None,
title="", show_stats=True):
"""Panel visualisasi 3-kolom: Asli | Anotasi | Statistik mini."""
has_extra = bool(masks or poses)
ncols = 3 if show_stats else 2
fig = plt.figure(figsize=(18, 7) if ncols==3 else (14,6))
fig.patch.set_facecolor("#12121e")
gs = GridSpec(1, ncols, figure=fig, wspace=0.05)
axes = [fig.add_subplot(gs[0,i]) for i in range(ncols)]
for ax in axes:
ax.set_facecolor("#1a1a2e"); ax.axis("off")
# kolom 0: gambar asli
axes[0].imshow(img_rgb)
axes[0].set_title("Gambar Asli", color="#aaa", fontsize=11, pad=6)
# kolom 1: hasil anotasi lengkap
img_ann = draw_full(img_rgb, anns, masks, poses)
axes[1].imshow(img_ann)
src_tag = ""
if any(a.get("source")=="seg" for a in anns): src_tag += " +SEG"
if any(a.get("source")=="yoloe" for a in anns): src_tag += " +YOLOE"
if poses: src_tag += " +POSE"
axes[1].set_title(f"Hasil Anotasi ({len(anns)} objek){src_tag}",
color="white", fontsize=11, pad=6)
# kolom 2: mini bar chart
if show_stats and ncols == 3:
ax3 = axes[2]
ax3.set_facecolor("#0f0f1e")
ax3.axis("on")
cat_cnt = {}
for a in anns:
cat_cnt[a.get("category","?")] = cat_cnt.get(a.get("category","?"),0)+1
cats = sorted(cat_cnt.items(), key=lambda x:-x[1])
y_pos = range(len(cats))
colors = [CATEGORY_COLORS.get(c[0],"#888") for c in cats]
bars = ax3.barh([c[0] for c in cats],[c[1] for c in cats],
color=colors, edgecolor="#12121e", height=0.6)
for bar, (_,cnt) in zip(bars,cats):
ax3.text(bar.get_width()+0.1, bar.get_y()+bar.get_height()/2,
str(cnt), va="center", color="white", fontsize=9)
ax3.set_title("Per Kategori", color="#aaa", fontsize=10, pad=6)
ax3.tick_params(colors="#aaa", labelsize=8)
ax3.spines[["top","right","bottom","left"]].set_color("#333")
ax3.set_facecolor("#0f0f1e")
# legend bawah
legend_p = [mpatches.Patch(color=c,label=k)
for k,c in CATEGORY_COLORS.items()]
fig.legend(handles=legend_p, loc="lower center", ncol=7,
facecolor="#0f3460", edgecolor="none",
labelcolor="white", fontsize=8, framealpha=0.9,
bbox_to_anchor=(0.5,-0.05))
plt.suptitle(title or os.path.basename(fname),
color="white", fontsize=13, y=1.01)
plt.tight_layout()
plt.show()
# ══════════════════════════════════════════════════════════════════
# CELL 6 STATISTIK
# ══════════════════════════════════════════════════════════════════
def show_statistics(ann_manager: AnnotationManager):
all_anns = [a for info in ann_manager.data.values()
for a in info["annotations"]]
if not all_anns:
print("[!] Belum ada anotasi."); return
cat_cnt = {k:0 for k in CATEGORY_META}
lbl_cnt = {}
src_cnt = {"auto":0,"seg":0,"yoloe":0,"manual":0}
conf_all = []
for a in all_anns:
cat = a.get("category","Objek Sekitar")
cat_cnt[cat] = cat_cnt.get(cat,0)+1
lbl = a.get("label","?")
lbl_cnt[lbl] = lbl_cnt.get(lbl,0)+1
src = a.get("source","manual")
src_cnt[src] = src_cnt.get(src,0)+1
if src != "manual":
conf_all.append(a.get("confidence",1.0))
fig = plt.figure(figsize=(18,9))
fig.patch.set_facecolor("#12121e")
# 1. Pie
ax1 = fig.add_subplot(2,3,1); ax1.set_facecolor("#1a1a2e")
labs = [k for k,v in cat_cnt.items() if v>0]
sizes = [cat_cnt[k] for k in labs]
cols = [CATEGORY_COLORS[k] for k in labs]
_,_,at = ax1.pie(sizes,labels=labs,autopct="%1.0f%%",
colors=cols,startangle=90,
textprops={"color":"white","fontsize":7})
for t in at: t.set_color("black"); t.set_fontsize(7)
ax1.set_title("Distribusi Kategori",color="white",fontsize=11)
# 2. Top label bar
ax2 = fig.add_subplot(2,3,2); ax2.set_facecolor("#1a1a2e")
top = sorted(lbl_cnt.items(),key=lambda x:-x[1])[:15]
bar_colors = [CATEGORY_COLORS.get(LABEL2CAT.get(l,"Objek Sekitar"),"#888")
for l,_ in top]
bars = ax2.barh([t[0] for t in top],[t[1] for t in top],
color=bar_colors,edgecolor="#1a1a2e")
for bar,(_,cnt) in zip(bars,top):
ax2.text(bar.get_width()+.1,bar.get_y()+bar.get_height()/2,
str(cnt),va="center",color="white",fontsize=8)
ax2.set_title("Top 15 Label",color="white",fontsize=11)
ax2.tick_params(colors="white",labelsize=8)
ax2.spines[["top","right","bottom","left"]].set_color("#333")
# 3. Objek per gambar
ax3 = fig.add_subplot(2,3,3); ax3.set_facecolor("#1a1a2e")
names = [os.path.basename(f)[:12]+"~" if len(os.path.basename(f))>12
else os.path.basename(f) for f in ann_manager.data]
counts = [len(i["annotations"]) for i in ann_manager.data.values()]
ax3.bar(names,counts,color="#FF6B35",edgecolor="#12121e")
ax3.set_title("Objek per Gambar",color="white",fontsize=11)
ax3.tick_params(colors="white",labelsize=7,axis="x",rotation=35)
ax3.tick_params(colors="white",labelsize=8,axis="y")
ax3.spines[["top","right"]].set_color("#333")
# 4. Source pie
ax4 = fig.add_subplot(2,3,4); ax4.set_facecolor("#1a1a2e")
src_labs = [k for k,v in src_cnt.items() if v>0]
src_sizes = [src_cnt[k] for k in src_labs]
src_cols = ["#4ECDC4","#85C1E9","#FFE66D","#FF8B94"][:len(src_labs)]
ax4.pie(src_sizes,labels=src_labs,autopct="%1.0f%%",
colors=src_cols,startangle=90,
textprops={"color":"white","fontsize":9})
ax4.set_title("Sumber Anotasi",color="white",fontsize=11)
# 5. Confidence histogram
ax5 = fig.add_subplot(2,3,5); ax5.set_facecolor("#1a1a2e")
if conf_all:
ax5.hist(conf_all,bins=20,color="#4ECDC4",edgecolor="#12121e")
ax5.axvline(np.mean(conf_all),color="#FFE66D",linewidth=1.5,
linestyle="--",label=f"Avg {np.mean(conf_all):.1%}")
ax5.legend(facecolor="#0f3460",labelcolor="white",fontsize=8)
ax5.set_title("Distribusi Confidence",color="white",fontsize=11)
ax5.tick_params(colors="white",labelsize=8)
ax5.spines[["top","right"]].set_color("#333")
ax5.set_xlabel("Confidence",color="#aaa",fontsize=9)
# 6. Summary teks
ax6 = fig.add_subplot(2,3,6); ax6.axis("off")
ax6.set_facecolor("#0f0f1e")
lines = [
f"Total Gambar : {len(ann_manager.data)}",
f"Total Objek : {len(all_anns)}",
f"Auto YOLO26 : {src_cnt['auto']}",
f"Segmentation : {src_cnt['seg']}",
f"YOLOE Prompt : {src_cnt['yoloe']}",
f"Manual : {src_cnt['manual']}",
"",
f"Avg Conf(auto) : {np.mean(conf_all):.1%}" if conf_all else "No auto dets",
f"Label unik : {len(lbl_cnt)}",
]
for i,l in enumerate(lines):
ax6.text(0.05, 0.95-i*0.1, l, transform=ax6.transAxes,
color="white" if l else "#555",
fontsize=9, va="top", fontfamily="monospace")
ax6.set_title("Ringkasan",color="white",fontsize=11)
plt.suptitle("STATISTIK ANOTASI — AI Self-Driving Dataset",
color="white",fontsize=14,y=1.01)
plt.tight_layout()
plt.show()
# ══════════════════════════════════════════════════════════════════
# CELL 7 UI WIDGET
# ══════════════════════════════════════════════════════════════════
class AnnotationUI:
def __init__(self):
self._build_ui()
def _build_ui(self):
s = {"description_width": "160px"}
W6 = widgets.Layout(width="66%")
W3 = widgets.Layout(width="30%")
# ── Header ──────────────────────────────────────────────
self.header = widgets.HTML("""
<div style="background:linear-gradient(135deg,#0a0a1e,#0f3460,#0a0a1e);
border-radius:14px;padding:20px 28px;margin-bottom:10px;
border:1px solid #1a5276;">
<div style="font-family:monospace;color:#4ECDC4;font-size:18px;
letter-spacing:3px;font-weight:bold;">
[YOLO26] AI SELF-DRIVING ANNOTATION v3.0
</div>
<div style="color:#85C1E9;font-size:12px;margin-top:4px;">
Detection | Segmentation | Pose | YOLOE Text-Prompt | SAHI
</div>
<div style="margin-top:8px;font-size:11px;color:#555;">
YOLO26 Dual-Head · 7 Kategori · 48 Label · 4 Format Export
</div>
</div>""")
# ── Model Settings ──────────────────────────────────────
self.dd_det = widgets.Dropdown(
options=list(ModelManager.DETECT_MODELS.keys()),
value="YOLO26l — Large (sangat akurat)",
description="Detection Model:", style=s, layout=W6)
self.chk_seg = widgets.Checkbox(value=False,
description="Aktifkan Segmentation (YOLO26-seg)",
style={"description_width":"initial"})
self.chk_pose = widgets.Checkbox(value=False,
description="Aktifkan Pose Estimation (YOLO26-pose)",
style={"description_width":"initial"})
self.chk_yoloe = widgets.Checkbox(value=False,
description="Aktifkan YOLOE Text-Prompt (deteksi kelas kustom)",
style={"description_width":"initial"})
self.dd_yoloe = widgets.Dropdown(
options=list(ModelManager.YOLOE_MODELS.keys()),
description="YOLOE Model:", style=s, layout=W6,
disabled=True)
# toggle yoloe dropdown
def _on_yoloe_chk(change):
self.dd_yoloe.disabled = not change["new"]
self.chk_yoloe.observe(_on_yoloe_chk, "value")
# ── Detection Settings ───────────────────────────────────
self.sl_conf = widgets.FloatSlider(
value=0.22, min=0.05, max=0.85, step=0.01,
description="Confidence:", readout_format=".0%",
style=s, layout=W6)
self.sl_iou = widgets.FloatSlider(
value=0.45, min=0.10, max=0.80, step=0.05,
description="IoU (NMS):", readout_format=".0%",
style=s, layout=W6)
self.chk_end2end = widgets.Checkbox(value=True,
description="YOLO26 One-to-One Head (no NMS, lebih cepat)",
style={"description_width":"initial"})
self.chk_sahi = widgets.Checkbox(value=False,
description="SAHI Slicing — untuk objek kecil/jauh",
style={"description_width":"initial"})
self.sl_sahi = widgets.IntSlider(
value=640, min=320, max=1280, step=64,
description="Slice Size (px):", style=s, layout=W6)
self.chk_showcat = widgets.Checkbox(value=True,
description="Tampilkan ikon kategori pada label",
style={"description_width":"initial"})
self.chk_showconf = widgets.Checkbox(value=True,
description="Tampilkan confidence pada label",
style={"description_width":"initial"})
# ── Buttons ─────────────────────────────────────────────
btn = lambda txt, style, w="155px": widgets.Button(
description=txt, button_style=style,
layout=widgets.Layout(width=w))
self.btn_load = btn("[*] Load Model", "primary", "165px")
self.lbl_status = widgets.Label(" Belum dimuat")
self.btn_upload = btn("[+] Upload Gambar", "info", "175px")
self.btn_detect = btn("[>] Auto Detect", "success")
self.btn_detect_all= btn("[>>] Detect Semua", "success")
self.btn_redetect = btn("[!] Re-Detect (reset)","warning", "185px")
self.btn_show = btn("[v] Tampilkan", "")
self.btn_add = btn("[+] Tambah Manual", "warning", "175px")
self.btn_del = btn("[x] Hapus Idx", "danger", "135px")
self.btn_stats = btn("[S] Statistik", "")
self.btn_exp_json = btn("[J] JSON", "")
self.btn_exp_yolo = btn("[Y] YOLO", "")
self.btn_exp_coco = btn("[C] COCO", "")
self.btn_exp_mask = btn("[M] Mask PNG", "")
self.btn_exp_zip = btn("[Z] ZIP Semua", "primary")
for b in [self.btn_upload, self.btn_detect, self.btn_detect_all,
self.btn_redetect, self.btn_show, self.btn_add, self.btn_del]:
b.disabled = True
# ── Gambar & Manual Input ────────────────────────────────
self.dd_img = widgets.Dropdown(
options=[], description="Gambar aktif:",
style=s, layout=W6, disabled=True)
self.dd_cat = widgets.Dropdown(
options=list(CATEGORY_LABELS.keys()),
description="Kategori:", style=s, layout=W6)
self.dd_lbl = widgets.Dropdown(
options=CATEGORY_LABELS["Kendaraan"],
description="Label:", style=s, layout=W6)
self.int_x1 = widgets.IntText(description="x1:", style=s, layout=W3)
self.int_y1 = widgets.IntText(description="y1:", style=s, layout=W3)
self.int_x2 = widgets.IntText(description="x2:", style=s, layout=W3)
self.int_y2 = widgets.IntText(description="y2:", style=s, layout=W3)
self.int_del = widgets.IntText(description="Index:", value=0,
style=s, layout=W3)
self.out = widgets.Output()
# ── Events ──────────────────────────────────────────────
self.btn_load.on_click(self._load)
self.btn_upload.on_click(self._upload)
self.btn_detect.on_click(lambda _: self._detect(False))
self.btn_detect_all.on_click(self._detect_all)
self.btn_redetect.on_click(lambda _: self._detect(True))
self.btn_show.on_click(self._show)
self.dd_cat.observe(lambda _: setattr(
self.dd_lbl,"options",
CATEGORY_LABELS.get(self.dd_cat.value,[])), "value")
self.btn_add.on_click(self._add_manual)
self.btn_del.on_click(self._delete)
self.btn_stats.on_click(lambda _: self._stats())
self.btn_exp_json.on_click(lambda _: self._export("json"))
self.btn_exp_yolo.on_click(lambda _: self._export("yolo"))
self.btn_exp_coco.on_click(lambda _: self._export("coco"))
self.btn_exp_mask.on_click(lambda _: self._export("mask"))
self.btn_exp_zip.on_click(self._download_zip)
# ── Layout ──────────────────────────────────────────────────
def _sec(self, t):
return widgets.HTML(
f'<div style="background:linear-gradient(90deg,#0f3460,#0a0a1e);'
f'border-radius:8px;padding:6px 16px;margin:10px 0 4px;'
f'border-left:3px solid #4ECDC4;">'
f'<b style="color:#FFE66D;font-size:12px;font-family:monospace;">'
f'{t}</b></div>')
def _tip(self, t):
return widgets.HTML(
f'<p style="color:#666;font-size:10px;margin:2px 0 6px 4px;">{t}</p>')
def display(self):
ui = widgets.VBox([
self.header,
self._sec("(1) KONFIGURASI MODEL"),
self.dd_det,
widgets.HBox([self.chk_seg, self.chk_pose]),
widgets.HBox([self.chk_yoloe, self.dd_yoloe]),
widgets.HBox([self.btn_load, self.lbl_status]),
self._sec("(2) UPLOAD GAMBAR"),
self.btn_upload,
self._sec("(3) PILIH GAMBAR"),
widgets.HBox([self.dd_img, self.btn_show]),
self._sec("(4) PENGATURAN DETEKSI"),
self._tip("Tips: Gunakan 15-25% untuk jalan ramai. SAHI membantu objek kecil di kejauhan."),
self.sl_conf, self.sl_iou,
self.chk_end2end,
widgets.HBox([self.chk_sahi, self.sl_sahi]),
widgets.HBox([self.chk_showcat, self.chk_showconf]),
widgets.HBox([self.btn_detect, self.btn_detect_all,
self.btn_redetect]),
self._sec("(5) TAMBAH MANUAL"),
widgets.HBox([self.dd_cat, self.dd_lbl]),
widgets.HBox([self.int_x1, self.int_y1,
self.int_x2, self.int_y2]),
self.btn_add,
self._sec("(6) HAPUS ANOTASI"),
widgets.HBox([self.int_del, self.btn_del]),
self._sec("(7) STATISTIK & EXPORT"),
widgets.HBox([self.btn_stats]),
widgets.HBox([self.btn_exp_json, self.btn_exp_yolo,
self.btn_exp_coco, self.btn_exp_mask,
self.btn_exp_zip]),
self.out,
], layout=widgets.Layout(padding="14px",
border="1px solid #1a3a5c",
border_radius="14px"))
display(ui)
# ── Callbacks ───────────────────────────────────────────────
def _load(self, _):
with self.out:
clear_output()
model_manager.load(
det_key = self.dd_det.value,
use_seg = self.chk_seg.value,
use_pose = self.chk_pose.value,
use_yoloe = self.chk_yoloe.value,
yoloe_key = self.dd_yoloe.value if self.chk_yoloe.value else None,
)
mods = ["YOLO26"]
if self.chk_seg.value: mods.append("SEG")
if self.chk_pose.value: mods.append("POSE")
if self.chk_yoloe.value: mods.append("YOLOE")
self.lbl_status.value = f" [OK] {'+'.join(mods)} siap"
self.btn_upload.disabled = False
def _upload(self, _):
with self.out:
clear_output()
print("Pilih gambar (bisa multiple) ...")
uploaded = files.upload()
if not uploaded: return
for fname in uploaded:
img_bgr = cv2.imread(fname)
if img_bgr is None:
print(f"[!] Gagal: {fname}"); continue
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
ann_manager.add_image(fname, img_rgb)
print(f" [OK] {fname} "
f"({img_rgb.shape[1]}x{img_rgb.shape[0]} px)")
opts = list(ann_manager.images.keys())
self.dd_img.options = opts
self.dd_img.value = opts[-1]
self.dd_img.disabled = False
for b in [self.btn_show, self.btn_add, self.btn_del,
self.btn_detect, self.btn_detect_all,
self.btn_redetect]:
b.disabled = False
print(f"\n[OK] {len(uploaded)} gambar siap dianotasi!")
def _run_detect(self, fname, clear_prev=False):
if fname not in ann_manager.images:
print("[!] Gambar tidak ada."); return
img_rgb = ann_manager.images[fname]
if clear_prev:
ann_manager.clear_auto(fname)
print("[*] Anotasi auto sebelumnya dihapus.")
conf = self.sl_conf.value
iou = self.sl_iou.value
sahi = self.chk_sahi.value
sz = self.sl_sahi.value
e2e = self.chk_end2end.value
print(f"[*] Deteksi: conf={conf:.0%} iou={iou:.0%}"
f" e2e={e2e}"
f" SAHI={'ON '+str(sz)+'px' if sahi else 'OFF'}")
mods = []
if self.chk_seg.value: mods.append("SEG")
if self.chk_pose.value: mods.append("POSE")
if self.chk_yoloe.value: mods.append("YOLOE")
if mods: print(f" Extra engines: {', '.join(mods)}")
t0 = time.time()
res = model_manager.detect_full(
img_rgb,
conf = conf,
iou = iou,
use_sahi = sahi,
sahi_size = sz,
use_seg = self.chk_seg.value and model_manager.seg_model is not None,
use_pose = self.chk_pose.value and model_manager.pose_model is not None,
use_yoloe = self.chk_yoloe.value and getattr(model_manager,"yoloe_model",None) is not None,
end2end = e2e,
)
elapsed = time.time()-t0
dets = res["detections"]
ann_manager.add_annotations(fname, dets,
masks = res["masks"] or None,
keypoints= res["keypoints"] or None)
total = len(ann_manager.get_anns(fname))
print(f"[OK] {len(dets)} objek baru | total: {total}"
f" | {elapsed:.1f}s\n")
# ringkasan kelas
cls_cnt = {}
for d in dets:
k = f"{d['label']:25s} ({d['yolo_class']})"
cls_cnt[k] = cls_cnt.get(k,0)+1
for k,v in sorted(cls_cnt.items(),key=lambda x:-x[1]):
bar = "#"*min(v,25)
print(f" {v:4d} {bar} {k}")
show_result(
fname, img_rgb,
ann_manager.get_anns(fname),
ann_manager.get_masks(fname) or None,
ann_manager.get_poses(fname) or None,
title=f"YOLO26 Detect | {os.path.basename(fname)}"
f" | conf={conf:.0%}",
show_stats=True,
)
self._print_table(fname)
def _detect(self, clear_prev):
with self.out:
clear_output()
self._run_detect(self.dd_img.value, clear_prev)
def _detect_all(self, _):
with self.out:
clear_output()
total = 0
for fname in list(ann_manager.images.keys()):
print(f"\n{'='*50}")
print(f"[>>] {os.path.basename(fname)}")
self._run_detect(fname, clear_prev=False)
total += len(ann_manager.get_anns(fname))
print(f"\n{'='*50}")
print(f"[OK] Selesai! Total {total} objek di "
f"{len(ann_manager.images)} gambar.")
def _show(self, _):
with self.out:
clear_output()
fname = self.dd_img.value
if fname not in ann_manager.images: return
anns = ann_manager.get_anns(fname)
show_result(fname, ann_manager.images[fname], anns,
ann_manager.get_masks(fname) or None,
ann_manager.get_poses(fname) or None)
self._print_table(fname)
def _add_manual(self, _):
with self.out:
clear_output()
fname = self.dd_img.value
if not fname: return
ann = {
"category": self.dd_cat.value,
"label": self.dd_lbl.value,
"bbox": [self.int_x1.value, self.int_y1.value,
self.int_x2.value, self.int_y2.value],
"confidence": 1.0,
"source": "manual",
}
ann_manager.add_one(fname, ann)
anns = ann_manager.get_anns(fname)
print(f"[+] Manual anotasi ditambahkan (idx {len(anns)-1})\n")
show_result(fname, ann_manager.images[fname], anns)
self._print_table(fname)
def _delete(self, _):
with self.out:
clear_output()
fname = self.dd_img.value; idx = self.int_del.value
ann_manager.remove(fname, idx)
anns = ann_manager.get_anns(fname)
print(f"[x] Index {idx} dihapus. Sisa: {len(anns)}\n")
show_result(fname, ann_manager.images[fname], anns)
self._print_table(fname)
def _export(self, fmt):
with self.out:
clear_output()
if fmt=="json": ann_manager.export_json()
elif fmt=="yolo":ann_manager.export_yolo()
elif fmt=="coco":ann_manager.export_coco()
elif fmt=="mask":ann_manager.export_masks()
def _stats(self):
with self.out:
clear_output()
show_statistics(ann_manager)
def _download_zip(self, _):
with self.out:
clear_output()
zp = ann_manager.export_zip("annotation_export.zip")
print(f"\n[D] Mengunduh {zp} ...")
files.download(zp)
print("[OK] Download selesai!")
def _print_table(self, fname):
anns = ann_manager.get_anns(fname)
if not anns: print(" (belum ada anotasi)"); return
print(f"\n{'Idx':>4} {'Kategori':<18} {'Label':<24}"
f" {'BBox':<22} {'Conf':>5} {'Src'}")
print("─"*90)
for i,a in enumerate(anns):
print(f"{i:4d} {a.get('category',''):<18} "
f"{a.get('label',''):<24} "
f"{str(a.get('bbox','')):<22} "
f"{a.get('confidence',1.0):>5.1%} "
f"{a.get('source','')}")
print(f"\n Total: {len(anns)} objek "
f"| {len(ann_manager.images)} gambar "
f"| {ann_manager.total()} semua")
# ══════════════════════════════════════════════════════════════════
# CELL 8 MAIN
# ══════════════════════════════════════════════════════════════════
def main():
banner = """
╔══════════════════════════════════════════════════════════╗
║ AI SELF-DRIVING ANNOTATION TOOL v3.0 — YOLO26 ║
╠══════════════════════════════════════════════════════════╣
║ PANDUAN CEPAT untuk jalan ramai: ║
║ 1. Load Model → pilih YOLO26l (atau x untuk max) ║
║ 2. Aktifkan Segmentation + YOLOE jika GPU cukup ║
║ 3. Upload gambar jalan raya ║
║ 4. Set Confidence: 15-22% | IoU: 40-45% ║
║ 5. Aktifkan SAHI jika masih ada objek terlewat ║
║ 6. Klik [Auto Detect] ║
║ 7. Export → ZIP (JSON + YOLO + COCO sekaligus) ║
╚══════════════════════════════════════════════════════════╝
"""
print(banner)
ui = AnnotationUI()
ui.display()
if __name__ == "__main__":
main()
# !pip install ultralytics opencv-python-headless matplotlib ipywidgets Pillow tqdm
# !pip install -q lapx # tracker opsional
!pip install ultralytics opencv-python-headless matplotlib ipywidgets Pillow tqdm
!pip install -q lapx # tracker opsional
!pip install sahi
@Xnuvers007
Copy link
Copy Markdown
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment