File size: 3,289 Bytes
6c30253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
const ALLOWED_KEYS = new Set(['image_id', 'label', 'bbox_2d', 'point_2d']);

function normalizeCoordinateArray(value, length) {
  if (!Array.isArray(value) || value.length !== length ||
    !value.every(coordinate => typeof coordinate === 'number' && Number.isFinite(coordinate))) return null;
  if (value.every(coordinate => coordinate >= 0 && coordinate <= 1)) {
    return value.map(coordinate => Math.round(coordinate * 1000));
  }
  if (value.every(coordinate => Number.isInteger(coordinate) && coordinate >= 0 && coordinate <= 1000)) {
    return value.slice();
  }
  return null;
}

function parseBareBoxes(text) {
  const number = '(-?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?)';
  const pattern = new RegExp(`\\[\\s*${number}\\s*,\\s*${number}\\s*,\\s*${number}\\s*,\\s*${number}\\s*\\]`, 'g');
  const boxes = [];
  for (const match of text.matchAll(pattern)) {
    const coordinates = normalizeCoordinateArray(match.slice(1, 5).map(Number), 4);
    if (!coordinates) continue;
    const [xmin, ymin, xmax, ymax] = coordinates;
    if (xmax <= xmin || ymax <= ymin) continue;
    boxes.push({
      imageId: 0,
      label: boxes.length ? `Bounding box ${boxes.length + 1}` : 'Bounding box',
      type: 'box',
      coordinates,
    });
  }
  return boxes;
}

export function parseGroundingResponse(text, imageCount) {
  if (!Number.isInteger(imageCount) || imageCount < 1 || typeof text !== 'string') return null;
  let parsed;
  try {
    parsed = JSON.parse(text.trim());
  } catch {
    const boxes = parseBareBoxes(text);
    return boxes.length ? boxes : null;
  }
  if (!Array.isArray(parsed)) {
    const boxes = parseBareBoxes(text);
    return boxes.length ? boxes : null;
  }

  const structuredCandidate = parsed.length === 0 || parsed.every(item => item && typeof item === 'object' && !Array.isArray(item));
  if (!structuredCandidate) {
    const boxes = parseBareBoxes(text);
    return boxes.length ? boxes : null;
  }

  const normalized = [];
  let structured = true;
  for (const item of parsed) {
    if (!item || typeof item !== 'object' || Array.isArray(item) ||
      Object.keys(item).some(key => !ALLOWED_KEYS.has(key)) ||
      !Number.isInteger(item.image_id) || item.image_id < 0 || item.image_id >= imageCount ||
      typeof item.label !== 'string' || !item.label.trim()) {
      structured = false;
      break;
    }

    const hasBox = Object.hasOwn(item, 'bbox_2d');
    const hasPoint = Object.hasOwn(item, 'point_2d');
    if (hasBox === hasPoint) {
      structured = false;
      break;
    }

    if (hasBox) {
      const coordinates = normalizeCoordinateArray(item.bbox_2d, 4);
      if (!coordinates) {
        structured = false;
        break;
      }
      const [xmin, ymin, xmax, ymax] = coordinates;
      if (xmax <= xmin || ymax <= ymin) {
        structured = false;
        break;
      }
      normalized.push({ imageId: item.image_id, label: item.label.trim(), type: 'box', coordinates });
    } else {
      const coordinates = normalizeCoordinateArray(item.point_2d, 2);
      if (!coordinates) {
        structured = false;
        break;
      }
      normalized.push({ imageId: item.image_id, label: item.label.trim(), type: 'point', coordinates });
    }
  }
  if (structured) return normalized;
  return null;
}