Files
boc/quixzoom-video-pipeline/dataset-builder/builder.js
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

595 lines
15 KiB
JavaScript

/**
* 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,
};