LFM2.5-VL-3B-WebGPU / src /grounding.js
shubeydoo's picture
Initial release
6c30253
Raw
History Blame Contribute Delete
3.29 kB
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;
}