/** * POST /v1/extract — Extrahering av data från bilder * OCR-liknande extraktion via AI + bildanalys */ import { Router } from 'express'; import sharp from 'sharp'; import { fetchImage, hashInput, saveResult, genReqId, requireAuth } from './utils.mjs'; const router = Router(); const OLLAMA_BASE = process.env.OLLAMA_URL || 'http://172.31.40.60:11434'; const GROQ_KEY = process.env.GROQ_API_KEY || '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'; async function extractData(buffer, extractType) { const base64 = buffer.toString('base64'); // Image analysis for structure const { data, info } = await sharp(buffer).resize(128, 128).raw().toBuffer({ resolveWithObject: true }); const w = info.width, h = info.height; // Detect text-like regions (high contrast horizontal bands) const textRegions = []; for (let y = 2; y < h - 2; y++) { let rowContrast = 0; for (let x = 1; x < w - 1; x++) { const i = (y * w + x) * 3; const gray = (data[i] + data[i+1] + data[i+2]) / 3; const prevGray = (data[i-3] + data[i-2] + data[i-1]) / 3; rowContrast += Math.abs(gray - prevGray); } if (rowContrast / w > 15) { textRegions.push({ y, contrast: Math.round(rowContrast / w) }); } } // Try AI extraction let extracted = null; try { const promptMap = { text: 'Extract all visible text from this image. Return as plain text.', faces: 'Count the number of faces in this image. Return only the number.', objects: 'List all objects visible in this image as a comma-separated list.', colors: 'List the dominant colors in this image as hex codes.', metadata: 'Describe the image type, approximate dimensions, and visible content.', }; const r = await fetch(`${OLLAMA_BASE}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'amos-r2:latest', prompt: promptMap[extractType] || promptMap.text, images: [base64], stream: false, options: { num_predict: 300 } }), signal: AbortSignal.timeout(15000) }); if (r.ok) { const d = await r.json(); extracted = d.response?.trim(); } } catch (e) { console.log('[extract] ollama failed:', e.message); } // Fallback extraction if (!extracted) { const fallbacks = { text: `Detected ${textRegions.length} text-like horizontal bands in the image. Text extraction requires OCR engine.`, faces: 'Face count requires face detection model. Use /v1/detect endpoint.', objects: 'Object list requires object detection model. Use /v1/detect endpoint.', colors: extractDominantColors(data, w, h), metadata: `Image analyzed: ${w*4}x${h*4}px estimated, ${textRegions.length > 10 ? 'text-heavy' : 'image-heavy'} content.`, }; extracted = fallbacks[extractType] || fallbacks.text; } return { extracted, text_regions_detected: textRegions.length, has_text: textRegions.length > 5, }; } function extractDominantColors(data, w, h) { const colorMap = {}; for (let i = 0; i < w * h; i++) { const r = Math.round(data[i*3] / 32) * 32; const g = Math.round(data[i*3+1] / 32) * 32; const b = Math.round(data[i*3+2] / 32) * 32; const key = `#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`; colorMap[key] = (colorMap[key] || 0) + 1; } return Object.entries(colorMap) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([color, count]) => ({ color, coverage_pct: Math.round(count / (w * h) * 100) })); } router.post('/', requireAuth, async (req, res) => { const requestId = genReqId(); const start = Date.now(); try { const { image_url, image_base64, extract_type = 'text' } = req.body || {}; const img = await fetchImage({ image_url, image_base64 }); const inputHash = hashInput(img.buffer); const { extracted, text_regions_detected, has_text } = await extractData(img.buffer, extract_type); const confidence = has_text ? 0.8 : 0.5; const result = { ok: true, endpoint: 'extract', request_id: requestId, extract_type, extracted_data: extracted, text_regions_detected, has_text_content: has_text, confidence, inference_time_ms: Date.now() - start, }; await saveResult('extract', requestId, inputHash, result, confidence, { extract_type, source: img.source }); res.json(result); } catch (e) { console.error('[extract]', e); res.status(500).json({ ok: false, error: e.message, request_id: requestId }); } }); export default router;