Download miner.py from flare325/ScoreVision: direct link, hf CLI and curl.
- Browser
- Download file 6.32 kB
-
https://huggingface.co/flare325/ScoreVision/resolve/main/miner.py
- Command line
-
hf download hf://flare325/ScoreVision/miner.py
-
curl -L -o miner.py https://huggingface.co/flare325/ScoreVision/resolve/main/miner.py
6.32 kB
| """TurboVision public-track miner for manak0/Detect-worksite-ppe-compliance-detection-probe-set. | |
| Model: YOLO26n exported to ONNX (input 1x3x704x704, NMS embedded, output 1x300x6 | |
| rows of x1, y1, x2, y2, score, class in letterboxed pixel space). | |
| cls_id in the response is the index into the element's manifest `objects` list. | |
| """ | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| import onnxruntime as ort | |
| from numpy import ndarray | |
| from pydantic import BaseModel | |
| MANIFEST_CLASSES = [ | |
| "safety helmet", | |
| "high-visibility safety vest", | |
| "protective gloves", | |
| "protective eyewear", | |
| ] | |
| # Per-class confidence thresholds, indexed like MANIFEST_CLASSES. | |
| CLASS_CONF = np.array([0.40, 0.40, 0.40, 0.30], dtype=np.float32) | |
| MODEL_FILENAME = "weights.onnx" | |
| LETTERBOX_FILL = 114 | |
| class BoundingBox(BaseModel): | |
| x1: int | |
| y1: int | |
| x2: int | |
| y2: int | |
| cls_id: int | |
| conf: float | |
| class Polygon(BaseModel): | |
| cls_id: int | |
| conf: float | |
| points: list[tuple[int, int]] | |
| class TVFrameResult(BaseModel): | |
| frame_id: int | |
| boxes: list[BoundingBox] | None = None | |
| polygons: list[Polygon] | None = None | |
| keypoints: list[tuple[int, int]] | None = None | |
| def _letterbox(image: ndarray, size: int) -> tuple[ndarray, float, int, int]: | |
| """Ultralytics-style centred letterbox to a size x size canvas.""" | |
| height, width = image.shape[:2] | |
| ratio = min(size / height, size / width) | |
| new_w = int(round(width * ratio)) | |
| new_h = int(round(height * ratio)) | |
| if (new_w, new_h) != (width, height): | |
| image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR) | |
| pad_w = (size - new_w) / 2 | |
| pad_h = (size - new_h) / 2 | |
| top, bottom = int(round(pad_h - 0.1)), int(round(pad_h + 0.1)) | |
| left, right = int(round(pad_w - 0.1)), int(round(pad_w + 0.1)) | |
| canvas = cv2.copyMakeBorder( | |
| image, | |
| top, | |
| bottom, | |
| left, | |
| right, | |
| cv2.BORDER_CONSTANT, | |
| value=(LETTERBOX_FILL, LETTERBOX_FILL, LETTERBOX_FILL), | |
| ) | |
| return canvas, ratio, left, top | |
| class Miner: | |
| def __init__(self, path_hf_repo: Path) -> None: | |
| model_path = Path(path_hf_repo) / MODEL_FILENAME | |
| if not model_path.is_file(): | |
| raise FileNotFoundError(f"{MODEL_FILENAME} not found in {path_hf_repo}") | |
| options = ort.SessionOptions() | |
| options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL | |
| options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL | |
| options.intra_op_num_threads = 2 | |
| options.inter_op_num_threads = 1 | |
| providers = ["CPUExecutionProvider"] | |
| if "CUDAExecutionProvider" in ort.get_available_providers(): | |
| providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] | |
| try: | |
| self.session = ort.InferenceSession( | |
| str(model_path), sess_options=options, providers=providers | |
| ) | |
| except Exception as exc: | |
| print(f"CUDA session failed, falling back to CPU: {exc}") | |
| self.session = ort.InferenceSession( | |
| str(model_path), sess_options=options, providers=["CPUExecutionProvider"] | |
| ) | |
| model_input = self.session.get_inputs()[0] | |
| self.input_name = model_input.name | |
| self.input_size = int(model_input.shape[2]) | |
| self.output_name = self.session.get_outputs()[0].name | |
| self._check_class_order() | |
| warmup = np.zeros((self.input_size, self.input_size, 3), dtype=np.uint8) | |
| for _ in range(2): | |
| self._infer_one(warmup) | |
| print(f"Miner ready: providers={self.session.get_providers()} input={self.input_size}") | |
| def __repr__(self) -> str: | |
| return ( | |
| f"PPE YOLO26n ONNX input={self.input_size} " | |
| f"providers={self.session.get_providers()}" | |
| ) | |
| def _check_class_order(self) -> None: | |
| names = self.session.get_modelmeta().custom_metadata_map.get("names", "") | |
| for idx, name in enumerate(MANIFEST_CLASSES): | |
| if f"{idx}: '{name}'" not in names: | |
| raise ValueError(f"ONNX class order does not match manifest: {names}") | |
| def _infer_one(self, image: ndarray) -> list[BoundingBox]: | |
| canvas, ratio, pad_x, pad_y = _letterbox(image, self.input_size) | |
| rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) | |
| tensor = np.ascontiguousarray(rgb.transpose(2, 0, 1)[None], dtype=np.float32) / 255.0 | |
| raw = self.session.run([self.output_name], {self.input_name: tensor})[0][0] | |
| scores = raw[:, 4] | |
| cls_ids = raw[:, 5].astype(np.int64) | |
| known = (cls_ids >= 0) & (cls_ids < len(MANIFEST_CLASSES)) | |
| keep = known & (scores >= CLASS_CONF[np.clip(cls_ids, 0, len(CLASS_CONF) - 1)]) | |
| if not keep.any(): | |
| return [] | |
| boxes = raw[keep, :4].astype(np.float64) | |
| boxes[:, [0, 2]] = (boxes[:, [0, 2]] - pad_x) / ratio | |
| boxes[:, [1, 3]] = (boxes[:, [1, 3]] - pad_y) / ratio | |
| height, width = image.shape[:2] | |
| boxes[:, [0, 2]] = boxes[:, [0, 2]].clip(0, width) | |
| boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, height) | |
| results: list[BoundingBox] = [] | |
| for (x1, y1, x2, y2), score, cls_id in zip(boxes, scores[keep], cls_ids[keep]): | |
| x1, y1, x2, y2 = (int(round(v)) for v in (x1, y1, x2, y2)) | |
| if x2 <= x1 or y2 <= y1: | |
| continue | |
| results.append( | |
| BoundingBox( | |
| x1=x1, y1=y1, x2=x2, y2=y2, cls_id=int(cls_id), conf=float(score) | |
| ) | |
| ) | |
| return results | |
| def predict_batch( | |
| self, | |
| batch_images: list[ndarray], | |
| offset: int, | |
| n_keypoints: int, | |
| ) -> list[TVFrameResult]: | |
| results: list[TVFrameResult] = [] | |
| for index, image in enumerate(batch_images): | |
| try: | |
| boxes = self._infer_one(image) | |
| except Exception as exc: | |
| print(f"Inference failed on frame {offset + index}: {exc}") | |
| boxes = [] | |
| results.append( | |
| TVFrameResult( | |
| frame_id=offset + index, | |
| boxes=boxes, | |
| polygons=[], | |
| keypoints=[(0, 0)] * max(0, int(n_keypoints)), | |
| ) | |
| ) | |
| return results | |