const COORDINATE = '(\\d+(?:\\.\\d+)?)'; const HEADER_PATTERN = new RegExp(`^image_index\\s*=\\s*(\\d+)\\s+([^\\[\\]\\n]+?)\\s+\\[\\s*${COORDINATE}\\s*,\\s*${COORDINATE}\\s*,\\s*${COORDINATE}\\s*,\\s*${COORDINATE}\\s*\\]\\s*$`); function normalizeRegion(match, content, imageCount) { if (!content) return null; const [, rawImageIndex, rawLabel, ...rawCoordinates] = match; const imageId = Number(rawImageIndex); const label = rawLabel.trim(); let coordinates = rawCoordinates.map(Number); if (imageId < 0 || imageId >= imageCount || !label) return null; if (coordinates.every(value => value >= 0 && value <= 1)) { coordinates = coordinates.map(value => Math.round(value * 1000)); } else if (coordinates.some(value => !Number.isInteger(value) || value < 0 || value > 1000)) { return null; } const [xmin, ymin, xmax, ymax] = coordinates; if (xmax <= xmin || ymax <= ymin) return null; return { imageId, label, type: 'box', coordinates, content }; } export function parseDocumentRegions(text, imageCount) { if (!Number.isInteger(imageCount) || imageCount < 1 || typeof text !== 'string' || !text.trim()) return null; const lines = text.trim().replace(/\r\n?/g, '\n').split('\n'); const regions = []; let currentMatch = null; let contentLines = []; let sawHeader = false; const finishCurrentRegion = () => { if (!currentMatch) return; const region = normalizeRegion(currentMatch, contentLines.join('\n').trim(), imageCount); if (region) regions.push(region); }; for (const line of lines) { const match = HEADER_PATTERN.exec(line); if (match) { if (currentMatch) { finishCurrentRegion(); } else if (!sawHeader && contentLines.some(value => value.trim())) { return null; } sawHeader = true; currentMatch = match; contentLines = []; } else if (/^\s*image_index\b/.test(line)) { finishCurrentRegion(); sawHeader = true; currentMatch = null; contentLines = []; } else if (currentMatch || !sawHeader) { contentLines.push(line); } else { // Ignore content belonging to an incomplete or malformed region while // retaining complete regions that were already parsed. } } finishCurrentRegion(); return regions.length ? regions : null; }