530 lines
23 KiB
TypeScript
530 lines
23 KiB
TypeScript
import type {
|
||
DatasetCreateResponse,
|
||
JobRead,
|
||
SegmentationModelCapability,
|
||
SegmentationQaResult,
|
||
SegmentationRead,
|
||
SegmentationRunRead,
|
||
SegmentationRunResponse,
|
||
} from '../../types'
|
||
import { ModelSelector } from '../models/ModelSelector'
|
||
import {
|
||
analysisModelAvailabilityMessage,
|
||
analysisModelDisplayName,
|
||
toAnalysisModelOption,
|
||
} from '../models/modelOptions'
|
||
import { getDatasetSelectionLabel } from '../../lib/datasetDisplay'
|
||
|
||
interface SegmentationLabProps {
|
||
managementLocked?: boolean
|
||
blockedInferenceDatasetCount?: number
|
||
segmentationModels: SegmentationModelCapability[]
|
||
loadingSegmentationModels: boolean
|
||
segmentationModelError: string | null
|
||
selectedSegmentationDatasetId: string
|
||
selectedSegmentationModelId: string
|
||
segmentationTileManifestPath: string
|
||
segmentationConfidenceThreshold: number
|
||
runningSegmentation: boolean
|
||
segmentationJob: JobRead | null
|
||
segmentationRunResult: SegmentationRunResponse | null
|
||
segmentationRunError: string | null
|
||
segmentationRuns: SegmentationRunRead[]
|
||
selectedSegmentationRunId: string
|
||
segmentationItems: SegmentationRead[]
|
||
segmentationTotal: number
|
||
segmentationTruncated: boolean
|
||
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'
|
||
if (modelName === 'segmentation-placeholder') return 'Segmentatiemodel niet geconfigureerd'
|
||
if (modelName === 'yolo-seg-configured') return 'Lokaal YOLO-segmentatiemodel'
|
||
if (modelName === 'sam-configured') return 'Lokaal SAM-model'
|
||
return modelName
|
||
}
|
||
|
||
function segmentationClassLabel(className: string): string {
|
||
if (className.toLowerCase() === 'building') return 'Gebouw'
|
||
return className
|
||
}
|
||
|
||
export function SegmentationLab({
|
||
managementLocked = false,
|
||
blockedInferenceDatasetCount = 0,
|
||
segmentationModels,
|
||
loadingSegmentationModels,
|
||
segmentationModelError,
|
||
selectedSegmentationDatasetId,
|
||
selectedSegmentationModelId,
|
||
segmentationTileManifestPath,
|
||
segmentationConfidenceThreshold,
|
||
runningSegmentation,
|
||
segmentationJob,
|
||
segmentationRunResult,
|
||
segmentationRunError,
|
||
segmentationRuns,
|
||
selectedSegmentationRunId,
|
||
segmentationItems,
|
||
segmentationTotal,
|
||
segmentationTruncated,
|
||
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 selectedSegmentationModel = segmentationModels.find(
|
||
(model) => model.model_id === selectedSegmentationModelId,
|
||
) ?? null
|
||
const selectedSegmentationModelAvailability = selectedSegmentationModel
|
||
? analysisModelAvailabilityMessage(selectedSegmentationModel)
|
||
: selectedSegmentationModelLimitation ?? 'Het gekozen segmentatiemodel is niet geconfigureerd'
|
||
const segmentationRunReady =
|
||
Boolean(selectedProjectId) && segmentationHasDataset && segmentationModelUiRunnable
|
||
const segmentationJobActive = segmentationJob?.status === 'queued' || segmentationJob?.status === 'running'
|
||
const segmentationRunBlockedReason = !selectedProjectId
|
||
? 'Kies eerst een werkruimte'
|
||
: !segmentationHasDataset
|
||
? 'Kies een rasterbestand'
|
||
: selectedSegmentationModelId === 'fixture-segmenter'
|
||
? 'Het fixturemodel is alleen bedoeld voor expliciete tests en demo’s'
|
||
: !selectedSegmentationModelConfigured
|
||
? selectedSegmentationModelAvailability
|
||
: 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>{analysisModelDisplayName(model)}</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">{analysisModelAvailabilityMessage(model)}</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={segmentationModelUiRunnable ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||
<span>Analysemodel</span>
|
||
<strong>
|
||
{selectedSegmentationModelId === 'fixture-segmenter'
|
||
? 'Alleen beschikbaar voor geautomatiseerde tests'
|
||
: selectedSegmentationModelConfigured
|
||
? 'Het gekozen model is beschikbaar'
|
||
: selectedSegmentationModelAvailability}
|
||
</strong>
|
||
</div>
|
||
<div className={segmentationHasDataset ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||
<span>Beeldtegels</span>
|
||
<strong>{segmentationHasTileManifest ? 'Technisch manifest gekoppeld' : segmentationHasDataset ? 'Worden automatisch voorbereid' : 'Wachten op een rasterbestand'}</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>
|
||
{blockedInferenceDatasetCount > 0 ? (
|
||
<p>{blockedInferenceDatasetCount} rasterbestand(en) zijn vooraf uitgesloten door fixture-, quarantaine-, validatie- of provenancegegevens. De volledige servergate blijft beslissend.</p>
|
||
) : null}
|
||
<p>{managementLocked
|
||
? 'Ga naar Kaart en maak een begrensde officiële bronselectie. De synthetische demo-context blijft bewust uitgesloten van geconfigureerde modelinference.'
|
||
: 'Voeg eerst een gevalideerd rasterbestand toe onder Bronnen of haal via Kaart een begrensde officiële bron op.'}</p>
|
||
</div>
|
||
) : null}
|
||
<div className="lab-form-grid">
|
||
<label>
|
||
Rasterbestand
|
||
<select
|
||
value={selectedSegmentationDatasetId}
|
||
onChange={(event) => {
|
||
onSelectDataset(event.target.value)
|
||
onSetTileManifestPath('')
|
||
}}
|
||
>
|
||
<option value="">Kies een rasterbestand</option>
|
||
{rasterDatasets.map((dataset) => (
|
||
<option key={dataset.id} value={dataset.id}>
|
||
{getDatasetSelectionLabel(dataset)}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
<ModelSelector
|
||
label="Analysemodel"
|
||
value={selectedSegmentationModelId}
|
||
options={segmentationModels.map(toAnalysisModelOption)}
|
||
onChange={onSelectModel}
|
||
loading={loadingSegmentationModels}
|
||
error={segmentationModelError}
|
||
onRefresh={onLoadModels}
|
||
/>
|
||
<label>
|
||
Minimale zekerheid
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
max="1"
|
||
step="0.05"
|
||
value={segmentationConfidenceThreshold}
|
||
onChange={(event) => onSetConfidenceThreshold(Number(event.target.value))}
|
||
/>
|
||
</label>
|
||
</div>
|
||
{!managementLocked ? <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> : null}
|
||
<button
|
||
className="primary-action"
|
||
type="button"
|
||
onClick={onRunSegmentation}
|
||
disabled={runningSegmentation || segmentationJobActive || !segmentationRunReady}
|
||
>
|
||
{segmentationJob?.status === 'queued'
|
||
? 'Wachten op NVIDIA GPU…'
|
||
: runningSegmentation
|
||
? segmentationHasTileManifest ? 'GPU-segmentatie wordt verwerkt…' : 'Beeldtegels voorbereiden…'
|
||
: 'Segmentatie starten'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="ai-lab-state-stack">
|
||
{segmentationJobActive ? (
|
||
<div className="result-state result-state-loading" role="status" aria-live="polite">
|
||
<strong>{segmentationJob?.status === 'queued' ? 'GPU-taak staat in de wachtrij.' : 'GPU-segmentatie wordt uitgevoerd.'}</strong>
|
||
<p>
|
||
{segmentationJob?.status === 'queued'
|
||
? 'De server start de taak zodra de NVIDIA-worker beschikbaar is.'
|
||
: 'GeoIntel volgt de servertaak en toont na voltooiing alleen de werkelijk bewaarde polygonen.'}
|
||
</p>
|
||
<span className="muted">Taak-ID: {segmentationJob?.id}</span>
|
||
</div>
|
||
) : null}
|
||
{!selectedSegmentationModelConfigured ? (
|
||
<div className="result-state result-state-empty">
|
||
<strong>Het segmentatiemodel is nog niet gereed.</strong>
|
||
<p>{selectedSegmentationModelAvailability}</p>
|
||
</div>
|
||
) : null}
|
||
{segmentationRunError ? (
|
||
<div className="result-state result-state-error" role="alert">
|
||
<strong>De segmentatie is mislukt.</strong>
|
||
<p>{segmentationRunError}</p>
|
||
</div>
|
||
) : null}
|
||
{segmentationRunResult ? (
|
||
<div className={segmentationRunResult.segmentation_count === 0 ? 'result-state result-state-warning' : 'result-summary-card'} role="status">
|
||
<p>Status: {analysisStatusLabel(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">
|
||
{loadingSegmentationResults || segmentationRunError ? null : !selectedSegmentationRunId ? (
|
||
<div className="result-state result-state-empty">
|
||
<strong>Kies eerst een bewaarde analyse.</strong>
|
||
<p>Daarna toont GeoIntel uitsluitend de polygonen van die analyserun.</p>
|
||
</div>
|
||
) : segmentationTotal === 0 ? (
|
||
<div className="result-state result-state-empty">
|
||
<strong>Geen bewaarde vlakken binnen deze filters.</strong>
|
||
<p>Dit bewijst niet dat het gebied geen relevante objecten bevat.</p>
|
||
</div>
|
||
) : (
|
||
<div className={segmentationTruncated ? 'result-state result-state-warning' : 'result-state result-state-ready'}>
|
||
<strong>
|
||
{segmentationTruncated
|
||
? `${segmentationItems.length} van ${segmentationTotal} vlakken geladen`
|
||
: `${segmentationTotal} vlakken geladen`}
|
||
</strong>
|
||
<p>
|
||
{segmentationTruncated
|
||
? 'De kaart en tabel tonen een begrensde pagina. Gebruik filters om het resultaat gericht te verfijnen.'
|
||
: 'Deze resultaten zijn bewaard in de database.'}
|
||
</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}>
|
||
{getDatasetSelectionLabel(dataset)}
|
||
</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>
|
||
)
|
||
}
|