feat: add map-driven orthophoto analysis
This commit is contained in:
@@ -229,12 +229,18 @@ export function useDetectionWorkflow({
|
||||
}
|
||||
}
|
||||
|
||||
const executeDetection = async (projectId: string, datasetId: string, manifestPath: string | null) => {
|
||||
const executeDetection = async (
|
||||
projectId: string,
|
||||
datasetId: string,
|
||||
manifestPath: string | null,
|
||||
modelId = selectedDetectionModelId,
|
||||
modelAssetId = selectedModelAssetId,
|
||||
) => {
|
||||
const result = await detectionApi.run({
|
||||
project_id: projectId,
|
||||
dataset_id: datasetId,
|
||||
model_id: selectedDetectionModelId,
|
||||
model_asset_id: selectedModelAssetId || null,
|
||||
model_id: modelId,
|
||||
model_asset_id: modelAssetId || null,
|
||||
confidence_threshold: detectionConfidenceThreshold,
|
||||
tile_manifest_path: manifestPath,
|
||||
parameters_json: {},
|
||||
@@ -303,24 +309,31 @@ export function useDetectionWorkflow({
|
||||
}
|
||||
}
|
||||
|
||||
const prepareAndRunDetection = async (): Promise<boolean> => {
|
||||
const prepareAndRunDetection = async (
|
||||
datasetIdOverride?: string,
|
||||
modelIdOverride?: string,
|
||||
): Promise<DetectionRunResponse | null> => {
|
||||
if (!selectedProjectId) {
|
||||
setDetectionRunError('De regionale werkruimte is nog niet geladen')
|
||||
return false
|
||||
return null
|
||||
}
|
||||
const datasetId = selectedDetectionDatasetId || rasterDatasets[0]?.id
|
||||
const datasetId = datasetIdOverride || selectedDetectionDatasetId || rasterDatasets[0]?.id
|
||||
if (!datasetId) {
|
||||
setDetectionRunError('Kies of voeg eerst een gegeorefereerd luchtbeeld toe')
|
||||
return false
|
||||
return null
|
||||
}
|
||||
const selectedModel = detectionModels.find((model) => model.model_id === selectedDetectionModelId)
|
||||
if (!selectedModel?.configured || selectedDetectionModelId === 'manual-fixture-detector') {
|
||||
const effectiveModelId = modelIdOverride || selectedDetectionModelId
|
||||
const effectiveModelAssetId = effectiveModelId === 'yolo-configured'
|
||||
? modelAssets.find((asset) => asset.active)?.model_asset_id ?? selectedModelAssetId
|
||||
: selectedModelAssetId
|
||||
const selectedModel = detectionModels.find((model) => model.model_id === effectiveModelId)
|
||||
if (!selectedModel?.configured || effectiveModelId === 'manual-fixture-detector') {
|
||||
setDetectionRunError(selectedModel?.limitation_message ?? 'Het gekozen analysemodel is niet beschikbaar')
|
||||
return false
|
||||
return null
|
||||
}
|
||||
if (selectedDetectionModelId === 'yolo-configured' && modelAssets.length > 0 && !selectedModelAssetId) {
|
||||
if (effectiveModelId === 'yolo-configured' && modelAssets.length > 0 && !effectiveModelAssetId) {
|
||||
setDetectionRunError('Kies eerst een lokaal modelbestand')
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
setDetectionRunError(null)
|
||||
@@ -355,7 +368,7 @@ export function useDetectionWorkflow({
|
||||
setDetectionWorkflowStage('validating')
|
||||
const preflight = await detectionApi.getYoloPreflight({
|
||||
tile_manifest_path: manifestPath,
|
||||
model_asset_id: selectedModelAssetId || null,
|
||||
model_asset_id: effectiveModelAssetId || null,
|
||||
})
|
||||
setYoloPreflight(preflight)
|
||||
setYoloPreflightError(null)
|
||||
@@ -370,46 +383,63 @@ export function useDetectionWorkflow({
|
||||
}
|
||||
|
||||
setDetectionWorkflowStage('detecting')
|
||||
await executeDetection(selectedProjectId, datasetId, manifestPath)
|
||||
const result = await executeDetection(
|
||||
selectedProjectId,
|
||||
datasetId,
|
||||
manifestPath,
|
||||
effectiveModelId,
|
||||
effectiveModelAssetId,
|
||||
)
|
||||
setDetectionWorkflowStage('complete')
|
||||
return true
|
||||
return result
|
||||
} catch (error) {
|
||||
setDetectionRunError(formatError(error, 'De beeldanalyse is mislukt'))
|
||||
setDetectionWorkflowStage('failed')
|
||||
return false
|
||||
return null
|
||||
} finally {
|
||||
setRunningDetection(false)
|
||||
}
|
||||
}
|
||||
|
||||
const runDetectionQa = async () => {
|
||||
if (!selectedDetectionRunId) {
|
||||
const compareDetectionRunWithReference = async (
|
||||
analysisRunId: string,
|
||||
referenceDatasetId: string,
|
||||
useCurrentFilters = true,
|
||||
): Promise<DetectionQaResult | null> => {
|
||||
if (!analysisRunId) {
|
||||
setDetectionQaError('Select a detection run')
|
||||
return
|
||||
return null
|
||||
}
|
||||
if (!detectionReferenceDatasetId) {
|
||||
if (!referenceDatasetId) {
|
||||
setDetectionQaError('Select a reference dataset')
|
||||
return
|
||||
return null
|
||||
}
|
||||
setSelectedDetectionRunId(analysisRunId)
|
||||
setDetectionReferenceDatasetId(referenceDatasetId)
|
||||
setDetectionQaError(null)
|
||||
setDetectionQaResult(null)
|
||||
setRunningDetectionQa(true)
|
||||
try {
|
||||
const result = await detectionApi.compareWithReference(selectedDetectionRunId, {
|
||||
reference_dataset_id: detectionReferenceDatasetId,
|
||||
const result = await detectionApi.compareWithReference(analysisRunId, {
|
||||
reference_dataset_id: referenceDatasetId,
|
||||
iou_threshold: qaIouThreshold,
|
||||
class_name: detectionClassFilter || null,
|
||||
min_confidence: detectionMinConfidenceFilter > 0 ? detectionMinConfidenceFilter : null,
|
||||
class_name: useCurrentFilters ? detectionClassFilter || null : null,
|
||||
min_confidence: useCurrentFilters && detectionMinConfidenceFilter > 0 ? detectionMinConfidenceFilter : null,
|
||||
})
|
||||
setDetectionQaResult(result)
|
||||
await loadQualityChecks(selectedProjectId)
|
||||
return result
|
||||
} catch (error) {
|
||||
setDetectionQaError(formatError(error, 'Detection QA failed'))
|
||||
return null
|
||||
} finally {
|
||||
setRunningDetectionQa(false)
|
||||
}
|
||||
}
|
||||
|
||||
const runDetectionQa = async (): Promise<DetectionQaResult | null> =>
|
||||
compareDetectionRunWithReference(selectedDetectionRunId, detectionReferenceDatasetId)
|
||||
|
||||
const runDetectionCalibration = async () => {
|
||||
if (!selectedProjectId) {
|
||||
setDetectionCalibrationError('Select a project before calibration')
|
||||
@@ -560,6 +590,7 @@ export function useDetectionWorkflow({
|
||||
runDetection,
|
||||
uploadDetectionRaster,
|
||||
prepareAndRunDetection,
|
||||
compareDetectionRunWithReference,
|
||||
runDetectionQa,
|
||||
runDetectionCalibration,
|
||||
applyDetectionOperatorProfile,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { useState } from 'react'
|
||||
import { datasetsApi } from '../services/api'
|
||||
import type {
|
||||
DatasetCreateResponse,
|
||||
DetectionQaResult,
|
||||
DetectionRunResponse,
|
||||
OrthophotoAcquisitionResult,
|
||||
VectorSelectionBBox,
|
||||
} from '../types'
|
||||
import { formatError } from '../lib/formatError'
|
||||
|
||||
export type MapOrthophotoAnalysisStage =
|
||||
| 'idle'
|
||||
| 'acquiring'
|
||||
| 'detecting'
|
||||
| 'validating'
|
||||
| 'complete'
|
||||
| 'failed'
|
||||
|
||||
interface MapOrthophotoAnalysisOptions {
|
||||
selectedProjectId: string | null
|
||||
selectedAreaId: string
|
||||
datasets: DatasetCreateResponse[]
|
||||
loadProjectData: (projectId: string) => Promise<unknown>
|
||||
prepareAndRunDetection: (datasetId?: string) => Promise<DetectionRunResponse | null>
|
||||
compareDetectionRunWithReference: (
|
||||
analysisRunId: string,
|
||||
referenceDatasetId: string,
|
||||
) => Promise<DetectionQaResult | null>
|
||||
onAnalysisReady: () => void
|
||||
}
|
||||
|
||||
function findBuildingReference(datasets: DatasetCreateResponse[]): DatasetCreateResponse | null {
|
||||
return datasets.find(
|
||||
(dataset) =>
|
||||
dataset.status === 'ready' &&
|
||||
dataset.dataset_role === 'reference' &&
|
||||
dataset.source_name === 'grb' &&
|
||||
dataset.reference_layer_name === 'buildings',
|
||||
) ?? null
|
||||
}
|
||||
|
||||
export function useMapOrthophotoAnalysis({
|
||||
selectedProjectId,
|
||||
selectedAreaId,
|
||||
datasets,
|
||||
loadProjectData,
|
||||
prepareAndRunDetection,
|
||||
compareDetectionRunWithReference,
|
||||
onAnalysisReady,
|
||||
}: MapOrthophotoAnalysisOptions) {
|
||||
const [stage, setStage] = useState<MapOrthophotoAnalysisStage>('idle')
|
||||
const [status, setStatus] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [lastResult, setLastResult] = useState<OrthophotoAcquisitionResult | null>(null)
|
||||
|
||||
const run = async (bbox: VectorSelectionBBox): Promise<boolean> => {
|
||||
if (!selectedProjectId) {
|
||||
setError('De regionale werkruimte is nog niet geladen.')
|
||||
setStage('failed')
|
||||
return false
|
||||
}
|
||||
setError(null)
|
||||
setLastResult(null)
|
||||
setStage('acquiring')
|
||||
setStatus('1/3 Officieel luchtbeeld voor de rechthoek ophalen...')
|
||||
try {
|
||||
const job = await datasetsApi.acquireOrthophoto(selectedProjectId, {
|
||||
bbox,
|
||||
area_id: selectedAreaId || undefined,
|
||||
})
|
||||
const acquisition = job.result_json as unknown as OrthophotoAcquisitionResult | null
|
||||
const datasetId = job.output_dataset_id || acquisition?.output_dataset_id
|
||||
if (job.status !== 'success' || !datasetId || !acquisition) {
|
||||
throw new Error(job.error_message || 'Het officiële luchtbeeld werd niet als dataset bewaard.')
|
||||
}
|
||||
setLastResult(acquisition)
|
||||
await loadProjectData(selectedProjectId)
|
||||
|
||||
setStage('detecting')
|
||||
setStatus('2/3 Lokaal AI-model herkent gebouwen...')
|
||||
const detection = await prepareAndRunDetection(datasetId)
|
||||
if (!detection) {
|
||||
throw new Error('De beeldanalyse stopte. Open Beeldanalyse voor de technische oorzaak.')
|
||||
}
|
||||
|
||||
const reference = findBuildingReference(datasets)
|
||||
if (reference) {
|
||||
setStage('validating')
|
||||
setStatus('3/3 Resultaat vergelijken met officiële GRB-gebouwen...')
|
||||
const quality = await compareDetectionRunWithReference(detection.analysis_run_id, reference.id)
|
||||
setStatus(
|
||||
quality
|
||||
? `Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} gebouwen herkend en gecontroleerd.`
|
||||
: `Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} gebouwen herkend; kwaliteitscontrole kon niet afronden.`,
|
||||
)
|
||||
} else {
|
||||
setStatus(
|
||||
`Analyse klaar: ${detection.detection_count.toLocaleString('nl-BE')} gebouwen herkend. De GRB-referentielaag ontbreekt voor automatische controle.`,
|
||||
)
|
||||
}
|
||||
setStage('complete')
|
||||
onAnalysisReady()
|
||||
return true
|
||||
} catch (caught) {
|
||||
setError(formatError(caught, 'De kaartgestuurde beeldanalyse is mislukt'))
|
||||
setStatus('Analyse gestopt.')
|
||||
setStage('failed')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
stage,
|
||||
status,
|
||||
error,
|
||||
lastResult,
|
||||
running: stage === 'acquiring' || stage === 'detecting' || stage === 'validating',
|
||||
run,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user