bae705aa97
- 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
842 lines
24 KiB
JavaScript
842 lines
24 KiB
JavaScript
/**
|
|
* QUIXZOOM Guided Capture — AI-driven data collection
|
|
*
|
|
* Fyra lägen:
|
|
* 1. Recording Mode — Filma gata från början till slut
|
|
* 2. Guided Mode — AI ger realtidsinstruktioner
|
|
* 3. Mission Mode — Specifika uppgifter (övergångsställen, lyktstolpar)
|
|
* 4. Adaptive Intelligence Mode — AI planerar nästa bästa observation
|
|
*
|
|
* Teknik: TensorFlow.js, WebRTC, WebSocket
|
|
*/
|
|
|
|
const tf = require('@tensorflow/tfjs-node');
|
|
const EventEmitter = require('events');
|
|
|
|
class GuidedCapture extends EventEmitter {
|
|
constructor(options = {}) {
|
|
super();
|
|
|
|
this.mode = options.mode || 'recording'; // recording, guided, mission, adaptive
|
|
this.confidenceThreshold = options.confidenceThreshold || 0.7;
|
|
this.uncertaintyThreshold = options.uncertaintyThreshold || 0.3;
|
|
this.knowledgeGapThreshold = options.knowledgeGapThreshold || 0.5;
|
|
|
|
// Kunskapsbas — vad vi redan vet om området
|
|
this.knowledgeBase = new Map();
|
|
|
|
// Pågående uppdrag
|
|
this.currentMission = null;
|
|
|
|
// Active learning state
|
|
this.uncertaintyMap = new Map();
|
|
this.informationGain = new Map();
|
|
|
|
// Real-time analysis
|
|
this.frameBuffer = [];
|
|
this.analysisInterval = null;
|
|
}
|
|
|
|
/**
|
|
* ============================================================
|
|
* NIVÅ 1 — RECORDING MODE
|
|
* ============================================================
|
|
* Basinsamling: Zoomern filmar hela gatan.
|
|
* Systemet loggar kontinuerligt GPS, IMU, kompass, höjd.
|
|
*/
|
|
|
|
async startRecording(sessionConfig) {
|
|
this.mode = 'recording';
|
|
|
|
console.log('[GUIDED] Starting Recording Mode');
|
|
console.log('[GUIDED] Config:', sessionConfig);
|
|
|
|
const session = {
|
|
id: this.generateSessionId(),
|
|
startTime: Date.now(),
|
|
mode: 'recording',
|
|
config: sessionConfig,
|
|
frames: [],
|
|
telemetry: [],
|
|
path: [], // GPS-spår
|
|
};
|
|
|
|
// Starta kontinuerlig insamling
|
|
this.analysisInterval = setInterval(() => {
|
|
this.processRecordingFrame(session);
|
|
}, 100); // 10 Hz
|
|
|
|
this.emit('sessionStarted', session);
|
|
|
|
return session;
|
|
}
|
|
|
|
async processRecordingFrame(session) {
|
|
// Samla in telemetri
|
|
const telemetry = await this.captureTelemetry();
|
|
session.telemetry.push(telemetry);
|
|
session.path.push({
|
|
lat: telemetry.gps.latitude,
|
|
lng: telemetry.gps.longitude,
|
|
timestamp: telemetry.timestamp,
|
|
});
|
|
|
|
// Grundläggande analys — spara men analysera inte djupt
|
|
const frame = await this.captureFrame();
|
|
|
|
// Snabb kvalitetskontroll
|
|
const quality = await this.quickQualityCheck(frame);
|
|
|
|
if (quality.isUsable) {
|
|
session.frames.push({
|
|
timestamp: telemetry.timestamp,
|
|
frameId: this.generateFrameId(),
|
|
quality: quality.score,
|
|
gps: telemetry.gps,
|
|
});
|
|
|
|
this.emit('frameCaptured', {
|
|
sessionId: session.id,
|
|
frameId: frame.frameId,
|
|
quality: quality.score,
|
|
});
|
|
}
|
|
|
|
// Uppdatera kunskapsbas med grov information
|
|
this.updateKnowledgeBase(session.path[session.path.length - 1], {
|
|
type: 'recording',
|
|
coverage: this.calculateCoverage(session.path),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* ============================================================
|
|
* NIVÅ 2 — GUIDED MODE
|
|
* ============================================================
|
|
* AI analyserar videoströmmen i realtid och ger instruktioner:
|
|
* "Stanna två sekunder", "Vrid kameran åt vänster",
|
|
* "Gå närmare belysningsstolpen", "Filma undersidan av skylten"
|
|
*/
|
|
|
|
async startGuided(sessionConfig) {
|
|
this.mode = 'guided';
|
|
|
|
console.log('[GUIDED] Starting Guided Mode');
|
|
|
|
const session = {
|
|
id: this.generateSessionId(),
|
|
startTime: Date.now(),
|
|
mode: 'guided',
|
|
config: sessionConfig,
|
|
instructions: [],
|
|
completedInstructions: [],
|
|
};
|
|
|
|
this.analysisInterval = setInterval(() => {
|
|
this.processGuidedFrame(session);
|
|
}, 500); // 2 Hz — mer tid för analys
|
|
|
|
this.emit('sessionStarted', session);
|
|
|
|
return session;
|
|
}
|
|
|
|
async processGuidedFrame(session) {
|
|
const frame = await this.captureFrame();
|
|
const telemetry = await this.captureTelemetry();
|
|
|
|
// Djup analys av nuvarande vy
|
|
const analysis = await this.analyzeFrame(frame);
|
|
|
|
// Identifiera vad som saknas eller är osäkert
|
|
const gaps = this.identifyKnowledgeGaps(analysis);
|
|
const uncertainties = this.identifyUncertainties(analysis);
|
|
|
|
// Generera instruktion
|
|
const instruction = this.generateInstruction(gaps, uncertainties, telemetry);
|
|
|
|
if (instruction) {
|
|
session.instructions.push({
|
|
timestamp: Date.now(),
|
|
...instruction,
|
|
});
|
|
|
|
this.emit('instruction', {
|
|
sessionId: session.id,
|
|
instruction: instruction.text,
|
|
priority: instruction.priority,
|
|
reason: instruction.reason,
|
|
});
|
|
}
|
|
|
|
// Kontrollera om tidigare instruktion är utförd
|
|
this.checkCompletedInstructions(session, analysis);
|
|
}
|
|
|
|
generateInstruction(gaps, uncertainties, telemetry) {
|
|
// Prioritera: osäkerhet > kunskapsgap > generell förbättring
|
|
|
|
if (uncertainties.length > 0) {
|
|
const topUncertainty = uncertainties[0];
|
|
return {
|
|
text: this.instructionForUncertainty(topUncertainty),
|
|
priority: 'high',
|
|
reason: `Low confidence (${(topUncertainty.confidence * 100).toFixed(0)}%) on: ${topUncertainty.object}`,
|
|
target: topUncertainty,
|
|
};
|
|
}
|
|
|
|
if (gaps.length > 0) {
|
|
const topGap = gaps[0];
|
|
return {
|
|
text: this.instructionForGap(topGap),
|
|
priority: 'medium',
|
|
reason: `Missing view: ${topGap.description}`,
|
|
target: topGap,
|
|
};
|
|
}
|
|
|
|
// Generell förbättring
|
|
if (Math.random() < 0.1) { // 10% chans för generell instruktion
|
|
return {
|
|
text: this.randomImprovementInstruction(),
|
|
priority: 'low',
|
|
reason: 'General quality improvement',
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
instructionForUncertainty(uncertainty) {
|
|
const instructions = {
|
|
'blurry': 'Stanna två sekunder och håll kameran stadigare',
|
|
'occluded': 'Flytta dig åt vänster eller höger för bättre sikt',
|
|
'too_far': 'Gå närmare objektet',
|
|
'too_close': 'Backa två meter',
|
|
'poor_lighting': 'Håll kameran högre eller vänta på bättre ljus',
|
|
'reflection': 'Vinkla kameran nedåt för att undvika reflexer',
|
|
};
|
|
|
|
return instructions[uncertainty.type] || 'Försök få en tydligare vy';
|
|
}
|
|
|
|
instructionForGap(gap) {
|
|
const instructions = {
|
|
'missing_back_view': 'Gå runt objektet och filma baksidan',
|
|
'missing_top_view': 'Håll kameran högre och filma uppifrån',
|
|
'missing_detail': 'Zooma in eller gå närmare för detalj',
|
|
'missing_context': 'Backa och filma med mer omgivning',
|
|
'occluded_text': 'Vrid kameran för att undvika skugga på texten',
|
|
'partial_sign': 'Flytta dig för att se hela skylten',
|
|
};
|
|
|
|
return instructions[gap.type] || `Komplettera med: ${gap.description}`;
|
|
}
|
|
|
|
randomImprovementInstruction() {
|
|
const instructions = [
|
|
'Vrid kameran långsamt åt vänster',
|
|
'Filma i tre sekunder till utan att röra dig',
|
|
'Gå närmare trottoarkanten',
|
|
'Håll kameran i brösthöjd',
|
|
'Dokumentera gatans andra sida',
|
|
];
|
|
|
|
return instructions[Math.floor(Math.random() * instructions.length)];
|
|
}
|
|
|
|
/**
|
|
* ============================================================
|
|
* NIVÅ 3 — MISSION MODE
|
|
* ============================================================
|
|
* Specifika uppgifter:
|
|
* "Dokumentera samtliga övergångsställen på denna sträcka"
|
|
* "Fotografera alla gatlyktor mellan punkt A och B"
|
|
* "Kartlägg alla sprickor i vägen"
|
|
*/
|
|
|
|
async startMission(missionConfig) {
|
|
this.mode = 'mission';
|
|
this.currentMission = missionConfig;
|
|
|
|
console.log('[GUIDED] Starting Mission Mode');
|
|
console.log('[GUIDED] Mission:', missionConfig.type);
|
|
|
|
const session = {
|
|
id: this.generateSessionId(),
|
|
startTime: Date.now(),
|
|
mode: 'mission',
|
|
mission: missionConfig,
|
|
targets: [], // Objekt att dokumentera
|
|
completed: [],
|
|
remaining: [],
|
|
};
|
|
|
|
// Generera mål baserat på uppdragstyp
|
|
session.targets = await this.generateMissionTargets(missionConfig);
|
|
session.remaining = [...session.targets];
|
|
|
|
this.analysisInterval = setInterval(() => {
|
|
this.processMissionFrame(session);
|
|
}, 500);
|
|
|
|
this.emit('missionStarted', {
|
|
sessionId: session.id,
|
|
missionType: missionConfig.type,
|
|
targetCount: session.targets.length,
|
|
});
|
|
|
|
return session;
|
|
}
|
|
|
|
async generateMissionTargets(config) {
|
|
const targetGenerators = {
|
|
'crosswalks': () => this.findCrosswalks(config.area),
|
|
'streetlights': () => this.findStreetlights(config.area),
|
|
'potholes': () => this.findPotholes(config.area),
|
|
'signs': () => this.findSigns(config.area),
|
|
'utility_poles': () => this.findUtilityPoles(config.area),
|
|
'buildings': () => this.findBuildings(config.area),
|
|
'sidewalks': () => this.findSidewalks(config.area),
|
|
'cracks': () => this.findCracks(config.area),
|
|
'custom': () => config.targets || [],
|
|
};
|
|
|
|
const generator = targetGenerators[config.type];
|
|
if (!generator) {
|
|
throw new Error(`Unknown mission type: ${config.type}`);
|
|
}
|
|
|
|
return generator();
|
|
}
|
|
|
|
async processMissionFrame(session) {
|
|
const frame = await this.captureFrame();
|
|
const telemetry = await this.captureTelemetry();
|
|
|
|
// Detektera objekt i nuvarande vy
|
|
const detections = await this.detectObjects(frame);
|
|
|
|
// Matcha mot återstående mål
|
|
for (const detection of detections) {
|
|
const match = this.findMatchingTarget(detection, session.remaining);
|
|
|
|
if (match) {
|
|
// Kontrollera kvalitet
|
|
const quality = await this.assessCaptureQuality(frame, detection, match);
|
|
|
|
if (quality.isAdequate) {
|
|
// Markera som komplett
|
|
session.completed.push({
|
|
target: match,
|
|
capture: {
|
|
frameId: this.generateFrameId(),
|
|
timestamp: Date.now(),
|
|
gps: telemetry.gps,
|
|
quality: quality.score,
|
|
},
|
|
});
|
|
|
|
session.remaining = session.remaining.filter(t => t.id !== match.id);
|
|
|
|
this.emit('targetCompleted', {
|
|
sessionId: session.id,
|
|
targetId: match.id,
|
|
remaining: session.remaining.length,
|
|
completed: session.completed.length,
|
|
});
|
|
} else {
|
|
// Be om bättre bild
|
|
this.emit('instruction', {
|
|
sessionId: session.id,
|
|
instruction: `Förbättra bild av ${match.type}: ${quality.issues.join(', ')}`,
|
|
priority: 'high',
|
|
target: match,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Uppdatera progress
|
|
const progress = session.completed.length / session.targets.length;
|
|
|
|
if (session.remaining.length === 0) {
|
|
this.emit('missionCompleted', {
|
|
sessionId: session.id,
|
|
completed: session.completed.length,
|
|
duration: Date.now() - session.startTime,
|
|
});
|
|
|
|
this.stop();
|
|
} else {
|
|
this.emit('progress', {
|
|
sessionId: session.id,
|
|
progress,
|
|
remaining: session.remaining.length,
|
|
nextTarget: this.suggestNextTarget(session, telemetry),
|
|
});
|
|
}
|
|
}
|
|
|
|
suggestNextTarget(session, currentPosition) {
|
|
// Hitta närmaste återstående mål
|
|
let closest = null;
|
|
let closestDistance = Infinity;
|
|
|
|
for (const target of session.remaining) {
|
|
const distance = this.calculateDistance(currentPosition, target.location);
|
|
if (distance < closestDistance) {
|
|
closestDistance = distance;
|
|
closest = target;
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
/**
|
|
* ============================================================
|
|
* NIVÅ 4 — ADAPTIVE INTELLIGENCE MODE
|
|
* ============================================================
|
|
* AI planerar själv nästa bästa observation baserat på:
|
|
* - Vad den redan vet om området
|
|
* - Vad som saknas
|
|
* - Vilken information som ger störst nytta
|
|
*/
|
|
|
|
async startAdaptive(areaConfig) {
|
|
this.mode = 'adaptive';
|
|
|
|
console.log('[GUIDED] Starting Adaptive Intelligence Mode');
|
|
console.log('[GUIDED] Area:', areaConfig);
|
|
|
|
// Ladda existerande kunskap om området
|
|
await this.loadAreaKnowledge(areaConfig);
|
|
|
|
const session = {
|
|
id: this.generateSessionId(),
|
|
startTime: Date.now(),
|
|
mode: 'adaptive',
|
|
area: areaConfig,
|
|
observations: [],
|
|
plannedObservations: [],
|
|
informationGain: new Map(),
|
|
};
|
|
|
|
// Planera initiala observationer
|
|
session.plannedObservations = await this.planObservations(areaConfig);
|
|
|
|
this.analysisInterval = setInterval(() => {
|
|
this.processAdaptiveFrame(session);
|
|
}, 1000); // 1 Hz — mer tid för planering
|
|
|
|
this.emit('adaptiveStarted', {
|
|
sessionId: session.id,
|
|
plannedObservations: session.plannedObservations.length,
|
|
knowledgeCoverage: this.calculateKnowledgeCoverage(),
|
|
});
|
|
|
|
return session;
|
|
}
|
|
|
|
async planObservations(areaConfig) {
|
|
const plans = [];
|
|
|
|
// 1. Identifiera kunskapsgap
|
|
const gaps = this.identifyAreaKnowledgeGaps(areaConfig);
|
|
|
|
// 2. Beräkna informationsvärde för varje gap
|
|
for (const gap of gaps) {
|
|
const informationValue = this.calculateInformationValue(gap);
|
|
|
|
plans.push({
|
|
type: 'close_gap',
|
|
target: gap,
|
|
priority: informationValue,
|
|
estimatedTime: this.estimateObservationTime(gap),
|
|
expectedInformationGain: informationValue,
|
|
});
|
|
}
|
|
|
|
// 3. Identifiera osäkerheter som behöver reduceras
|
|
const uncertainties = this.identifyModelUncertainties(areaConfig);
|
|
|
|
for (const uncertainty of uncertainties) {
|
|
const informationValue = this.calculateUncertaintyReductionValue(uncertainty);
|
|
|
|
plans.push({
|
|
type: 'reduce_uncertainty',
|
|
target: uncertainty,
|
|
priority: informationValue,
|
|
estimatedTime: this.estimateObservationTime(uncertainty),
|
|
expectedInformationGain: informationValue,
|
|
});
|
|
}
|
|
|
|
// 4. Identifiera nya områden att utforska
|
|
const unexplored = this.findUnexploredRegions(areaConfig);
|
|
|
|
for (const region of unexplored) {
|
|
plans.push({
|
|
type: 'explore',
|
|
target: region,
|
|
priority: region.potentialValue,
|
|
estimatedTime: region.estimatedTime,
|
|
expectedInformationGain: region.potentialValue,
|
|
});
|
|
}
|
|
|
|
// Sortera efter informationsvärde / tid (effektivitet)
|
|
plans.sort((a, b) => {
|
|
const efficiencyA = a.expectedInformationGain / a.estimatedTime;
|
|
const efficiencyB = b.expectedInformationGain / b.estimatedTime;
|
|
return efficiencyB - efficiencyA;
|
|
});
|
|
|
|
return plans;
|
|
}
|
|
|
|
async processAdaptiveFrame(session) {
|
|
const frame = await this.captureFrame();
|
|
const telemetry = await this.captureTelemetry();
|
|
|
|
// Analysera nuvarande observation
|
|
const analysis = await this.analyzeFrame(frame);
|
|
|
|
// Uppdatera kunskapsbas
|
|
this.updateKnowledgeBase(telemetry.gps, {
|
|
timestamp: Date.now(),
|
|
analysis,
|
|
quality: await this.assessCaptureQuality(frame, null, null),
|
|
});
|
|
|
|
session.observations.push({
|
|
timestamp: Date.now(),
|
|
gps: telemetry.gps,
|
|
analysis: analysis.summary,
|
|
});
|
|
|
|
// Omplanera baserat på ny information
|
|
if (session.observations.length % 10 === 0) { // Omplanera var 10:e observation
|
|
const newPlans = await this.planObservations(session.area);
|
|
|
|
// Kombinera med befintliga planer
|
|
session.plannedObservations = this.mergePlans(
|
|
session.plannedObservations,
|
|
newPlans
|
|
);
|
|
|
|
this.emit('planUpdated', {
|
|
sessionId: session.id,
|
|
newPlans: newPlans.length,
|
|
topPriority: session.plannedObservations[0],
|
|
});
|
|
}
|
|
|
|
// Ge nästa instruktion
|
|
const nextPlan = session.plannedObservations[0];
|
|
|
|
if (nextPlan) {
|
|
this.emit('adaptiveInstruction', {
|
|
sessionId: session.id,
|
|
instruction: this.adaptiveInstructionText(nextPlan),
|
|
priority: nextPlan.priority,
|
|
reason: `Information gain: ${(nextPlan.expectedInformationGain * 100).toFixed(0)}%`,
|
|
estimatedTime: nextPlan.estimatedTime,
|
|
});
|
|
}
|
|
|
|
// Uppdatera coverage
|
|
const coverage = this.calculateKnowledgeCoverage();
|
|
|
|
this.emit('adaptiveProgress', {
|
|
sessionId: session.id,
|
|
observations: session.observations.length,
|
|
coverage,
|
|
remainingPlans: session.plannedObservations.length,
|
|
});
|
|
}
|
|
|
|
adaptiveInstructionText(plan) {
|
|
const templates = {
|
|
'close_gap': `Dokumentera ${plan.target.description} — kunskap saknas`,
|
|
'reduce_uncertainty': `Förbättra bild av ${plan.target.object} — osäkerhet: ${(plan.target.uncertainty * 100).toFixed(0)}%`,
|
|
'explore': `Utforska ${plan.target.description} — potentiellt högt värde`,
|
|
};
|
|
|
|
return templates[plan.type] || `Prioriterad observation: ${plan.target.description}`;
|
|
}
|
|
|
|
calculateInformationValue(gap) {
|
|
// Faktorer som påverkar informationsvärde:
|
|
// 1. Hur kritisk är informationen?
|
|
// 2. Hur sällsynt är denna typ av data?
|
|
// 3. Hur mycket skulle modellen förbättras?
|
|
|
|
const criticality = gap.criticality || 0.5;
|
|
const rarity = gap.rarity || 0.5;
|
|
const modelImpact = gap.modelImpact || 0.5;
|
|
|
|
return (criticality + rarity + modelImpact) / 3;
|
|
}
|
|
|
|
calculateUncertaintyReductionValue(uncertainty) {
|
|
// Värdet av att reducera osäkerhet beror på:
|
|
// 1. Hur osäker är modellen?
|
|
// 2. Hur viktigt är objektet?
|
|
// 3. Hur mycket data finns redan?
|
|
|
|
const uncertaintyLevel = uncertainty.confidence < 0.3 ? 1.0 :
|
|
uncertainty.confidence < 0.5 ? 0.7 : 0.4;
|
|
|
|
const importance = uncertainty.importance || 0.5;
|
|
const dataScarcity = uncertainty.existingSamples < 10 ? 1.0 :
|
|
uncertainty.existingSamples < 100 ? 0.7 : 0.4;
|
|
|
|
return (uncertaintyLevel + importance + dataScarcity) / 3;
|
|
}
|
|
|
|
/**
|
|
* ============================================================
|
|
* HJÄLPMETODER
|
|
* ============================================================
|
|
*/
|
|
|
|
async captureTelemetry() {
|
|
// Simulerad telemetri — i praktiken från iPhone sensors
|
|
return {
|
|
timestamp: Date.now(),
|
|
gps: {
|
|
latitude: 13.7563 + (Math.random() - 0.5) * 0.01,
|
|
longitude: 100.5018 + (Math.random() - 0.5) * 0.01,
|
|
accuracy: 5 + Math.random() * 10,
|
|
altitude: 10 + Math.random() * 5,
|
|
},
|
|
imu: {
|
|
accelerometer: { x: Math.random(), y: Math.random(), z: Math.random() },
|
|
gyroscope: { x: Math.random(), y: Math.random(), z: Math.random() },
|
|
},
|
|
compass: Math.random() * 360,
|
|
device: {
|
|
model: 'iPhone14,2',
|
|
os: 'iOS 17.1',
|
|
},
|
|
};
|
|
}
|
|
|
|
async captureFrame() {
|
|
// Simulerad frame — i praktiken från kamera
|
|
return {
|
|
frameId: this.generateFrameId(),
|
|
timestamp: Date.now(),
|
|
data: null, // Buffer med bilddata
|
|
};
|
|
}
|
|
|
|
async analyzeFrame(frame) {
|
|
// Simulerad analys — i praktiken AI-modeller
|
|
return {
|
|
objects: [],
|
|
scene: 'urban_street',
|
|
confidence: 0.8,
|
|
summary: 'Street scene with buildings and vehicles',
|
|
};
|
|
}
|
|
|
|
async quickQualityCheck(frame) {
|
|
// Snabb kvalitetskontroll
|
|
const score = 0.7 + Math.random() * 0.3;
|
|
return {
|
|
score,
|
|
isUsable: score > 0.5,
|
|
};
|
|
}
|
|
|
|
async assessCaptureQuality(frame, detection, target) {
|
|
// Djupare kvalitetsbedömning
|
|
const issues = [];
|
|
|
|
if (detection) {
|
|
if (detection.confidence < 0.7) issues.push('low_confidence');
|
|
if (detection.occlusion > 0.3) issues.push('occluded');
|
|
if (detection.blur > 0.2) issues.push('blurry');
|
|
}
|
|
|
|
return {
|
|
score: issues.length === 0 ? 0.9 : 0.9 - (issues.length * 0.2),
|
|
isAdequate: issues.length < 2,
|
|
issues,
|
|
};
|
|
}
|
|
|
|
identifyKnowledgeGaps(analysis) {
|
|
// Identifiera vad som saknas i nuvarande vy
|
|
const gaps = [];
|
|
|
|
// Exempel: saknas baksida?
|
|
if (!analysis.backView) {
|
|
gaps.push({
|
|
type: 'missing_back_view',
|
|
description: 'Baksida av objektet inte synlig',
|
|
criticality: 0.6,
|
|
});
|
|
}
|
|
|
|
// Exempel: saknas detalj?
|
|
if (analysis.objects?.some(o => o.confidence < 0.5)) {
|
|
gaps.push({
|
|
type: 'missing_detail',
|
|
description: 'Detaljer osynliga på grund av avstånd',
|
|
criticality: 0.7,
|
|
});
|
|
}
|
|
|
|
return gaps;
|
|
}
|
|
|
|
identifyUncertainties(analysis) {
|
|
// Identifiera osäkra detektioner
|
|
const uncertainties = [];
|
|
|
|
for (const obj of analysis.objects || []) {
|
|
if (obj.confidence < this.confidenceThreshold) {
|
|
uncertainties.push({
|
|
type: 'low_confidence',
|
|
object: obj.class,
|
|
confidence: obj.confidence,
|
|
});
|
|
}
|
|
}
|
|
|
|
return uncertainties;
|
|
}
|
|
|
|
updateKnowledgeBase(location, data) {
|
|
const key = `${location.lat.toFixed(4)},${location.lng.toFixed(4)}`;
|
|
|
|
if (!this.knowledgeBase.has(key)) {
|
|
this.knowledgeBase.set(key, []);
|
|
}
|
|
|
|
this.knowledgeBase.get(key).push(data);
|
|
}
|
|
|
|
calculateKnowledgeCoverage() {
|
|
// Beräkna hur mycket av området som är täckt
|
|
const totalCells = 100; // Förenklad grid
|
|
const coveredCells = this.knowledgeBase.size;
|
|
|
|
return coveredCells / totalCells;
|
|
}
|
|
|
|
calculateCoverage(path) {
|
|
// Beräkna spatial täckning av en väg
|
|
if (path.length < 2) return 0;
|
|
|
|
let totalDistance = 0;
|
|
for (let i = 1; i < path.length; i++) {
|
|
totalDistance += this.calculateDistance(path[i-1], path[i]);
|
|
}
|
|
|
|
return totalDistance;
|
|
}
|
|
|
|
calculateDistance(a, b) {
|
|
// Haversine-formel
|
|
const R = 6371e3; // Jordens radie i meter
|
|
const φ1 = a.lat * Math.PI / 180;
|
|
const φ2 = b.lat * Math.PI / 180;
|
|
const Δφ = (b.lat - a.lat) * Math.PI / 180;
|
|
const Δλ = (b.lng - a.lng) * Math.PI / 180;
|
|
|
|
const x = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
|
|
Math.cos(φ1) * Math.cos(φ2) *
|
|
Math.sin(Δλ/2) * Math.sin(Δλ/2);
|
|
const c = 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1-x));
|
|
|
|
return R * c;
|
|
}
|
|
|
|
generateSessionId() {
|
|
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
generateFrameId() {
|
|
return `frame_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
stop() {
|
|
if (this.analysisInterval) {
|
|
clearInterval(this.analysisInterval);
|
|
this.analysisInterval = null;
|
|
}
|
|
|
|
this.emit('sessionEnded', {
|
|
mode: this.mode,
|
|
duration: Date.now() - this.startTime,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Exportera
|
|
module.exports = GuidedCapture;
|
|
|
|
// Demo
|
|
if (require.main === module) {
|
|
const capture = new GuidedCapture();
|
|
|
|
capture.on('instruction', (data) => {
|
|
console.log(`[INSTRUCTION] ${data.instruction}`);
|
|
});
|
|
|
|
capture.on('missionStarted', (data) => {
|
|
console.log(`[MISSION] Started: ${data.missionType}, ${data.targetCount} targets`);
|
|
});
|
|
|
|
capture.on('adaptiveStarted', (data) => {
|
|
console.log(`[ADAPTIVE] Started: ${data.plannedObservations} planned observations`);
|
|
});
|
|
|
|
// Testa olika lägen
|
|
console.log('=== TESTING GUIDED CAPTURE ===\n');
|
|
|
|
// Test Recording Mode
|
|
console.log('1. Recording Mode');
|
|
capture.startRecording({ area: 'sukhumvit_22' });
|
|
|
|
setTimeout(() => {
|
|
capture.stop();
|
|
|
|
// Test Guided Mode
|
|
console.log('\n2. Guided Mode');
|
|
capture.startGuided({ area: 'sukhumvit_22' });
|
|
|
|
setTimeout(() => {
|
|
capture.stop();
|
|
|
|
// Test Mission Mode
|
|
console.log('\n3. Mission Mode');
|
|
capture.startMission({
|
|
type: 'crosswalks',
|
|
area: 'sukhumvit_22',
|
|
});
|
|
|
|
setTimeout(() => {
|
|
capture.stop();
|
|
|
|
// Test Adaptive Mode
|
|
console.log('\n4. Adaptive Intelligence Mode');
|
|
capture.startAdaptive({
|
|
bounds: { lat: 13.75, lng: 100.50, radius: 500 },
|
|
});
|
|
|
|
setTimeout(() => {
|
|
capture.stop();
|
|
console.log('\n=== TEST COMPLETE ===');
|
|
}, 3000);
|
|
}, 3000);
|
|
}, 3000);
|
|
}, 3000);
|
|
}
|