Spaces:
Runtime error
Runtime error
Zhen Ye Claude Opus 4.6 (1M context) commited on
Commit ·
a67c231
1
Parent(s): 9f46868
feat(inspection): persist RLE masks during segmentation inference
Browse filesCo-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- inference.py +18 -2
- jobs/storage.py +36 -0
inference.py
CHANGED
|
@@ -22,6 +22,7 @@ from models.segmenters.model_loader import load_segmenter, load_segmenter_on_dev
|
|
| 22 |
from models.depth_estimators.model_loader import load_depth_estimator, load_depth_estimator_on_device
|
| 23 |
from utils.video import StreamingVideoWriter
|
| 24 |
from jobs.storage import set_track_data, store_latest_frame
|
|
|
|
| 25 |
import tempfile
|
| 26 |
import json as json_module
|
| 27 |
|
|
@@ -1253,6 +1254,7 @@ def run_grounded_sam2_tracking(
|
|
| 1253 |
# --- ObjectInfo → detection dict adapter ---
|
| 1254 |
def _objectinfo_to_dets(frame_objects_dict):
|
| 1255 |
dets = []
|
|
|
|
| 1256 |
for obj_id, info in frame_objects_dict.items():
|
| 1257 |
dets.append({
|
| 1258 |
"label": info.class_name,
|
|
@@ -1261,7 +1263,17 @@ def run_grounded_sam2_tracking(
|
|
| 1261 |
"track_id": f"T{obj_id:02d}",
|
| 1262 |
"instance_id": obj_id,
|
| 1263 |
})
|
| 1264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1265 |
|
| 1266 |
# Shared streaming state (publisher ↔ writer)
|
| 1267 |
_stream_deque: collections.deque = collections.deque() # unbounded — publisher drains at its own pace
|
|
@@ -1300,7 +1312,7 @@ def run_grounded_sam2_tracking(
|
|
| 1300 |
|
| 1301 |
# Build detection records and track history
|
| 1302 |
if fobjs:
|
| 1303 |
-
dets = _objectinfo_to_dets(fobjs)
|
| 1304 |
|
| 1305 |
# Maintain per-track bbox history (30-frame window)
|
| 1306 |
for det in dets:
|
|
@@ -1316,6 +1328,10 @@ def run_grounded_sam2_tracking(
|
|
| 1316 |
if job_id:
|
| 1317 |
set_track_data(job_id, next_idx, copy.deepcopy(dets))
|
| 1318 |
store_latest_frame(job_id, frm)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1319 |
else:
|
| 1320 |
if job_id:
|
| 1321 |
set_track_data(job_id, next_idx, [])
|
|
|
|
| 22 |
from models.depth_estimators.model_loader import load_depth_estimator, load_depth_estimator_on_device
|
| 23 |
from utils.video import StreamingVideoWriter
|
| 24 |
from jobs.storage import set_track_data, store_latest_frame
|
| 25 |
+
from inspection.masks import rle_encode
|
| 26 |
import tempfile
|
| 27 |
import json as json_module
|
| 28 |
|
|
|
|
| 1254 |
# --- ObjectInfo → detection dict adapter ---
|
| 1255 |
def _objectinfo_to_dets(frame_objects_dict):
|
| 1256 |
dets = []
|
| 1257 |
+
masks_rle = {} # {instance_id: rle_dict}
|
| 1258 |
for obj_id, info in frame_objects_dict.items():
|
| 1259 |
dets.append({
|
| 1260 |
"label": info.class_name,
|
|
|
|
| 1263 |
"track_id": f"T{obj_id:02d}",
|
| 1264 |
"instance_id": obj_id,
|
| 1265 |
})
|
| 1266 |
+
# RLE-encode the mask if present
|
| 1267 |
+
if info.mask is not None:
|
| 1268 |
+
try:
|
| 1269 |
+
if isinstance(info.mask, torch.Tensor):
|
| 1270 |
+
mask_np = info.mask.cpu().numpy().astype(bool)
|
| 1271 |
+
else:
|
| 1272 |
+
mask_np = np.asarray(info.mask).astype(bool)
|
| 1273 |
+
masks_rle[obj_id] = rle_encode(mask_np)
|
| 1274 |
+
except Exception:
|
| 1275 |
+
logging.debug("Failed to RLE-encode mask for obj %d", obj_id)
|
| 1276 |
+
return dets, masks_rle
|
| 1277 |
|
| 1278 |
# Shared streaming state (publisher ↔ writer)
|
| 1279 |
_stream_deque: collections.deque = collections.deque() # unbounded — publisher drains at its own pace
|
|
|
|
| 1312 |
|
| 1313 |
# Build detection records and track history
|
| 1314 |
if fobjs:
|
| 1315 |
+
dets, masks_rle = _objectinfo_to_dets(fobjs)
|
| 1316 |
|
| 1317 |
# Maintain per-track bbox history (30-frame window)
|
| 1318 |
for det in dets:
|
|
|
|
| 1328 |
if job_id:
|
| 1329 |
set_track_data(job_id, next_idx, copy.deepcopy(dets))
|
| 1330 |
store_latest_frame(job_id, frm)
|
| 1331 |
+
# Store masks
|
| 1332 |
+
from jobs.storage import set_mask_data as _set_mask
|
| 1333 |
+
for iid, rle in masks_rle.items():
|
| 1334 |
+
_set_mask(job_id, next_idx, iid, rle)
|
| 1335 |
else:
|
| 1336 |
if job_id:
|
| 1337 |
set_track_data(job_id, next_idx, [])
|
jobs/storage.py
CHANGED
|
@@ -40,6 +40,7 @@ class JobStorage:
|
|
| 40 |
self._jobs: Dict[str, JobInfo] = {}
|
| 41 |
self._tracks: Dict[str, Dict[int, list]] = {} # job_id -> {frame_idx -> tracks}
|
| 42 |
self._latest_frames: Dict[str, any] = {} # job_id -> np.ndarray
|
|
|
|
| 43 |
self._lock = RLock()
|
| 44 |
|
| 45 |
def create(self, job: JobInfo) -> None:
|
|
@@ -78,6 +79,31 @@ class JobStorage:
|
|
| 78 |
"""Get the most recent frame for ISR cropping."""
|
| 79 |
return self._latest_frames.get(job_id)
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
def get(self, job_id: str) -> Optional[JobInfo]:
|
| 82 |
with self._lock:
|
| 83 |
return self._jobs.get(job_id)
|
|
@@ -95,6 +121,7 @@ class JobStorage:
|
|
| 95 |
self._jobs.pop(job_id, None)
|
| 96 |
self._tracks.pop(job_id, None)
|
| 97 |
self._latest_frames.pop(job_id, None)
|
|
|
|
| 98 |
shutil.rmtree(get_job_directory(job_id), ignore_errors=True)
|
| 99 |
|
| 100 |
def cleanup_expired(self, max_age: timedelta) -> None:
|
|
@@ -131,3 +158,12 @@ def store_latest_frame(job_id: str, frame) -> None:
|
|
| 131 |
|
| 132 |
def get_latest_frame(job_id: str):
|
| 133 |
return get_job_storage().get_latest_frame(job_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
self._jobs: Dict[str, JobInfo] = {}
|
| 41 |
self._tracks: Dict[str, Dict[int, list]] = {} # job_id -> {frame_idx -> tracks}
|
| 42 |
self._latest_frames: Dict[str, any] = {} # job_id -> np.ndarray
|
| 43 |
+
self._mask_data: Dict[str, Dict[str, any]] = {} # job_id -> {f"{frame_idx}:{track_id}" -> rle_dict}
|
| 44 |
self._lock = RLock()
|
| 45 |
|
| 46 |
def create(self, job: JobInfo) -> None:
|
|
|
|
| 79 |
"""Get the most recent frame for ISR cropping."""
|
| 80 |
return self._latest_frames.get(job_id)
|
| 81 |
|
| 82 |
+
def set_mask_data(self, job_id: str, frame_idx: int, track_id: int, rle: dict) -> None:
|
| 83 |
+
"""Store RLE mask for a specific object at a specific frame."""
|
| 84 |
+
with self._lock:
|
| 85 |
+
if job_id not in self._mask_data:
|
| 86 |
+
self._mask_data[job_id] = {}
|
| 87 |
+
key = f"{frame_idx}:{track_id}"
|
| 88 |
+
self._mask_data[job_id][key] = rle
|
| 89 |
+
|
| 90 |
+
def get_mask_data(self, job_id: str, frame_idx: int, track_id: int) -> dict | None:
|
| 91 |
+
"""Retrieve RLE mask for a specific object at a specific frame."""
|
| 92 |
+
with self._lock:
|
| 93 |
+
key = f"{frame_idx}:{track_id}"
|
| 94 |
+
return self._mask_data.get(job_id, {}).get(key)
|
| 95 |
+
|
| 96 |
+
def get_all_masks_for_frame(self, job_id: str, frame_idx: int) -> dict:
|
| 97 |
+
"""Return {track_id: rle_dict} for all objects in a frame."""
|
| 98 |
+
with self._lock:
|
| 99 |
+
prefix = f"{frame_idx}:"
|
| 100 |
+
result = {}
|
| 101 |
+
for key, rle in self._mask_data.get(job_id, {}).items():
|
| 102 |
+
if key.startswith(prefix):
|
| 103 |
+
tid = int(key.split(":")[1])
|
| 104 |
+
result[tid] = rle
|
| 105 |
+
return result
|
| 106 |
+
|
| 107 |
def get(self, job_id: str) -> Optional[JobInfo]:
|
| 108 |
with self._lock:
|
| 109 |
return self._jobs.get(job_id)
|
|
|
|
| 121 |
self._jobs.pop(job_id, None)
|
| 122 |
self._tracks.pop(job_id, None)
|
| 123 |
self._latest_frames.pop(job_id, None)
|
| 124 |
+
self._mask_data.pop(job_id, None)
|
| 125 |
shutil.rmtree(get_job_directory(job_id), ignore_errors=True)
|
| 126 |
|
| 127 |
def cleanup_expired(self, max_age: timedelta) -> None:
|
|
|
|
| 158 |
|
| 159 |
def get_latest_frame(job_id: str):
|
| 160 |
return get_job_storage().get_latest_frame(job_id)
|
| 161 |
+
|
| 162 |
+
def set_mask_data(job_id: str, frame_idx: int, track_id: int, rle: dict) -> None:
|
| 163 |
+
get_job_storage().set_mask_data(job_id, frame_idx, track_id, rle)
|
| 164 |
+
|
| 165 |
+
def get_mask_data(job_id: str, frame_idx: int, track_id: int) -> dict | None:
|
| 166 |
+
return get_job_storage().get_mask_data(job_id, frame_idx, track_id)
|
| 167 |
+
|
| 168 |
+
def get_all_masks_for_frame(job_id: str, frame_idx: int) -> dict:
|
| 169 |
+
return get_job_storage().get_all_masks_for_frame(job_id, frame_idx)
|