Initial GeoIntel V1 foundation
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-16 23:36:32 +02:00
commit 6ea3586a3e
605 changed files with 45284 additions and 0 deletions
View File
+116
View File
@@ -0,0 +1,116 @@
import { useEffect, useRef } from 'react'
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
interface GeoMapProps {
data: GeoJSON.FeatureCollection | null
}
function collectCoordinates(featureCollection: GeoJSON.FeatureCollection): maplibregl.LngLatBoundsLike | null {
const coordinates: [number, number][] = []
const walk = (coords: unknown) => {
if (!Array.isArray(coords)) {
return
}
if (coords.length === 2 && typeof coords[0] === 'number' && typeof coords[1] === 'number') {
coordinates.push([coords[0], coords[1]])
return
}
for (const item of coords) {
walk(item)
}
}
for (const feature of featureCollection.features) {
const geometry = feature.geometry as any
if (geometry && geometry.coordinates) {
walk(geometry.coordinates)
}
}
if (coordinates.length === 0) {
return null
}
const xs = coordinates.map((point) => point[0])
const ys = coordinates.map((point) => point[1])
return [
[Math.min(...xs), Math.min(...ys)],
[Math.max(...xs), Math.max(...ys)],
]
}
function GeoMap({ data }: GeoMapProps): JSX.Element {
const containerRef = useRef<HTMLDivElement | null>(null)
const mapRef = useRef<maplibregl.Map | null>(null)
useEffect(() => {
if (!containerRef.current || mapRef.current) {
return
}
const map = new maplibregl.Map({
container: containerRef.current,
style: import.meta.env.VITE_MAP_STYLE_URL || 'https://demotiles.maplibre.org/style.json',
center: [5.3, 51.3],
zoom: 9,
})
map.addControl(new maplibregl.NavigationControl(), 'top-right')
mapRef.current = map
return () => {
map.remove()
mapRef.current = null
}
}, [])
useEffect(() => {
const map = mapRef.current
if (!map) {
return
}
if (map.getSource('dataset')) {
if (data) {
;(map.getSource('dataset') as maplibregl.GeoJSONSource).setData(data)
} else {
if (map.getLayer('dataset-fill')) {
map.removeLayer('dataset-fill')
}
if (map.getLayer('dataset-line')) {
map.removeLayer('dataset-line')
}
map.removeSource('dataset')
return
}
} else if (data) {
map.addSource('dataset', { type: 'geojson', data })
map.addLayer({
id: 'dataset-fill',
type: 'fill',
source: 'dataset',
paint: { 'fill-color': '#f97316', 'fill-opacity': 0.4 },
})
map.addLayer({
id: 'dataset-line',
type: 'line',
source: 'dataset',
paint: { 'line-color': '#ea580c', 'line-width': 2 },
})
}
if (data) {
const collection = data
if (collection.type === 'FeatureCollection' && collection.features.length > 0) {
const bounds = collectCoordinates(collection)
if (bounds) {
map.fitBounds(bounds, { padding: 40 })
}
}
}
}, [data])
return <div className="map-container" ref={containerRef} />
}
export default GeoMap
@@ -0,0 +1,241 @@
import type {
DatasetCreateResponse,
DetectionModelCapability,
DetectionQaResult,
DetectionRead,
DetectionRunRead,
DetectionRunResponse,
} from '../../types'
interface DetectionLabProps {
detectionModels: DetectionModelCapability[]
loadingDetectionModels: boolean
detectionModelError: string | null
selectedDetectionDatasetId: string
selectedDetectionModelId: string
detectionTileManifestPath: string
detectionConfidenceThreshold: number
runningDetection: boolean
detectionRunResult: DetectionRunResponse | null
detectionRunError: string | null
detectionRuns: DetectionRunRead[]
selectedDetectionRunId: string
detectionItems: DetectionRead[]
detectionClassFilter: string
detectionMinConfidenceFilter: number
loadingDetectionResults: boolean
detectionReferenceDatasetId: string
detectionQaResult: DetectionQaResult | null
detectionQaError: string | null
runningDetectionQa: boolean
selectedProjectId: string | null
rasterDatasets: DatasetCreateResponse[]
referenceDatasets: DatasetCreateResponse[]
onLoadModels: () => void
onSelectDataset: (datasetId: string) => void
onSelectModel: (modelId: string) => void
onSetConfidenceThreshold: (value: number) => void
onSetTileManifestPath: (value: string) => void
onRunDetection: () => void
onLoadRuns: () => void
onSelectRun: (runId: string) => void
onSetClassFilter: (value: string) => void
onSetMinConfidenceFilter: (value: number) => void
onLoadResults: () => void
onSelectReferenceDataset: (datasetId: string) => void
onRunQa: () => void
}
export function DetectionLab({
detectionModels,
loadingDetectionModels,
detectionModelError,
selectedDetectionDatasetId,
selectedDetectionModelId,
detectionTileManifestPath,
detectionConfidenceThreshold,
runningDetection,
detectionRunResult,
detectionRunError,
detectionRuns,
selectedDetectionRunId,
detectionItems,
detectionClassFilter,
detectionMinConfidenceFilter,
loadingDetectionResults,
detectionReferenceDatasetId,
detectionQaResult,
detectionQaError,
runningDetectionQa,
selectedProjectId,
rasterDatasets,
referenceDatasets,
onLoadModels,
onSelectDataset,
onSelectModel,
onSetConfidenceThreshold,
onSetTileManifestPath,
onRunDetection,
onLoadRuns,
onSelectRun,
onSetClassFilter,
onSetMinConfidenceFilter,
onLoadResults,
onSelectReferenceDataset,
onRunQa,
}: DetectionLabProps): JSX.Element {
return (
<section>
<h2>Detection Lab</h2>
<button type="button" onClick={onLoadModels} disabled={loadingDetectionModels}>
Refresh detection models
</button>
{loadingDetectionModels ? <p>Loading detection models...</p> : null}
{detectionModelError ? <p className="error">{detectionModelError}</p> : null}
{detectionModels.length === 0 && !loadingDetectionModels ? <p>No detection models reported by backend</p> : null}
<ul>
{detectionModels.map((model) => (
<li key={model.model_id}>
<strong>{model.display_name}</strong>
<div>model: {model.model_id}</div>
<div>framework: {model.framework}</div>
<div>task: {model.task_type}</div>
<div>status: {model.status}</div>
<div>configured: {model.configured ? 'yes' : 'no'}</div>
<div>classes: {model.supported_classes.join(', ')}</div>
<div>limitation: {model.limitation_message}</div>
</li>
))}
</ul>
<div>
<select value={selectedDetectionDatasetId} onChange={(event) => onSelectDataset(event.target.value)}>
<option value="">Select raster dataset</option>
{rasterDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
<select value={selectedDetectionModelId} onChange={(event) => onSelectModel(event.target.value)}>
{detectionModels.map((model) => (
<option key={model.model_id} value={model.model_id}>
{model.display_name}
</option>
))}
</select>
<input
type="number"
min="0"
max="1"
step="0.05"
value={detectionConfidenceThreshold}
onChange={(event) => onSetConfidenceThreshold(Number(event.target.value))}
/>
{selectedDetectionModelId === 'yolo-configured' ? (
<input
type="text"
placeholder="Raster tile manifest path"
value={detectionTileManifestPath}
onChange={(event) => onSetTileManifestPath(event.target.value)}
/>
) : null}
<button type="button" onClick={onRunDetection} disabled={runningDetection || !selectedProjectId || rasterDatasets.length === 0}>
Run detection
</button>
</div>
{detectionRunError ? <p className="error">{detectionRunError}</p> : null}
{detectionRunResult ? (
<div>
<p>Status: {detectionRunResult.status}</p>
<p>Message: {detectionRunResult.message}</p>
<p>Analysis run: {detectionRunResult.analysis_run_id}</p>
<p>Job: {detectionRunResult.job_id}</p>
<p>Detections: {detectionRunResult.detection_count}</p>
{detectionRunResult.error_code ? <p className="error">Code: {detectionRunResult.error_code}</p> : null}
</div>
) : null}
<div>
<h3>Detection results</h3>
<button type="button" onClick={onLoadRuns} disabled={!selectedProjectId}>
Refresh detection runs
</button>
<select value={selectedDetectionRunId} onChange={(event) => onSelectRun(event.target.value)}>
<option value="">Select detection run</option>
{detectionRuns.map((run) => (
<option key={run.id} value={run.id}>
{run.model_name || 'detection'} - {run.status} - {run.id}
</option>
))}
</select>
<input
type="text"
placeholder="Class filter"
value={detectionClassFilter}
onChange={(event) => onSetClassFilter(event.target.value)}
/>
<input
type="number"
min="0"
max="1"
step="0.05"
value={detectionMinConfidenceFilter}
onChange={(event) => onSetMinConfidenceFilter(Number(event.target.value))}
/>
<button type="button" onClick={onLoadResults} disabled={!selectedDetectionRunId || loadingDetectionResults}>
Load detections
</button>
{loadingDetectionResults ? <p>Loading detection results...</p> : null}
<p>Detections loaded: {detectionItems.length}</p>
{detectionItems.length > 0 ? (
<table>
<thead>
<tr>
<th>Class</th>
<th>Confidence</th>
<th>Model</th>
<th>Source tile</th>
</tr>
</thead>
<tbody>
{detectionItems.map((detection) => (
<tr key={detection.id}>
<td>{detection.class_name}</td>
<td>{detection.confidence.toFixed(2)}</td>
<td>{detection.model_name}</td>
<td>{detection.source_tile_path || 'n/a'}</td>
</tr>
))}
</tbody>
</table>
) : null}
</div>
<div>
<h3>Detection QA</h3>
<select value={detectionReferenceDatasetId} onChange={(event) => onSelectReferenceDataset(event.target.value)}>
<option value="">Select reference dataset</option>
{referenceDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
<button type="button" onClick={onRunQa} disabled={runningDetectionQa || !selectedDetectionRunId || !detectionReferenceDatasetId}>
Compare detections to reference
</button>
{detectionQaError ? <p className="error">{detectionQaError}</p> : null}
{detectionQaResult ? (
<div>
<p>Status: {detectionQaResult.status}</p>
<p>Quality check: {detectionQaResult.quality_check_id}</p>
<p>Precision: {detectionQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
<p>Recall: {detectionQaResult.recall?.toFixed(3) ?? 'n/a'}</p>
<p>F1: {detectionQaResult.f1_score?.toFixed(3) ?? 'n/a'}</p>
<p>Mean IoU: {detectionQaResult.mean_iou?.toFixed(3) ?? 'n/a'}</p>
<p>False positives: {detectionQaResult.false_positives}</p>
<p>False negatives: {detectionQaResult.false_negatives}</p>
</div>
) : null}
</div>
</section>
)
}
@@ -0,0 +1,94 @@
import type { DatasetCreateResponse, ExportCreateResponse, ExportRead } from '../../types'
interface ExportCenterProps {
selectedProjectId: string | null
selectedDataset: DatasetCreateResponse | null
selectedDetectionRunId: string
selectedSegmentationRunId: string
exports: ExportRead[]
latestExport: ExportCreateResponse | null
exportError: string | null
loadingExports: boolean
exporting: boolean
onRefresh: () => void
onExportDataset: () => void
onExportDetectionRun: () => void
onExportSegmentationRun: () => void
onExportProjectMetadata: () => void
onExportProjectReport: () => void
onPreviewContent: (exportId: string) => void
onDownload: (exportId: string) => void
}
function isVectorDatasetType(datasetType: string): boolean {
return datasetType === 'vector' || datasetType === 'geojson'
}
export function ExportCenter({
selectedProjectId,
selectedDataset,
selectedDetectionRunId,
selectedSegmentationRunId,
exports,
latestExport,
exportError,
loadingExports,
exporting,
onRefresh,
onExportDataset,
onExportDetectionRun,
onExportSegmentationRun,
onExportProjectMetadata,
onExportProjectReport,
onPreviewContent,
onDownload,
}: ExportCenterProps): JSX.Element {
const canExportDataset = Boolean(selectedDataset && isVectorDatasetType(selectedDataset.dataset_type))
return (
<section>
<h2>Export Center</h2>
<button type="button" onClick={onRefresh} disabled={!selectedProjectId || loadingExports}>
Refresh exports
</button>
<button type="button" onClick={onExportProjectMetadata} disabled={!selectedProjectId || exporting}>
Export project metadata JSON
</button>
<button type="button" onClick={onExportProjectReport} disabled={!selectedProjectId || exporting}>
Export project report HTML
</button>
<button type="button" onClick={onExportDataset} disabled={!canExportDataset || exporting}>
Export selected vector GeoJSON
</button>
<button type="button" onClick={onExportDetectionRun} disabled={!selectedDetectionRunId || exporting}>
Export selected detection run GeoJSON
</button>
<button type="button" onClick={onExportSegmentationRun} disabled={!selectedSegmentationRunId || exporting}>
Export selected segmentation run GeoJSON
</button>
{exportError ? <p className="error">{exportError}</p> : null}
{latestExport ? (
<p>
Latest export: {latestExport.export_type} {'->'} {latestExport.path}
</p>
) : null}
{exports.length === 0 ? <p>No exports registered yet.</p> : null}
<ul>
{exports.map((item) => (
<li key={item.id}>
<strong>{item.export_type}</strong>
<div>status: {item.status}</div>
<div>path: {item.storage_path}</div>
<div>export id: {item.id}</div>
<button type="button" onClick={() => onPreviewContent(item.id)}>
Preview JSON content
</button>
<button type="button" onClick={() => onDownload(item.id)}>
Download artifact
</button>
</li>
))}
</ul>
</section>
)
}
@@ -0,0 +1,66 @@
import type { FormEvent } from 'react'
import type { AreaRead, ProjectRead } from '../../types'
interface AreaFormState {
name: string
geometry: string
crs: string
}
interface AreaPanelProps {
areas: AreaRead[]
selectedProject: ProjectRead | null
selectedProjectId: string | null
loadingAreas: boolean
areaForm: AreaFormState
onCreateArea: (event: FormEvent<HTMLFormElement>) => void
onUpdateAreaForm: (areaForm: AreaFormState) => void
}
export function AreaPanel({
areas,
selectedProject,
selectedProjectId,
loadingAreas,
areaForm,
onCreateArea,
onUpdateAreaForm,
}: AreaPanelProps): JSX.Element {
return (
<section>
<h2>Area manager</h2>
<p>{selectedProject ? `Selected project: ${selectedProject.name}` : 'Select a project first'}</p>
<form onSubmit={onCreateArea}>
<input
value={areaForm.name}
onChange={(event) => onUpdateAreaForm({ ...areaForm, name: event.target.value })}
placeholder="AOI name"
/>
<input
value={areaForm.crs}
onChange={(event) => onUpdateAreaForm({ ...areaForm, crs: event.target.value })}
placeholder="EPSG:4326"
/>
<textarea
value={areaForm.geometry}
onChange={(event) => onUpdateAreaForm({ ...areaForm, geometry: event.target.value })}
rows={4}
/>
<button type="submit" disabled={!selectedProjectId}>
Create area
</button>
</form>
{loadingAreas ? <p>Loading areas...</p> : null}
{areas.length === 0 ? <p>No areas yet</p> : null}
<ul>
{areas.map((area) => (
<li key={area.id}>
{area.name} · {area.area_m2 ? `${area.area_m2.toFixed(2)} m²` : 'n/a'}
</li>
))}
</ul>
</section>
)
}
@@ -0,0 +1,76 @@
import type { FormEvent } from 'react'
import type { ProjectCreate, ProjectRead } from '../../types'
interface ProjectPanelProps {
projects: ProjectRead[]
selectedProjectId: string | null
loadingProjects: boolean
projectForm: ProjectCreate
loadingDemoWorkflow: boolean
demoWorkflowMessage: string | null
onCreateProject: (event: FormEvent<HTMLFormElement>) => void
onUpdateProjectForm: (projectForm: ProjectCreate) => void
onSelectProject: (projectId: string) => void
onLoadDemoWorkflow: () => void
}
export function ProjectPanel({
projects,
selectedProjectId,
loadingProjects,
projectForm,
loadingDemoWorkflow,
demoWorkflowMessage,
onCreateProject,
onUpdateProjectForm,
onSelectProject,
onLoadDemoWorkflow,
}: ProjectPanelProps): JSX.Element {
return (
<section>
<h2>Projects</h2>
{loadingProjects ? <p>Loading projects...</p> : null}
<form onSubmit={onCreateProject}>
<input
value={projectForm.name}
onChange={(event) => onUpdateProjectForm({ ...projectForm, name: event.target.value })}
placeholder="Project name"
/>
<input
value={projectForm.description ?? ''}
onChange={(event) => onUpdateProjectForm({ ...projectForm, description: event.target.value })}
placeholder="Description"
/>
<input
value={projectForm.region ?? 'Kempen'}
onChange={(event) => onUpdateProjectForm({ ...projectForm, region: event.target.value })}
placeholder="Region"
/>
<button type="submit">Create project</button>
</form>
<div className="demo-actions">
<button type="button" onClick={onLoadDemoWorkflow} disabled={loadingDemoWorkflow}>
{loadingDemoWorkflow ? 'Loading demo...' : 'Load demo workflow'}
</button>
{demoWorkflowMessage ? <p>{demoWorkflowMessage}</p> : null}
</div>
<ul>
{projects.map((project) => (
<li key={project.id}>
<button
type="button"
onClick={() => onSelectProject(project.id)}
aria-pressed={project.id === selectedProjectId}
>
{project.name}
</button>
</li>
))}
{projects.length === 0 ? <li>No projects yet</li> : null}
</ul>
</section>
)
}
@@ -0,0 +1,47 @@
import type { ProviderCapability } from '../../types'
interface ProviderPanelProps {
providers: ProviderCapability[]
loadingCapabilities: boolean
capabilitiesError: string | null
onRefresh: () => void
}
export function ProviderPanel({
providers,
loadingCapabilities,
capabilitiesError,
onRefresh,
}: ProviderPanelProps): JSX.Element {
return (
<section>
<h2>Provider Capabilities</h2>
<button type="button" onClick={onRefresh} disabled={loadingCapabilities}>
Refresh providers
</button>
{loadingCapabilities ? <p>Loading provider capabilities...</p> : null}
{capabilitiesError ? <p className="error">{capabilitiesError}</p> : null}
{providers.length === 0 && !loadingCapabilities ? <p>No providers reported by backend</p> : null}
<ul>
{providers.map((provider) => (
<li key={provider.provider_name}>
<strong>{provider.display_name}</strong>
<div>provider: {provider.provider_name}</div>
<div>authority: {provider.authority_level}</div>
<div>status: {provider.status}</div>
<div>configured: {provider.configured ? 'yes' : 'no'}</div>
<div>layers: {provider.supported_layers.join(', ')}</div>
<div>geometry: {provider.supported_geometry_types.join(', ')}</div>
<div>query modes: {provider.supported_query_modes.join(', ')}</div>
<div>limitation: {provider.limitation_message}</div>
<div>attribution: {provider.attribution}</div>
<div>license: {provider.license_note}</div>
{!provider.configured && provider.not_configured_reason ? (
<div>reason: {provider.not_configured_reason}</div>
) : null}
</li>
))}
</ul>
</section>
)
}
@@ -0,0 +1,244 @@
import type {
DatasetCreateResponse,
SegmentationModelCapability,
SegmentationQaResult,
SegmentationRead,
SegmentationRunRead,
SegmentationRunResponse,
} from '../../types'
interface SegmentationLabProps {
segmentationModels: SegmentationModelCapability[]
loadingSegmentationModels: boolean
segmentationModelError: string | null
selectedSegmentationDatasetId: string
selectedSegmentationModelId: 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
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
}
export function SegmentationLab({
segmentationModels,
loadingSegmentationModels,
segmentationModelError,
selectedSegmentationDatasetId,
selectedSegmentationModelId,
segmentationConfidenceThreshold,
runningSegmentation,
segmentationRunResult,
segmentationRunError,
segmentationRuns,
selectedSegmentationRunId,
segmentationItems,
segmentationClassFilter,
segmentationMinConfidenceFilter,
loadingSegmentationResults,
segmentationReferenceDatasetId,
segmentationQaResult,
segmentationQaError,
runningSegmentationQa,
selectedProjectId,
rasterDatasets,
referenceDatasets,
selectedSegmentationModelConfigured,
selectedSegmentationModelLimitation,
onLoadModels,
onSelectDataset,
onSelectModel,
onSetConfidenceThreshold,
onRunSegmentation,
onLoadRuns,
onSelectRun,
onSetClassFilter,
onSetMinConfidenceFilter,
onLoadResults,
onSelectReferenceDataset,
onRunQa,
}: SegmentationLabProps): JSX.Element {
return (
<section>
<h2>Segmentation Lab</h2>
<button type="button" onClick={onLoadModels} disabled={loadingSegmentationModels}>
Refresh segmentation models
</button>
{loadingSegmentationModels ? <p>Loading segmentation models...</p> : null}
{segmentationModelError ? <p className="error">{segmentationModelError}</p> : null}
{segmentationModels.length === 0 && !loadingSegmentationModels ? <p>No segmentation models reported by backend</p> : null}
<ul>
{segmentationModels.map((model) => (
<li key={model.model_id}>
<strong>{model.display_name}</strong>
<div>model: {model.model_id}</div>
<div>framework: {model.framework}</div>
<div>task: {model.task_type}</div>
<div>status: {model.status}</div>
<div>configured: {model.configured ? 'yes' : 'no'}</div>
<div>classes: {model.supported_classes.join(', ')}</div>
<div>limitation: {model.limitation_message}</div>
</li>
))}
</ul>
<div>
<select value={selectedSegmentationDatasetId} onChange={(event) => onSelectDataset(event.target.value)}>
<option value="">Select raster dataset</option>
{rasterDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
<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>
<input
type="number"
min="0"
max="1"
step="0.05"
value={segmentationConfidenceThreshold}
onChange={(event) => onSetConfidenceThreshold(Number(event.target.value))}
/>
<button
type="button"
onClick={onRunSegmentation}
disabled={runningSegmentation || !selectedProjectId || rasterDatasets.length === 0 || !selectedSegmentationModelConfigured}
>
Run segmentation
</button>
</div>
{!selectedSegmentationModelConfigured ? (
<p>{selectedSegmentationModelLimitation ?? 'Select a configured segmentation model'}</p>
) : null}
{segmentationRunError ? <p className="error">{segmentationRunError}</p> : null}
{segmentationRunResult ? (
<div>
<p>Status: {segmentationRunResult.status}</p>
<p>Message: {segmentationRunResult.message}</p>
<p>Analysis run: {segmentationRunResult.analysis_run_id}</p>
<p>Job: {segmentationRunResult.job_id}</p>
<p>Segmentations: {segmentationRunResult.segmentation_count}</p>
{segmentationRunResult.error_code ? <p className="error">Code: {segmentationRunResult.error_code}</p> : null}
</div>
) : null}
<div>
<h3>Segmentation results</h3>
<button type="button" onClick={onLoadRuns} disabled={!selectedProjectId}>
Refresh segmentation runs
</button>
<select value={selectedSegmentationRunId} onChange={(event) => onSelectRun(event.target.value)}>
<option value="">Select segmentation run</option>
{segmentationRuns.map((run) => (
<option key={run.id} value={run.id}>
{run.model_name || 'segmentation'} - {run.status} - {run.id}
</option>
))}
</select>
<input
type="text"
placeholder="Class filter"
value={segmentationClassFilter}
onChange={(event) => onSetClassFilter(event.target.value)}
/>
<input
type="number"
min="0"
max="1"
step="0.05"
value={segmentationMinConfidenceFilter}
onChange={(event) => onSetMinConfidenceFilter(Number(event.target.value))}
/>
<button type="button" onClick={onLoadResults} disabled={!selectedSegmentationRunId || loadingSegmentationResults}>
Load segmentations
</button>
{loadingSegmentationResults ? <p>Loading segmentation results...</p> : null}
<p>Segmentations loaded: {segmentationItems.length}</p>
{segmentationItems.length > 0 ? (
<table>
<thead>
<tr>
<th>Class</th>
<th>Confidence</th>
<th>Area m2</th>
<th>Model</th>
<th>Tile</th>
<th>Mask path</th>
</tr>
</thead>
<tbody>
{segmentationItems.map((segmentation) => (
<tr key={segmentation.id}>
<td>{segmentation.class_name}</td>
<td>{segmentation.confidence?.toFixed(2) ?? 'n/a'}</td>
<td>{segmentation.area_m2?.toFixed(2) ?? 'n/a'}</td>
<td>{segmentation.model_name}</td>
<td>{segmentation.source_tile_path || (segmentation.tile_index ?? 'n/a')}</td>
<td>{segmentation.mask_path || 'n/a'}</td>
</tr>
))}
</tbody>
</table>
) : null}
</div>
<div>
<h3>Segmentation QA</h3>
<select value={segmentationReferenceDatasetId} onChange={(event) => onSelectReferenceDataset(event.target.value)}>
<option value="">Select reference dataset</option>
{referenceDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{dataset.name}
</option>
))}
</select>
<button type="button" onClick={onRunQa} disabled={runningSegmentationQa || !selectedSegmentationRunId || !segmentationReferenceDatasetId}>
Compare segmentations to reference
</button>
{segmentationQaError ? <p className="error">{segmentationQaError}</p> : null}
{segmentationQaResult ? (
<div>
<p>Status: {segmentationQaResult.status}</p>
<p>Quality check: {segmentationQaResult.quality_check_id}</p>
<p>Precision: {segmentationQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
<p>Recall: {segmentationQaResult.recall?.toFixed(3) ?? 'n/a'}</p>
<p>F1: {segmentationQaResult.f1_score?.toFixed(3) ?? 'n/a'}</p>
<p>Mean IoU: {segmentationQaResult.mean_iou?.toFixed(3) ?? 'n/a'}</p>
<p>False positives: {segmentationQaResult.false_positives}</p>
<p>False negatives: {segmentationQaResult.false_negatives}</p>
</div>
) : null}
</div>
</section>
)
}