Skip to content

Commit 0df47bf

Browse files
mihowclaude
andcommitted
fix(yolo detector): accept tensor batches from antenna REST dataloader
The Mothbot YOLO detector's predict_batch strictly required list[PIL.Image], which works for the API/ml-layer path but fails for the antenna worker path. The antenna RESTDataset applies torchvision.transforms.ToTensor() to images before they reach the detector, producing tensor batches that the original check rejected with: Mothbot YOLO11m Creature Detector expects a list of PIL images from the collate fn; got <class 'torch.Tensor'> FasterRCNN's detection path already accepts both, which is why this only surfaced when wiring a YOLO-based pipeline into the worker. Accept three input forms now: - list[PIL.Image] (ML-layer dataloader, unchanged) - torch.Tensor (B, C, H, W) from REST stacked batches - list[torch.Tensor] (C, H, W) from REST mixed-size fallback For tensor inputs, convert back to HWC uint8 numpy so ultralytics does its own letterboxing / normalization at imgsz=1600, matching the PIL path's behavior. Discovered running job 1493 through the beast Antenna worker. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9f2b5f9 commit 0df47bf

1 file changed

Lines changed: 22 additions & 7 deletions

File tree

trapdata/ml/models/localization.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -515,14 +515,29 @@ def collate_as_lists(batch):
515515
return self.dataloader
516516

517517
def predict_batch(self, batch):
518-
"""batch is a list[PIL.Image]. Returns a list of ultralytics Results."""
519-
if not isinstance(batch, list):
520-
raise TypeError(
521-
f"{self.name} expects a list of PIL images from the collate fn; "
522-
f"got {type(batch)}"
523-
)
518+
"""Run YOLO inference. Accepts either:
519+
520+
- list[PIL.Image] (from our ML-layer dataloader, which collates as lists)
521+
- torch.Tensor of shape (B, C, H, W) (from the antenna REST dataloader,
522+
which applies torchvision.transforms.ToTensor to each PIL image)
523+
- list[torch.Tensor] of shape (C, H, W) (REST dataloader mixed-size fallback)
524+
525+
For tensor inputs we convert back to numpy HWC uint8 so ultralytics
526+
does its own letterboxing / normalization, matching the PIL path.
527+
"""
528+
if isinstance(batch, torch.Tensor):
529+
# (B, C, H, W) in [0, 1] float -> list of (H, W, C) uint8 numpy
530+
imgs = [
531+
(t.permute(1, 2, 0).cpu().numpy() * 255).astype(np.uint8) for t in batch
532+
]
533+
elif isinstance(batch, list) and batch and isinstance(batch[0], torch.Tensor):
534+
imgs = [
535+
(t.permute(1, 2, 0).cpu().numpy() * 255).astype(np.uint8) for t in batch
536+
]
537+
else:
538+
imgs = batch
524539
return self.model.predict(
525-
batch,
540+
imgs,
526541
imgsz=self.imgsz,
527542
conf=self.bbox_score_threshold,
528543
max_det=self.box_detections_per_img,

0 commit comments

Comments
 (0)