Surface YOLO preflight in Detection Lab
This commit is contained in:
@@ -120,6 +120,7 @@ AI Lab run controls explicitly explain when no raster dataset is available, inst
|
||||
## Sprint 8B additions
|
||||
- Detection Lab now exposes the `yolo-configured` capability reported by the backend.
|
||||
- When `yolo-configured` is selected, users can provide an existing raster tile manifest path.
|
||||
- Detection Lab includes a read-only YOLO runtime preflight panel with backend status, dependency visibility, local model configuration, `torch`/`ultralytics` versions, CUDA state and `YOLO_CONFIG_DIR`.
|
||||
- The UI still does not download models or create fake detections; backend status and error codes remain the source of truth.
|
||||
|
||||
## Sprint 8C additions
|
||||
|
||||
@@ -242,7 +242,11 @@ function App(): JSX.Element {
|
||||
detectionQaResult,
|
||||
detectionQaError,
|
||||
runningDetectionQa,
|
||||
yoloPreflight,
|
||||
loadingYoloPreflight,
|
||||
yoloPreflightError,
|
||||
loadDetectionModels,
|
||||
loadYoloPreflight,
|
||||
loadDetectionRuns,
|
||||
loadDetectionResults,
|
||||
runDetection,
|
||||
@@ -945,10 +949,14 @@ function App(): JSX.Element {
|
||||
detectionQaResult={detectionQaResult}
|
||||
detectionQaError={detectionQaError}
|
||||
runningDetectionQa={runningDetectionQa}
|
||||
yoloPreflight={yoloPreflight}
|
||||
loadingYoloPreflight={loadingYoloPreflight}
|
||||
yoloPreflightError={yoloPreflightError}
|
||||
selectedProjectId={selectedProjectId}
|
||||
rasterDatasets={rasterDatasets}
|
||||
referenceDatasets={referenceDatasets}
|
||||
onLoadModels={loadDetectionModels}
|
||||
onRefreshYoloPreflight={() => loadYoloPreflight()}
|
||||
onSelectDataset={setSelectedDetectionDatasetId}
|
||||
onSelectModel={setSelectedDetectionModelId}
|
||||
onSetConfidenceThreshold={setDetectionConfidenceThreshold}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
DetectionRead,
|
||||
DetectionRunRead,
|
||||
DetectionRunResponse,
|
||||
YoloPreflightResponse,
|
||||
} from '../../types'
|
||||
|
||||
interface DetectionLabProps {
|
||||
@@ -28,10 +29,14 @@ interface DetectionLabProps {
|
||||
detectionQaResult: DetectionQaResult | null
|
||||
detectionQaError: string | null
|
||||
runningDetectionQa: boolean
|
||||
yoloPreflight: YoloPreflightResponse | null
|
||||
loadingYoloPreflight: boolean
|
||||
yoloPreflightError: string | null
|
||||
selectedProjectId: string | null
|
||||
rasterDatasets: DatasetCreateResponse[]
|
||||
referenceDatasets: DatasetCreateResponse[]
|
||||
onLoadModels: () => void
|
||||
onRefreshYoloPreflight: () => void
|
||||
onSelectDataset: (datasetId: string) => void
|
||||
onSelectModel: (modelId: string) => void
|
||||
onSetConfidenceThreshold: (value: number) => void
|
||||
@@ -67,10 +72,14 @@ export function DetectionLab({
|
||||
detectionQaResult,
|
||||
detectionQaError,
|
||||
runningDetectionQa,
|
||||
yoloPreflight,
|
||||
loadingYoloPreflight,
|
||||
yoloPreflightError,
|
||||
selectedProjectId,
|
||||
rasterDatasets,
|
||||
referenceDatasets,
|
||||
onLoadModels,
|
||||
onRefreshYoloPreflight,
|
||||
onSelectDataset,
|
||||
onSelectModel,
|
||||
onSetConfidenceThreshold,
|
||||
@@ -164,6 +173,76 @@ export function DetectionLab({
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="ai-lab-model-surface" aria-label="YOLO runtime preflight">
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>YOLO runtime preflight</h3>
|
||||
<p>Read-only runtime status. This does not load a model, run inference or download weights.</p>
|
||||
</div>
|
||||
<button className="secondary-action" type="button" onClick={onRefreshYoloPreflight} disabled={loadingYoloPreflight}>
|
||||
Refresh preflight
|
||||
</button>
|
||||
</div>
|
||||
<div className="ai-lab-state-stack">
|
||||
{loadingYoloPreflight ? (
|
||||
<div className="result-state result-state-loading">
|
||||
<strong>Loading YOLO preflight.</strong>
|
||||
<p>Checking backend runtime configuration and optional dependency visibility.</p>
|
||||
</div>
|
||||
) : null}
|
||||
{yoloPreflightError ? (
|
||||
<div className="result-state result-state-error">
|
||||
<strong>YOLO preflight unavailable.</strong>
|
||||
<p>{yoloPreflightError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{!yoloPreflight && !loadingYoloPreflight && !yoloPreflightError ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>No YOLO preflight loaded.</strong>
|
||||
<p>Refresh preflight to inspect the live backend AI runtime before running configured YOLO.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{yoloPreflight ? (
|
||||
<div className={yoloPreflight.status === 'ready' ? 'lab-readiness-panel lab-readiness-panel-ready' : 'lab-readiness-panel'}>
|
||||
<div className="ai-lab-section-header">
|
||||
<div>
|
||||
<h3>Status: {yoloPreflight.status}</h3>
|
||||
<p>{yoloPreflight.message}</p>
|
||||
</div>
|
||||
<span className={yoloPreflight.status === 'ready' ? 'status-badge status-badge-ready' : 'status-badge'}>
|
||||
{yoloPreflight.checks.dependencies_available ? 'dependencies visible' : 'not ready'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="lab-readiness-grid">
|
||||
<div className={yoloPreflight.checks.enabled ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>YOLO enabled</span>
|
||||
<strong>{yoloPreflight.checks.enabled ? 'true' : 'false'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.dependencies_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Dependencies</span>
|
||||
<strong>{yoloPreflight.checks.dependencies_available === true ? 'available' : yoloPreflight.checks.dependencies_available === false ? 'unavailable' : 'not checked'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.checks.model_file_exists ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>Local model file</span>
|
||||
<strong>{yoloPreflight.checks.model_file_exists === true ? 'found' : yoloPreflight.checks.model_path_set ? 'missing' : 'not configured'}</strong>
|
||||
</div>
|
||||
<div className={yoloPreflight.runtime.cuda_available ? 'lab-readiness-item lab-readiness-item-ready' : 'lab-readiness-item'}>
|
||||
<span>CUDA</span>
|
||||
<strong>{yoloPreflight.runtime.cuda_available === true ? 'available' : yoloPreflight.runtime.cuda_available === false ? 'not available' : 'not checked'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="entity-meta">
|
||||
<span>torch_version: {yoloPreflight.runtime.torch_version ?? 'n/a'}</span>
|
||||
<span>ultralytics_version: {yoloPreflight.runtime.ultralytics_version ?? 'n/a'}</span>
|
||||
<span>cuda_available: {String(yoloPreflight.runtime.cuda_available ?? 'unknown')}</span>
|
||||
<span>YOLO_CONFIG_DIR: {yoloPreflight.runtime.yolo_config_dir ?? 'n/a'}</span>
|
||||
<span>model directory: {yoloPreflight.runtime.model_directory ?? 'n/a'}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="lab-block">
|
||||
<div className="ai-lab-run-surface" aria-label="Detection run controls">
|
||||
<h3>Run detection</h3>
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
DetectionRunRead,
|
||||
DetectionRunResponse,
|
||||
QualityCheckRead,
|
||||
YoloPreflightResponse,
|
||||
} from '../types'
|
||||
import { formatError } from '../lib/formatError'
|
||||
|
||||
@@ -47,6 +48,9 @@ export function useDetectionWorkflow({
|
||||
const [detectionQaResult, setDetectionQaResult] = useState<DetectionQaResult | null>(null)
|
||||
const [detectionQaError, setDetectionQaError] = useState<string | null>(null)
|
||||
const [runningDetectionQa, setRunningDetectionQa] = useState(false)
|
||||
const [yoloPreflight, setYoloPreflight] = useState<YoloPreflightResponse | null>(null)
|
||||
const [loadingYoloPreflight, setLoadingYoloPreflight] = useState(false)
|
||||
const [yoloPreflightError, setYoloPreflightError] = useState<string | null>(null)
|
||||
|
||||
const loadDetectionModels = async () => {
|
||||
setLoadingDetectionModels(true)
|
||||
@@ -64,6 +68,21 @@ export function useDetectionWorkflow({
|
||||
}
|
||||
}
|
||||
|
||||
const loadYoloPreflight = async (tileManifestPath = detectionTileManifestPath) => {
|
||||
setLoadingYoloPreflight(true)
|
||||
setYoloPreflightError(null)
|
||||
try {
|
||||
const response = await detectionApi.getYoloPreflight({
|
||||
tile_manifest_path: tileManifestPath.trim() || null,
|
||||
})
|
||||
setYoloPreflight(response)
|
||||
} catch (error) {
|
||||
setYoloPreflightError(formatError(error, 'Failed to load YOLO preflight status'))
|
||||
} finally {
|
||||
setLoadingYoloPreflight(false)
|
||||
}
|
||||
}
|
||||
|
||||
const loadDetectionRuns = async (projectId = selectedProjectId) => {
|
||||
if (!projectId) {
|
||||
setDetectionRuns([])
|
||||
@@ -199,7 +218,11 @@ export function useDetectionWorkflow({
|
||||
detectionQaResult,
|
||||
detectionQaError,
|
||||
runningDetectionQa,
|
||||
yoloPreflight,
|
||||
loadingYoloPreflight,
|
||||
yoloPreflightError,
|
||||
loadDetectionModels,
|
||||
loadYoloPreflight,
|
||||
loadDetectionRuns,
|
||||
loadDetectionResults,
|
||||
runDetection,
|
||||
|
||||
@@ -8,9 +8,10 @@ import type {
|
||||
DetectionRunRead,
|
||||
DetectionRunRequest,
|
||||
DetectionRunResponse,
|
||||
YoloPreflightResponse,
|
||||
} from '../../types'
|
||||
|
||||
function queryString(params: Record<string, string | number | null | undefined>): string {
|
||||
function queryString(params: Record<string, string | number | boolean | null | undefined>): string {
|
||||
const searchParams = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
@@ -23,6 +24,8 @@ function queryString(params: Record<string, string | number | null | undefined>)
|
||||
|
||||
export const detectionApi = {
|
||||
listModels: (): Promise<DetectionModelsResponse> => apiGet<DetectionModelsResponse>('/api/v1/detection/models'),
|
||||
getYoloPreflight: (params: { tile_manifest_path?: string | null; check_model_load?: boolean | null } = {}): Promise<YoloPreflightResponse> =>
|
||||
apiGet<YoloPreflightResponse>(`/api/v1/detection/yolo/preflight${queryString(params)}`),
|
||||
run: (payload: DetectionRunRequest): Promise<DetectionRunResponse> =>
|
||||
apiPost<DetectionRunResponse>('/api/v1/detection/run', payload),
|
||||
listRuns: (params: { project_id?: string | null; dataset_id?: string | null } = {}): Promise<DetectionRunListResponse> =>
|
||||
|
||||
@@ -404,6 +404,43 @@ export interface DetectionModelsResponse {
|
||||
models: DetectionModelCapability[]
|
||||
}
|
||||
|
||||
export interface YoloPreflightChecks {
|
||||
enabled: boolean
|
||||
dependencies_available?: boolean | null
|
||||
model_path_set?: boolean | null
|
||||
model_file_exists?: boolean | null
|
||||
model_load_requested: boolean
|
||||
model_load_ok?: boolean | null
|
||||
manifest_path_set?: boolean | null
|
||||
manifest_valid?: boolean | null
|
||||
tile_paths_exist?: boolean | null
|
||||
tile_limit_ok?: boolean | null
|
||||
}
|
||||
|
||||
export interface YoloPreflightRuntime {
|
||||
dependencies_assumed: boolean
|
||||
model_directory?: string | null
|
||||
yolo_config_dir?: string | null
|
||||
torch_version?: string | null
|
||||
ultralytics_version?: string | null
|
||||
cuda_available?: boolean | null
|
||||
}
|
||||
|
||||
export interface YoloPreflightResponse {
|
||||
model_id: string
|
||||
model_path?: string | null
|
||||
tile_manifest_path?: string | null
|
||||
status: string
|
||||
message: string
|
||||
checks: YoloPreflightChecks
|
||||
runtime: YoloPreflightRuntime
|
||||
tile_count: number
|
||||
max_tiles: number
|
||||
will_download_models: boolean
|
||||
will_run_inference: boolean
|
||||
error_code?: string | null
|
||||
}
|
||||
|
||||
export interface DetectionRunRequest {
|
||||
project_id: string
|
||||
dataset_id: string
|
||||
|
||||
Reference in New Issue
Block a user