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; }