/** * QUIXZOOM Video Pipeline — Dataset Builder * * Bygger träningsdataset från AI-annoterade frames: * - Roads * - Sidewalks * - Crosswalks * - Buildings * - Trees * - Utility Infrastructure * - Traffic Infrastructure * - Signage * - Lighting * - Accessibility * - Commercial Areas * - Urban Density * * Output: JSONL-format för ML-träning */ const fs = require('fs').promises; const path = require('path'); /** * Dataset-konfiguration */ const DATASET_CONFIG = { version: '1.0.0', format: 'jsonl', splits: { train: 0.7, validation: 0.15, test: 0.15, }, }; /** * Bygg dataset från annoterade frames */ async function buildDataset(inputDir, outputDir, options = {}) { console.log(`[DATASET] Building dataset from: ${inputDir}`); // Läs alla annotations const annotations = await loadAnnotations(inputDir); console.log(`[DATASET] Loaded ${annotations.length} annotations`); // Filtrera efter kvalitet const qualityFilter = options.minQuality || 60; const filtered = annotations.filter(a => (a.aiAnalysis?.qualityScore?.overall || 0) >= qualityFilter ); console.log(`[DATASET] ${filtered.length} annotations passed quality filter (${qualityFilter}+)`); // Bygg dataset för varje kategori const datasets = { roads: buildRoadDataset(filtered), sidewalks: buildSidewalkDataset(filtered), crosswalks: buildCrosswalkDataset(filtered), buildings: buildBuildingDataset(filtered), trees: buildTreeDataset(filtered), utilityInfrastructure: buildUtilityDataset(filtered), trafficInfrastructure: buildTrafficDataset(filtered), signage: buildSignageDataset(filtered), lighting: buildLightingDataset(filtered), accessibility: buildAccessibilityDataset(filtered), commercialAreas: buildCommercialDataset(filtered), urbanDensity: buildDensityDataset(filtered), }; // Spara dataset await saveDatasets(datasets, outputDir); // Generera statistik const stats = generateStats(datasets); await fs.writeFile( path.join(outputDir, 'stats.json'), JSON.stringify(stats, null, 2) ); console.log(`[DATASET] Complete. Saved to: ${outputDir}`); return { datasets, stats }; } /** * Läs annotations från katalog */ async function loadAnnotations(inputDir) { const files = await fs.readdir(inputDir); const jsonFiles = files.filter(f => f.endsWith('.json')); const annotations = []; for (const file of jsonFiles) { const content = await fs.readFile(path.join(inputDir, file), 'utf8'); annotations.push(JSON.parse(content)); } return annotations; } /** * Bygg väg-dataset */ function buildRoadDataset(annotations) { const samples = []; for (const annotation of annotations) { const roadSurface = annotation.roadSurface; const segmentation = annotation.segmentation; if (!roadSurface && !segmentation) continue; const sample = { image: annotation.framePath, labels: { surface_type: roadSurface?.topSurface?.type || 'unknown', surface_confidence: roadSurface?.topSurface?.confidence || 0, has_cracks: roadSurface?.hasCracks || false, crack_severity: annotation.cracks?.severity || 'none', road_pixels: segmentation?.distribution?.road || 0, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg trottoar-dataset */ function buildSidewalkDataset(annotations) { const samples = []; for (const annotation of annotations) { const sidewalk = annotation.sidewalk; const segmentation = annotation.segmentation; if (!sidewalk && !segmentation) continue; const sample = { image: annotation.framePath, labels: { present: sidewalk?.present || false, width: sidewalk?.width || 0, condition: sidewalk?.condition || 'unknown', wheelchair_accessible: sidewalk?.accessibility?.wheelchair || false, tactile_paving: sidewalk?.accessibility?.tactile_paving || false, sidewalk_pixels: segmentation?.distribution?.sidewalk || 0, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg övergångsställe-dataset */ function buildCrosswalkDataset(annotations) { const samples = []; for (const annotation of annotations) { const objects = annotation.objects?.objects || []; const trafficSigns = annotation.trafficSigns || []; const hasCrosswalk = objects.some(o => o.class === 'pedestrian crossing') || trafficSigns.some(s => s.type === 'pedestrian_crossing'); if (!hasCrosswalk) continue; const sample = { image: annotation.framePath, labels: { present: true, type: 'zebra', // Kan detekteras mer specifikt traffic_light_nearby: objects.some(o => o.class === 'traffic light'), sign_present: trafficSigns.some(s => s.type === 'pedestrian_crossing'), }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg byggnads-dataset */ function buildBuildingDataset(annotations) { const samples = []; for (const annotation of annotations) { const buildings = annotation.buildings; const segmentation = annotation.segmentation; const ocr = annotation.ocr; const sample = { image: annotation.framePath, labels: { count: buildings?.count || 0, types: buildings?.buildings?.map(b => b.type) || [], heights: buildings?.buildings?.map(b => b.height) || [], building_pixels: segmentation?.distribution?.building || 0, has_hotel: ocr?.storefronts?.some(s => /HOTEL/i.test(s)) || false, has_restaurant: ocr?.storefronts?.some(s => /RESTAURANT/i.test(s)) || false, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg träd-dataset */ function buildTreeDataset(annotations) { const samples = []; for (const annotation of annotations) { const vegetation = annotation.vegetation; const segmentation = annotation.segmentation; const sample = { image: annotation.framePath, labels: { tree_count: vegetation?.treeCount || 0, coverage: vegetation?.coverage || 0, health: vegetation?.health || 'unknown', vegetation_pixels: segmentation?.distribution?.vegetation || 0, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg utility-infrastruktur-dataset */ function buildUtilityDataset(annotations) { const samples = []; for (const annotation of annotations) { const poles = annotation.poles || []; const utilityBoxes = annotation.utilityBoxes || []; const sample = { image: annotation.framePath, labels: { pole_count: poles.length, pole_types: poles.map(p => p.type), utility_box_count: utilityBoxes.length, utility_box_types: utilityBoxes.map(b => b.type), has_overhead_wires: poles.some(p => p.type === 'utility_pole'), }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg trafik-infrastruktur-dataset */ function buildTrafficDataset(annotations) { const samples = []; for (const annotation of annotations) { const objects = annotation.objects?.objects || []; const trafficSigns = annotation.trafficSigns || []; const sample = { image: annotation.framePath, labels: { traffic_light_count: objects.filter(o => o.class === 'traffic light').length, traffic_sign_count: trafficSigns.length, sign_types: trafficSigns.map(s => s.type), has_stop_sign: trafficSigns.some(s => s.type === 'stop'), has_speed_limit: trafficSigns.some(s => s.type === 'speed_limit'), }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg skylt-dataset */ function buildSignageDataset(annotations) { const samples = []; for (const annotation of annotations) { const ocr = annotation.ocr; const trafficSigns = annotation.trafficSigns; const sample = { image: annotation.framePath, labels: { text_detected: ocr?.text || '', text_confidence: ocr?.confidence || 0, signs: ocr?.signs || [], storefronts: ocr?.storefronts || [], traffic_signs: trafficSigns?.map(s => s.type) || [], }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg belysnings-dataset */ function buildLightingDataset(annotations) { const samples = []; for (const annotation of annotations) { const lighting = annotation.lighting; const sample = { image: annotation.framePath, labels: { street_light_count: lighting?.streetLights?.length || 0, building_light_count: lighting?.buildingLights?.length || 0, time_of_day: lighting?.timeOfDay || 'unknown', natural_light_level: lighting?.naturalLight || 0, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg tillgänglighets-dataset */ function buildAccessibilityDataset(annotations) { const samples = []; for (const annotation of annotations) { const accessibility = annotation.accessibility; const sidewalk = annotation.sidewalk; const sample = { image: annotation.framePath, labels: { wheelchair_ramp: accessibility?.wheelchairRamp || false, tactile_paving: accessibility?.tactilePaving || false, audible_signals: accessibility?.audibleSignals || false, braille_signage: accessibility?.brailleSignage || false, accessible_parking: accessibility?.accessibleParking || false, overall_score: accessibility?.overallScore || 0, sidewalk_width: sidewalk?.width || 0, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg kommersiellt område-dataset */ function buildCommercialDataset(annotations) { const samples = []; for (const annotation of annotations) { const storefronts = annotation.storefronts || []; const ocr = annotation.ocr; const sample = { image: annotation.framePath, labels: { business_count: storefronts.length, business_types: storefronts.map(s => s.type), business_names: storefronts.map(s => s.name), open_businesses: storefronts.filter(s => s.open).length, has_hotel: ocr?.storefronts?.some(s => /HOTEL/i.test(s)) || false, has_restaurant: ocr?.storefronts?.some(s => /RESTAURANT/i.test(s)) || false, has_cafe: ocr?.storefronts?.some(s => /CAFE/i.test(s)) || false, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Bygg urban densitet-dataset */ function buildDensityDataset(annotations) { const samples = []; for (const annotation of annotations) { const objects = annotation.objects; const segmentation = annotation.segmentation; const sample = { image: annotation.framePath, labels: { person_count: objects?.grouped?.person?.length || 0, vehicle_count: ( (objects?.grouped?.car?.length || 0) + (objects?.grouped?.motorcycle?.length || 0) + (objects?.grouped?.bus?.length || 0) + (objects?.grouped?.truck?.length || 0) ), building_density: segmentation?.distribution?.building || 0, vegetation_density: segmentation?.distribution?.vegetation || 0, road_density: segmentation?.distribution?.road || 0, total_objects: objects?.totalCount || 0, }, metadata: { city: annotation.city, country: annotation.country, timestamp: annotation.timestamp, }, }; samples.push(sample); } return { samples, count: samples.length }; } /** * Spara dataset i JSONL-format */ async function saveDatasets(datasets, outputDir) { await fs.mkdir(outputDir, { recursive: true }); for (const [name, dataset] of Object.entries(datasets)) { if (dataset.count === 0) continue; const filePath = path.join(outputDir, `${name}.jsonl`); const lines = dataset.samples.map(s => JSON.stringify(s)).join('\n'); await fs.writeFile(filePath, lines + '\n'); console.log(`[DATASET] Saved ${name}: ${dataset.count} samples`); } } /** * Generera statistik */ function generateStats(datasets) { const stats = { version: DATASET_CONFIG.version, createdAt: new Date().toISOString(), totalSamples: 0, categories: {}, cities: new Set(), countries: new Set(), }; for (const [name, dataset] of Object.entries(datasets)) { stats.categories[name] = { count: dataset.count, }; stats.totalSamples += dataset.count; // Samla unika städer och länder for (const sample of dataset.samples) { if (sample.metadata?.city) stats.cities.add(sample.metadata.city); if (sample.metadata?.country) stats.countries.add(sample.metadata.country); } } stats.cities = Array.from(stats.cities); stats.countries = Array.from(stats.countries); return stats; } /** * Splitsa dataset i train/validation/test */ async function splitDataset(dataset, splits = DATASET_CONFIG.splits) { const shuffled = shuffleArray([...dataset.samples]); const trainEnd = Math.floor(shuffled.length * splits.train); const valEnd = trainEnd + Math.floor(shuffled.length * splits.validation); return { train: shuffled.slice(0, trainEnd), validation: shuffled.slice(trainEnd, valEnd), test: shuffled.slice(valEnd), }; } /** * Blanda array */ function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } module.exports = { buildDataset, buildRoadDataset, buildSidewalkDataset, buildCrosswalkDataset, buildBuildingDataset, buildTreeDataset, buildUtilityDataset, buildTrafficDataset, buildSignageDataset, buildLightingDataset, buildAccessibilityDataset, buildCommercialDataset, buildDensityDataset, splitDataset, };