CoolFace
Apppublic

Blablablab/audio-classification

sourceHugging Faceapache-2.0updated 1mo agoView on Hugging Face
0likes
cv_utils.py383 linesDownload Raw Back to export
1"""2CV Export Utilities3 4Shared helper functions for computer vision export formats (COCO, YOLO, VOC).5"""6 7from typing import Dict, List, Tuple, Any, Optional8import logging9 10logger = logging.getLogger(__name__)11 12 13def build_category_mapping(annotations: List[dict], schemas: List[dict]) -> Dict[str, int]:14    """15    Build a mapping from label names to integer category IDs.16 17    Extracts labels from image_annotation schemas first (preserving config order),18    then discovers any additional labels from annotations.19 20    Args:21        annotations: List of annotation records22        schemas: List of annotation_scheme config dicts23 24    Returns:25        Dict mapping label name -> integer ID (starting from 1 for COCO, 0-indexed for YOLO)26    """27    labels = []28    seen = set()29 30    # First, collect labels from schema configs (preserves defined order)31    for schema in schemas:32        if schema.get("annotation_type") == "image_annotation":33            for label_def in schema.get("labels", []):34                name = label_def if isinstance(label_def, str) else label_def.get("name", "")35                if name and name not in seen:36                    labels.append(name)37                    seen.add(name)38 39    # Then discover any labels in annotation data not already in config40    for ann in annotations:41        for schema_name, img_annotations in ann.get("image_annotations", {}).items():42            if not isinstance(img_annotations, list):43                continue44            for obj in img_annotations:45                label = obj.get("label", "")46                if label and label not in seen:47                    labels.append(label)48                    seen.add(label)49 50    return {name: idx for idx, name in enumerate(labels)}51 52 53def polygon_to_bbox(points: List[List[float]]) -> Tuple[float, float, float, float]:54    """55    Compute axis-aligned bounding box from a polygon.56 57    Args:58        points: List of [x, y] coordinate pairs59 60    Returns:61        Tuple of (x_min, y_min, width, height)62    """63    if not points:64        return (0, 0, 0, 0)65 66    xs = [p[0] for p in points]67    ys = [p[1] for p in points]68    x_min = min(xs)69    y_min = min(ys)70    return (x_min, y_min, max(xs) - x_min, max(ys) - y_min)71 72 73def polygon_area(points: List[List[float]]) -> float:74    """75    Compute the area of a polygon using the shoelace formula.76 77    Args:78        points: List of [x, y] coordinate pairs79 80    Returns:81        Absolute area of the polygon82    """83    n = len(points)84    if n < 3:85        return 0.086    area = 0.087    for i in range(n):88        j = (i + 1) % n89        area += points[i][0] * points[j][1]90        area -= points[j][0] * points[i][1]91    return abs(area) / 2.092 93 94def normalize_bbox(x: float, y: float, w: float, h: float,95                   img_w: float, img_h: float) -> Tuple[float, float, float, float]:96    """97    Normalize bounding box coordinates to [0, 1] range.98 99    Args:100        x, y: Top-left corner coordinates101        w, h: Width and height102        img_w, img_h: Image dimensions103 104    Returns:105        Tuple of (center_x, center_y, width, height) normalized to [0, 1]106    """107    if img_w <= 0 or img_h <= 0:108        return (0, 0, 0, 0)109    cx = max(0.0, min(1.0, (x + w / 2) / img_w))110    cy = max(0.0, min(1.0, (y + h / 2) / img_h))111    nw = max(0.0, min(1.0, w / img_w))112    nh = max(0.0, min(1.0, h / img_h))113    return (cx, cy, nw, nh)114 115 116def flatten_polygon(points: List[List[float]]) -> List[float]:117    """118    Flatten a list of [x, y] points into a flat coordinate list [x1, y1, x2, y2, ...].119 120    This is the format used by COCO segmentation.121 122    Args:123        points: List of [x, y] coordinate pairs124 125    Returns:126        Flat list of coordinates127    """128    result = []129    for p in points:130        result.extend(p[:2])131    return result132 133 134def extract_image_annotations(annotation: dict) -> List[Tuple[str, List[dict]]]:135    """136    Extract image annotation objects from an annotation record.137 138    Args:139        annotation: Single annotation record with image_annotations field140 141    Returns:142        List of (schema_name, annotation_objects) tuples143    """144    results = []145    for schema_name, objects in annotation.get("image_annotations", {}).items():146        if isinstance(objects, list) and objects:147            results.append((schema_name, objects))148    return results149 150 151def get_image_dimensions(item: dict, default_width: int = 0,152                         default_height: int = 0) -> Tuple[int, int]:153    """154    Extract image dimensions from item metadata.155 156    Checks common field names for image width/height.157 158    Args:159        item: Item data dict160        default_width: Fallback width161        default_height: Fallback height162 163    Returns:164        Tuple of (width, height)165    """166    # Check common field patterns167    width = default_width168    for w_key in ("image_width", "width", "img_width", "w"):169        if w_key in item:170            try:171                width = int(item[w_key])172            except (ValueError, TypeError):173                pass174            break175 176    height = default_height177    for h_key in ("image_height", "height", "img_height", "h"):178        if h_key in item:179            try:180                height = int(item[h_key])181            except (ValueError, TypeError):182                pass183            break184 185    return (width, height)186 187 188def get_image_filename(item: dict) -> Optional[str]:189    """190    Extract image filename from item data.191 192    Args:193        item: Item data dict194 195    Returns:196        Image filename/path string or None197    """198    for key in ("image", "image_path", "image_url", "file_name", "filename", "img"):199        if key in item and item[key]:200            return str(item[key])201    return None202 203 204# ---------------------------------------------------------------------------205# RLE mask utilities (Potato RLE <-> COCO RLE conversion)206# ---------------------------------------------------------------------------207 208 209def decode_rle(rle: dict, width: int, height: int) -> List[int]:210    """211    Decode Potato RLE-encoded mask to a flat binary array (row-major order).212 213    Potato RLE stores counts alternating between 0-pixels and 1-pixels,214    starting with 0s, in row-major (left-to-right, top-to-bottom) order.215 216    Args:217        rle: Dict with 'counts' (list of ints) and 'size' [height, width]218        width: Image width219        height: Image height220 221    Returns:222        Flat list of 0/1 values in row-major order223    """224    counts = rle.get("counts", [])225    total = width * height226    mask = [0] * total227    pos = 0228    val = 0229    for count in counts:230        for _ in range(count):231            if pos < total:232                mask[pos] = val233                pos += 1234        val = 1 - val235    return mask236 237 238def rle_bbox(mask: List[int], width: int, height: int) -> List[float]:239    """240    Compute axis-aligned bounding box [x, y, w, h] from a flat binary mask.241 242    Args:243        mask: Flat list of 0/1 values (row-major)244        width: Image width245        height: Image height246 247    Returns:248        [x_min, y_min, bbox_width, bbox_height] or [0, 0, 0, 0] if empty249    """250    x_min, y_min = width, height251    x_max, y_max = -1, -1252    for i, val in enumerate(mask):253        if val:254            y = i // width255            x = i % width256            if x < x_min:257                x_min = x258            if x > x_max:259                x_max = x260            if y < y_min:261                y_min = y262            if y > y_max:263                y_max = y264    if x_max < 0:265        return [0, 0, 0, 0]266    return [float(x_min), float(y_min),267            float(x_max - x_min + 1), float(y_max - y_min + 1)]268 269 270def rle_area(mask: List[int]) -> int:271    """272    Compute mask area as the count of foreground pixels.273 274    Args:275        mask: Flat list of 0/1 values276 277    Returns:278        Number of 1-pixels279    """280    return sum(mask)281 282 283def _column_major_rle_counts(mask_2d: List[List[int]], height: int,284                              width: int) -> List[int]:285    """286    Read a 2D mask in column-major order and compute RLE counts.287 288    Counts alternate between 0-pixels and 1-pixels, starting with 0s.289 290    Args:291        mask_2d: 2D list [height][width] of 0/1 values292        height: Image height293        width: Image width294 295    Returns:296        List of integer run counts in column-major order297    """298    counts: List[int] = []299    current_val = 0300    current_run = 0301 302    for x in range(width):303        for y in range(height):304            pixel = mask_2d[y][x]305            if pixel == current_val:306                current_run += 1307            else:308                counts.append(current_run)309                current_val = 1 - current_val310                current_run = 1311    counts.append(current_run)312    return counts313 314 315def _encode_coco_rle_string(counts: List[int]) -> str:316    """317    Encode RLE integer counts as a COCO compressed ASCII string.318 319    Implements the exact algorithm from pycocotools maskApi.c rleToString():320    - Delta encoding for i > 2: x = counts[i] - counts[i-2]321    - Each value encoded as 6-bit groups (5 data bits + 1 continuation bit)322    - Each group offset by 48 to produce printable ASCII323    - Signed values supported via arithmetic right shift324 325    Args:326        counts: List of integer run counts327 328    Returns:329        Encoded ASCII string330    """331    chars = []332    for i, cnt in enumerate(counts):333        # Delta encoding: for i > 2, encode difference from counts[i-2]334        x = cnt - counts[i - 2] if i > 2 else cnt335        while True:336            c = x & 0x1F337            x >>= 5338            # If bit 4 set, sign bit is 1 → more groups unless x is all-ones (-1)339            # If bit 4 clear, sign bit is 0 → more groups unless x is all-zeros (0)340            if c & 0x10:341                more = (x != -1)342            else:343                more = (x != 0)344            if more:345                c |= 0x20346            chars.append(chr(c + 48))347            if not more:348                break349    return "".join(chars)350 351 352def rle_to_coco_rle(rle: dict, width: int, height: int) -> Dict[str, Any]:353    """354    Convert Potato RLE to COCO RLE format.355 356    Potato RLE is row-major; COCO RLE is column-major with compressed357    ASCII string encoding.358 359    Args:360        rle: Potato RLE dict with 'counts' and 'size'361        width: Image width362        height: Image height363 364    Returns:365        COCO RLE dict {"counts": "encoded_string", "size": [height, width]}366    """367    # Decode to flat row-major mask368    flat = decode_rle(rle, width, height)369 370    # Reshape to 2D371    mask_2d = []372    for y in range(height):373        row = flat[y * width:(y + 1) * width]374        mask_2d.append(row)375 376    # Compute column-major RLE counts377    col_counts = _column_major_rle_counts(mask_2d, height, width)378 379    # Encode as COCO compressed string380    encoded = _encode_coco_rle_string(col_counts)381 382    return {"counts": encoded, "size": [height, width]}383