Files
geointel/frontend/src/components/segmentation/SegmentationLab.tsx
T
Codex 75ab8f64ab
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s
Finish audit UI and live validation closure
2026-07-17 14:36:55 +02:00

455 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {
DatasetCreateResponse,
SegmentationModelCapability,
SegmentationQaResult,
SegmentationRead,
SegmentationRunRead,
SegmentationRunResponse,
} from '../../types'
interface SegmentationLabProps {
segmentationModels: SegmentationModelCapability[]
loadingSegmentationModels: boolean
segmentationModelError: string | null
selectedSegmentationDatasetId: string
selectedSegmentationModelId: string
segmentationTileManifestPath: string
segmentationConfidenceThreshold: number
runningSegmentation: boolean
segmentationRunResult: SegmentationRunResponse | null
segmentationRunError: string | null
segmentationRuns: SegmentationRunRead[]
selectedSegmentationRunId: string
segmentationItems: SegmentationRead[]
segmentationClassFilter: string
segmentationMinConfidenceFilter: number
loadingSegmentationResults: boolean
segmentationReferenceDatasetId: string
segmentationQaResult: SegmentationQaResult | null
segmentationQaError: string | null
runningSegmentationQa: boolean
selectedProjectId: string | null
rasterDatasets: DatasetCreateResponse[]
referenceDatasets: DatasetCreateResponse[]
selectedSegmentationModelConfigured: boolean
selectedSegmentationModelLimitation: string | null
onLoadModels: () => void
onSelectDataset: (datasetId: string) => void
onSelectModel: (modelId: string) => void
onSetTileManifestPath: (value: string) => void
onSetConfidenceThreshold: (value: number) => void
onRunSegmentation: () => void
onLoadRuns: () => void
onSelectRun: (runId: string) => void
onSetClassFilter: (value: string) => void
onSetMinConfidenceFilter: (value: number) => void
onLoadResults: () => void
onSelectReferenceDataset: (datasetId: string) => void
onRunQa: () => void
}
function segmentationRunLabel(run: SegmentationRunRead): string {
const timestamp = run.finished_at ?? run.created_at
const dateLabel = timestamp
? new Intl.DateTimeFormat('nl-BE', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(timestamp))
: 'datum onbekend'
return `${persistedSegmentationModelLabel(run.model_name)} · ${analysisStatusLabel(run.status)} · ${dateLabel}`
}
function analysisStatusLabel(status: string): string {
if (status === 'completed') return 'afgerond'
if (status === 'success') return 'geslaagd'
if (status === 'failed') return 'mislukt'
if (status === 'running') return 'bezig'
if (status === 'queued') return 'in wachtrij'
return status.replace(/_/g, ' ')
}
function persistedSegmentationModelLabel(modelName: string | null | undefined): string {
if (!modelName) return 'Segmentatie'
if (modelName === 'fixture-segmenter') return 'Testsegmentatie'
if (modelName === 'sam-placeholder') return 'SAM-model niet geconfigureerd'
if (modelName === 'yolo-seg-placeholder') return 'YOLO-segmentatie niet geconfigureerd'
return modelName
}
function segmentationClassLabel(className: string): string {
if (className.toLowerCase() === 'building') return 'Gebouw'
return className
}
export function SegmentationLab({
segmentationModels,
loadingSegmentationModels,
segmentationModelError,
selectedSegmentationDatasetId,
selectedSegmentationModelId,
segmentationTileManifestPath,
segmentationConfidenceThreshold,
runningSegmentation,
segmentationRunResult,
segmentationRunError,
segmentationRuns,
selectedSegmentationRunId,
segmentationItems,
segmentationClassFilter,
segmentationMinConfidenceFilter,
loadingSegmentationResults,
segmentationReferenceDatasetId,
segmentationQaResult,
segmentationQaError,
runningSegmentationQa,
selectedProjectId,
rasterDatasets,
referenceDatasets,
selectedSegmentationModelConfigured,
selectedSegmentationModelLimitation,
onLoadModels,
onSelectDataset,
onSelectModel,
onSetTileManifestPath,
onSetConfidenceThreshold,
onRunSegmentation,
onLoadRuns,
onSelectRun,
onSetClassFilter,
onSetMinConfidenceFilter,
onLoadResults,
onSelectReferenceDataset,
onRunQa,
}: SegmentationLabProps): JSX.Element {
const segmentationHasDataset = selectedSegmentationDatasetId.length > 0
const segmentationHasTileManifest = segmentationTileManifestPath.trim().length > 0
const segmentationModelUiRunnable =
selectedSegmentationModelConfigured && selectedSegmentationModelId !== 'fixture-segmenter'
const segmentationRunReady =
Boolean(selectedProjectId) && segmentationHasDataset && segmentationModelUiRunnable
const segmentationRunBlockedReason = !selectedProjectId
? 'Kies eerst een werkruimte'
: !segmentationHasDataset
? 'Kies een rasterbestand'
: selectedSegmentationModelId === 'fixture-segmenter'
? 'Het fixturemodel is alleen bedoeld voor expliciete tests en demos'
: !selectedSegmentationModelConfigured
? selectedSegmentationModelLimitation ?? 'Het gekozen segmentatiemodel is niet geconfigureerd'
: null
return (
<section className="workspace-panel ai-lab-shell segmentation-lab-shell">
<div className="panel-title-row">
<div>
<p className="eyebrow">Vlakken herkennen in beeld</p>
<h2>Segmentatie</h2>
</div>
<button className="secondary-action" type="button" onClick={onLoadModels} disabled={loadingSegmentationModels}>
Status vernieuwen
</button>
</div>
<details className="ai-lab-model-surface" aria-label="Technische informatie over segmentatiemodellen">
<summary>
<span>Technische modelinformatie</span>
<strong>{segmentationModels.length} modellen</strong>
</summary>
<div className="ai-lab-disclosure-body">
<div className="ai-lab-state-stack">
{loadingSegmentationModels ? (
<div className="result-state result-state-loading">
<strong>Segmentatiemodellen worden gecontroleerd.</strong>
<p>GeoIntel leest de modelregistratie van de backend.</p>
</div>
) : null}
{segmentationModelError ? (
<div className="result-state result-state-error">
<strong>De modelregistratie voor segmentatie is niet bereikbaar.</strong>
<p>{segmentationModelError}</p>
</div>
) : null}
{segmentationModels.length === 0 && !loadingSegmentationModels ? (
<div className="result-state result-state-empty">
<strong>De backend meldt geen segmentatiemodellen.</strong>
<p>Vernieuw de status zodra de backend opnieuw bereikbaar is.</p>
</div>
) : null}
</div>
<ul className="model-list">
{segmentationModels.map((model) => (
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
<strong>{model.display_name}</strong>
<span className={model.configured ? 'status-badge status-badge-ready' : 'status-badge'}>{model.configured ? 'gereed' : 'niet geconfigureerd'}</span>
<p className="muted">Ondersteunde klassen: {model.supported_classes.join(', ') || 'niet opgegeven'}</p>
<p className="muted">{model.limitation_message}</p>
<details className="technical-inline-details">
<summary>Technische identificatie</summary>
<div className="entity-meta">
<span>Model-ID: {model.model_id}</span>
<span>Framework: {model.framework}</span>
<span>Taaktype: {model.task_type}</span>
</div>
</details>
</li>
))}
</ul>
</div>
</details>
<div className="lab-block">
<div className="ai-lab-run-surface" aria-label="Segmentatie starten">
<h3>Nieuwe segmentatie</h3>
<div
className={segmentationRunReady ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}
aria-label="Startklaar voor segmentatie"
>
<div className="ai-lab-section-header">
<div>
<h3>Wat is nog nodig?</h3>
<p>GeoIntel controleert het raster en model voordat de verwerking start.</p>
</div>
<span className={segmentationRunReady ? 'status-badge status-badge-ready' : 'status-badge'}>
{segmentationRunReady ? 'Klaar om te starten' : 'Nog niet startklaar'}
</span>
</div>
<div className="lab-readiness-grid">
<div className={segmentationHasDataset ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Rasterbestand</span>
<strong>{segmentationHasDataset ? 'Geselecteerd' : 'Kies een rasterbestand'}</strong>
</div>
<div className={selectedSegmentationModelConfigured ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Analysemodel</span>
<strong>
{selectedSegmentationModelConfigured
? 'Het gekozen model is beschikbaar'
: selectedSegmentationModelLimitation ?? 'Kies een geconfigureerd segmentatiemodel'}
</strong>
</div>
<div className={segmentationHasTileManifest ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
<span>Beeldtegels</span>
<strong>{segmentationHasTileManifest ? 'Technisch manifest gekoppeld' : 'Niet vereist voor het fixturemodel'}</strong>
</div>
</div>
</div>
<div className={segmentationRunReady ? 'lab-action-guardrail lab-action-guardrail-ready' : 'lab-action-guardrail'}>
<span>Analyse</span>
<strong>{segmentationRunReady ? 'Klaar om segmentatie te starten' : segmentationRunBlockedReason}</strong>
</div>
{rasterDatasets.length === 0 ? (
<div className="result-state result-state-empty">
<strong>Geen rasterbestand beschikbaar voor segmentatie.</strong>
<p>Voeg eerst een rasterbestand toe onder Bronnen.</p>
</div>
) : null}
<div className="lab-form-grid">
<label>
Rasterbestand
<select value={selectedSegmentationDatasetId} onChange={(event) => onSelectDataset(event.target.value)}>
<option value="">Kies een rasterbestand</option>
{rasterDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
</label>
<label>
Analysemodel
<select value={selectedSegmentationModelId} onChange={(event) => onSelectModel(event.target.value)}>
{segmentationModels.map((model) => (
<option key={model.model_id} value={model.model_id}>
{model.display_name}
</option>
))}
</select>
</label>
<label>
Minimale zekerheid
<input
type="number"
min="0"
max="1"
step="0.05"
value={segmentationConfidenceThreshold}
onChange={(event) => onSetConfidenceThreshold(Number(event.target.value))}
/>
</label>
</div>
<details className="technical-inline-details">
<summary>Technische beeldtegelinstelling</summary>
<label>
Beeldtegelmanifest
<input
type="text"
placeholder="Pad naar het beeldtegelmanifest"
value={segmentationTileManifestPath}
onChange={(event) => onSetTileManifestPath(event.target.value)}
/>
</label>
</details>
<button
className="primary-action"
type="button"
onClick={onRunSegmentation}
disabled={runningSegmentation || !segmentationRunReady}
>
Segmentatie starten
</button>
</div>
</div>
<div className="ai-lab-state-stack">
{!selectedSegmentationModelConfigured ? (
<div className="result-state result-state-empty">
<strong>Het segmentatiemodel is nog niet gereed.</strong>
<p>{selectedSegmentationModelLimitation ?? 'Kies een geconfigureerd segmentatiemodel.'}</p>
</div>
) : null}
{segmentationRunError ? (
<div className="result-state result-state-error">
<strong>De segmentatie is mislukt.</strong>
<p>{segmentationRunError}</p>
</div>
) : null}
{segmentationRunResult ? (
<div className="result-summary-card">
<p>Status: {segmentationRunResult.status === 'completed' ? 'afgerond' : segmentationRunResult.status}</p>
<p>{segmentationRunResult.message}</p>
<p>Herkende vlakken: {segmentationRunResult.segmentation_count}</p>
{segmentationRunResult.error_code ? <p className="error">Code: {segmentationRunResult.error_code}</p> : null}
<details className="technical-inline-details">
<summary>Technische verwerking</summary>
<div className="entity-meta">
<span>Analyserun-ID: {segmentationRunResult.analysis_run_id}</span>
<span>Taak-ID: {segmentationRunResult.job_id}</span>
</div>
</details>
</div>
) : null}
</div>
<div className="ai-lab-results-surface" aria-label="Segmentatieresultaten">
<div className="panel-title-row">
<div>
<h3>Herkende vlakken</h3>
<p className="muted">Bekijk bewaarde resultaten en filter op klasse of zekerheid.</p>
</div>
<button className="secondary-action" type="button" onClick={onLoadRuns} disabled={!selectedProjectId}>
Analyses vernieuwen
</button>
</div>
<div className="lab-form-grid">
<label>
Analyse
<select value={selectedSegmentationRunId} onChange={(event) => onSelectRun(event.target.value)}>
<option value="">Kies een bewaarde analyse</option>
{segmentationRuns.map((run) => (
<option key={run.id} value={run.id}>
{segmentationRunLabel(run)}
</option>
))}
</select>
</label>
<label>
Klasse
<input
type="text"
placeholder="Filter op klasse"
value={segmentationClassFilter}
onChange={(event) => onSetClassFilter(event.target.value)}
/>
</label>
<label>
Minimale zekerheid
<input
type="number"
min="0"
max="1"
step="0.05"
value={segmentationMinConfidenceFilter}
onChange={(event) => onSetMinConfidenceFilter(Number(event.target.value))}
/>
</label>
</div>
<button className="primary-action" type="button" onClick={onLoadResults} disabled={!selectedSegmentationRunId || loadingSegmentationResults}>
Resultaten laden
</button>
{loadingSegmentationResults ? (
<div className="result-state result-state-loading">
<strong>Segmentatieresultaten worden geladen.</strong>
<p>GeoIntel leest de bewaarde polygonen van deze analyse.</p>
</div>
) : null}
<div className="ai-lab-state-stack">
<div className="result-state result-state-ready">
<strong>{segmentationItems.length} vlakken geladen</strong>
<p>{selectedSegmentationRunId ? 'Deze resultaten zijn bewaard in de database.' : 'Kies eerst een bewaarde analyse.'}</p>
</div>
</div>
{segmentationItems.length > 0 ? (
<div className="table-scroll">
<table>
<thead>
<tr>
<th>Klasse</th>
<th>Zekerheid</th>
<th>Oppervlakte m²</th>
<th>Model</th>
<th>Herkomst</th>
</tr>
</thead>
<tbody>
{segmentationItems.map((segmentation) => (
<tr key={segmentation.id}>
<td>{segmentationClassLabel(segmentation.class_name)}</td>
<td>{segmentation.confidence?.toFixed(2) ?? 'n.v.t.'}</td>
<td>{segmentation.area_m2?.toFixed(2) ?? 'n.v.t.'}</td>
<td>{persistedSegmentationModelLabel(segmentation.model_name)}</td>
<td>{segmentation.tile_index == null ? 'n.v.t.' : 'Luchtbeeldtegel'}</td>
</tr>
))}
</tbody>
</table>
</div>
) : null}
</div>
<div className="ai-lab-qa-surface" aria-label="Kwaliteitscontrole voor segmentatie">
<h3>Kwaliteitscontrole segmentatie</h3>
<label>
Referentielaag
<select value={segmentationReferenceDatasetId} onChange={(event) => onSelectReferenceDataset(event.target.value)}>
<option value="">Kies een referentielaag</option>
{referenceDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
</label>
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningSegmentationQa || !selectedSegmentationRunId || !segmentationReferenceDatasetId}>
Vergelijk met referentielaag
</button>
{segmentationQaError ? (
<div className="result-state result-state-error">
<strong>De kwaliteitscontrole is mislukt.</strong>
<p>{segmentationQaError}</p>
</div>
) : null}
{segmentationQaResult ? (
<div className="result-summary-card">
<p>Status: {segmentationQaResult.status === 'completed' ? 'afgerond' : segmentationQaResult.status}</p>
<p>Precisie: {segmentationQaResult.precision?.toFixed(3) ?? 'n.v.t.'}</p>
<p>Herkenningsgraad: {segmentationQaResult.recall?.toFixed(3) ?? 'n.v.t.'}</p>
<p>F1: {segmentationQaResult.f1_score?.toFixed(3) ?? 'n.v.t.'}</p>
<p>Gemiddelde overlap: {segmentationQaResult.mean_iou?.toFixed(3) ?? 'n.v.t.'}</p>
<p>Onterecht gevonden: {segmentationQaResult.false_positives}</p>
<p>Gemist: {segmentationQaResult.false_negatives}</p>
<details className="technical-inline-details">
<summary>Technische referentie</summary>
<span>Kwaliteitscontrole-ID: {segmentationQaResult.quality_check_id}</span>
</details>
</div>
) : null}
</div>
</section>
)
}