Complete GeoIntel visual system and portfolio case study
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { AiPipelineIllustration } from './AiPipelineIllustration'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('AiPipelineIllustration', () => {
|
||||
it('shows the real readiness state and explains the selected stage', () => {
|
||||
render(
|
||||
<AiPipelineIllustration
|
||||
hasImagery
|
||||
hasTiles
|
||||
gpuReady
|
||||
hasDetections={false}
|
||||
hasQualityEvidence={false}
|
||||
running={false}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('CUDA gereed')).toBeTruthy()
|
||||
expect(screen.getByRole('tab', { name: /Detecties/ }).textContent).toContain('volgende stap')
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /NVIDIA GPU/ }))
|
||||
expect(screen.getByRole('tabpanel').textContent).toContain('Lokale PyTorch-inferentie')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useState } from 'react'
|
||||
import { BadgeCheck, Boxes, Cpu, Image, ScanSearch } from 'lucide-react'
|
||||
|
||||
interface AiPipelineIllustrationProps {
|
||||
hasImagery: boolean
|
||||
hasTiles: boolean
|
||||
gpuReady: boolean
|
||||
hasDetections: boolean
|
||||
hasQualityEvidence: boolean
|
||||
running: boolean
|
||||
}
|
||||
|
||||
const pipelineStages = [
|
||||
{ key: 'imagery', label: 'Orthofoto', title: 'Gegeorefereerd bronbeeld', detail: 'CRS, resolutie en ruimtelijke dekking blijven bij de dataset bewaard.', icon: Image },
|
||||
{ key: 'tiles', label: 'Beeldtegels', title: 'Controleerbare tilevoorbereiding', detail: 'Overlap en tile-identiteit houden detecties herleidbaar naar hun bronpixel.', icon: Boxes },
|
||||
{ key: 'gpu', label: 'NVIDIA GPU', title: 'Lokale PyTorch-inferentie', detail: 'GeoIntel gebruikt de server-GPU en faalt gesloten wanneer CUDA vereist maar niet beschikbaar is.', icon: Cpu },
|
||||
{ key: 'detections', label: 'Detecties', title: 'Gegeorefereerde gebouwobjecten', detail: 'Confidence, modelversie, brontegel en geometrie worden als reproduceerbaar resultaat bewaard.', icon: ScanSearch },
|
||||
{ key: 'quality', label: 'QA-bewijs', title: 'Controle vóór vrijgave', detail: 'Precision, recall, IoU en foutbewijs bepalen of een resultaat alleen verkennend of operationeel bruikbaar is.', icon: BadgeCheck },
|
||||
] as const
|
||||
|
||||
export function AiPipelineIllustration({
|
||||
hasImagery,
|
||||
hasTiles,
|
||||
gpuReady,
|
||||
hasDetections,
|
||||
hasQualityEvidence,
|
||||
running,
|
||||
}: AiPipelineIllustrationProps): JSX.Element {
|
||||
const readiness = [hasImagery, hasTiles, gpuReady, hasDetections, hasQualityEvidence]
|
||||
const firstIncomplete = readiness.findIndex((ready) => !ready)
|
||||
const [selectedIndex, setSelectedIndex] = useState(firstIncomplete === -1 ? 4 : firstIncomplete)
|
||||
const selected = pipelineStages[selectedIndex]
|
||||
|
||||
return (
|
||||
<section className={running ? 'ai-pipeline ai-pipeline-running' : 'ai-pipeline'} aria-labelledby="ai-pipeline-title">
|
||||
<div className="ai-pipeline-heading">
|
||||
<div>
|
||||
<p className="eyebrow">Van pixel naar bewijs</p>
|
||||
<h3 id="ai-pipeline-title">PyTorch-keten op de NVIDIA-server</h3>
|
||||
<p>Open een schakel om te zien welke technische context GeoIntel door de volledige analyse bewaart.</p>
|
||||
</div>
|
||||
<span className={gpuReady ? 'ai-pipeline-gpu ai-pipeline-gpu-ready' : 'ai-pipeline-gpu'}>
|
||||
<i /> {gpuReady ? 'CUDA gereed' : 'CUDA controleren'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="ai-pipeline-track" role="tablist" aria-label="PyTorch-analysekten">
|
||||
<span className="ai-pipeline-flow" aria-hidden="true" />
|
||||
{pipelineStages.map(({ key, label, icon: Icon }, index) => (
|
||||
<button
|
||||
key={key}
|
||||
id={`ai-pipeline-${key}`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={selectedIndex === index}
|
||||
aria-controls="ai-pipeline-detail"
|
||||
className={readiness[index] ? 'ai-pipeline-stage ai-pipeline-stage-ready' : 'ai-pipeline-stage'}
|
||||
onClick={() => setSelectedIndex(index)}
|
||||
>
|
||||
<span><Icon aria-hidden="true" /></span>
|
||||
<strong>{label}</strong>
|
||||
<small>{readiness[index] ? 'gereed' : index === firstIncomplete ? 'volgende stap' : 'wachten'}</small>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="ai-pipeline-detail"
|
||||
className="ai-pipeline-detail"
|
||||
role="tabpanel"
|
||||
aria-labelledby={`ai-pipeline-${selected.key}`}
|
||||
key={selected.key}
|
||||
>
|
||||
<span>{String(selectedIndex + 1).padStart(2, '0')}</span>
|
||||
<div><strong>{selected.title}</strong><p>{selected.detail}</p></div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
import type { DetectionCalibrationRunRow, DetectionWorkflowStage } from '../../hooks/useDetectionWorkflow'
|
||||
import { DETECTION_OPERATOR_PROFILES, type DetectionOperatorProfile } from './detectionProfiles'
|
||||
import { DetectionModelManagement, detectionModelLabel } from './DetectionModelManagement'
|
||||
import { AiPipelineIllustration } from './AiPipelineIllustration'
|
||||
|
||||
const DETECTION_PAGE_SIZE_OPTIONS = [25, 50, 100] as const
|
||||
const DEFAULT_DETECTION_PAGE_SIZE = 50
|
||||
@@ -285,6 +286,15 @@ export function DetectionLab({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AiPipelineIllustration
|
||||
hasImagery={detectionHasDataset}
|
||||
hasTiles={detectionHasTileManifest}
|
||||
gpuReady={yoloRuntimeReady && Boolean(yoloPreflight?.runtime.cuda_available)}
|
||||
hasDetections={detectionItems.length > 0 || detectionRuns.some((run) => run.status === 'completed')}
|
||||
hasQualityEvidence={Boolean(detectionQaResult) || qualityChecks.length > 0}
|
||||
running={runningDetection || runningDetectionQa || runningDetectionCalibration}
|
||||
/>
|
||||
|
||||
<div className="ai-user-summary" aria-label="Status gebouwdetectie">
|
||||
<div className="ai-user-summary-card ai-user-summary-card-primary">
|
||||
<span>Actieve analyse</span>
|
||||
|
||||
Reference in New Issue
Block a user