shuishen
11 hours ago fbb068ec702338d609c1ca6eddbdb9f182d8f211
apps/workbench-console/src/api/artifacts.ts
@@ -1,4 +1,5 @@
export interface Detection { class_id: number; class_name: string; confidence: number; bbox_xyxy: number[]; }
export interface GeoFeature { type?: string; properties?: Record<string, unknown>; geometry?: { type?: string; coordinates?: unknown }; }
export interface DetectionImage { file: string; annotated_file: string; width: number; height: number; detections: Detection[]; }
export interface DetectionRun { detection_count: number; processed_images: number; elapsed_seconds: number; device: string; model: string; confidence: number; input_dir: string; notes: string[]; created_at?: string; }
export interface DetectionCase { id: string; label: string; note: string; artifactRoot: string; inputRoot: string; createdAt: string; run: DetectionRun; images: DetectionImage[]; }
@@ -9,13 +10,22 @@
export interface MeasurementImage { file: string; width: number; height: number; raster_file: string; preview_file: string; vector_file: string; csv_file: string; object_count: number; class_counts: Record<string, number>; total_area: number; total_perimeter: number; area_unit: string; length_unit: string; measurement_basis: string; crs: string | null; georeferenced: boolean; vectorizer: string; elapsed_seconds: number; }
export interface MeasurementRun { capability: string; classification: string; created_at?: string; geoai_version: string; method: string; model: string; device: string; input_count: number; processed_images: number; elapsed_seconds: number; images: MeasurementImage[]; limitations: string[]; input_dir?: string; raw_input_dir?: string; }
export interface MeasurementCase { id: string; label: string; note: string; artifactRoot: string; createdAt: string; run: MeasurementRun; }
export interface PointCloudImage { file: string; original_points: number; downsampled_points: number; ground_inliers: number; elevated_points: number; elevated_point_ratio: number; elevated_footprint_count: number; mesh_triangles: number; mesh_skipped_for_large_cloud?: boolean; raster_width: number; raster_height: number; coordinate_basis: string; crs: string | null; classified_point_cloud: string; semantic_preview_point_cloud?: string; semantic_annotation_source_point_cloud?: string; semantic_preview_points?: number; semantic_classified_las?: string; semantic_raster_file?: string; semantic_preview_file?: string; semantic_vector_file?: string; semantic_summary_file?: string; semantic_footprint_count?: number; semantic_class_counts?: Record<string, number>; semantic_source_has_rgb?: boolean; semantic_method?: string; mesh_file: string | null; dsm_file: string; height_file: string; label_raster_file: string; preview_file: string; vector_file: string; summary_file: string; vectorizer: string; elapsed_seconds: number; }
export interface PhotoReconstruction { input_images: number; registered_images: number; unregistered_images: string[]; sparse_points: number; mean_reprojection_error_pixels: number; camera_count: number; coordinate_basis: string; point_cloud_file: string; preview_file: string; camera_pose_file: string; input_manifest_file: string; colmap_model_directory: string; elapsed_seconds: number; initial_min_triangulation_angle_degrees: number; }
export interface DensePhotoReconstruction { input_images: number; registered_images: number; sparse_points: number; dense_points: number; mesh_vertices: number; mesh_faces: number; preview_mesh_faces?: number; preview_mesh_face_ratio?: number; geometry_preview_faces?: number; point_color_preview_faces?: number; coordinate_basis: string; dense_point_cloud_file: string; mesh_file: string; textured_model_file: string; geometry_preview_model_file?: string; point_color_preview_model_file?: string; texture_preview_filter_report?: string; texture_file: string; elapsed_seconds: number | null; }
export interface PointCloudRun { capability: string; classification: string; created_at?: string; versions: Record<string, string>; method: string; model: string; device: string; thresholds: Record<string, number | string>; input_count?: number; processed_point_clouds?: number; elapsed_seconds: number; point_clouds?: PointCloudImage[]; photo_reconstruction?: PhotoReconstruction; dense_photo_reconstruction?: DensePhotoReconstruction; limitations: string[]; raw_input_dir?: string; }
export interface PointCloudCase { id: string; label: string; note: string; artifactRoot: string; createdAt: string; run: PointCloudRun; features: GeoFeature[]; }
export interface RiskScoreFeature { type: "Feature"; properties: { object_id: string; confidence: number; raw_score: number; risk_score: number; risk_level: "low" | "medium" | "high" | "critical"; score_capped: boolean; hit_rule_ids: string[]; suggestions: string[]; rule_contributions: Array<{ rule_id: string; score: number; suggestion: string }>; }; geometry: { type: string; coordinates: number[] }; }
export interface RiskSummary { object_count: number; risk_level_counts: Record<"low" | "medium" | "high" | "critical", number>; maximum_score: number; mean_score: number; hit_rule_counts: Record<string, number>; }
export interface RiskRuleRun { schema_version: number; capability: string; classification: string; created_at?: string; method: string; model: string; device: string; geoai_role: string; crs: string; zones_reprojected_to_observation_crs: boolean; parameters: { score_cap: number; levels: Record<string, number>; raster_resolution: number; rule_count: number; }; raster: { width: number; height: number; bounds: number[]; coordinate_basis: string; }; summary: RiskSummary; artifacts: { risk_raster: string; risk_preview: string; risk_vector: string; risk_scores_csv: string; summary: string; }; elapsed_seconds: number; limitations: string[]; }
export interface RiskRuleCase { id: string; label: string; note: string; artifactRoot: string; createdAt: string; run: RiskRuleRun; features: RiskScoreFeature[]; }
export interface AnomalyCandidate { type: "Feature"; properties: { feature_id?: number; mask_code?: number; method?: string; rule_flag?: boolean; isolation_flag?: boolean; agreement?: boolean; max_rule_score?: number; max_isolation_score?: number; reason_feature?: string; area_pixels?: number }; geometry: { type: string; coordinates: unknown }; }
export interface AnomalyImage { file: string; width: number; height: number; tile_count: number; rule_heatmap_file: string; isolation_heatmap_file: string; overlay_file: string; mask_file: string; vector_file: string; tiles_file: string; candidate_count: number; rule_anomaly_pixels: number; local_change_pixels?: number; isolation_anomaly_pixels: number; agreement_pixels: number; rule_anomaly_coverage: number; local_change_coverage?: number; isolation_anomaly_coverage: number; agreement_coverage: number; georeferenced: boolean; crs: string | null; coordinate_basis: string; vectorizer: string; elapsed_seconds: number; }
export interface AnomalyRun { capability: string; classification: string; created_at?: string; versions: Record<string, string>; method: string; model: string; device: string; reference_count: number; reference_tile_count: number; input_count: number; reference_images: Array<{ file: string; tile_count: number }>; feature_names: string[]; parameters: { tile_size: number; stride: number; threshold_quantile: number; random_state: number; spatial_mode_requested?: "auto" | "global" | "aligned"; rule_mode_selected?: "global" | "aligned" }; thresholds: { rule_score: number; isolation_score: number }; images: AnomalyImage[]; elapsed_seconds: number; limitations: string[]; }
export interface AnomalyCase { id: string; label: string; note: string; artifactRoot: string; inputRoot: string; referenceRoot: string; createdAt: string; run: AnomalyRun; candidates: Record<string, AnomalyCandidate[]>; }
export interface ChangeFeature { type: "Feature"; properties: { feature_id?: number; area_pixels?: number; mean_probability?: number; max_probability?: number; bounds_pixel?: number[]; confidence?: number; class?: number }; geometry: { type: string; coordinates: unknown }; }
export interface ChangeRun { schema_version: number; capability: string; classification: string; geoai_version: string; method: string; model: string; device: string; processing_mode?: "image" | "geotiff"; requested_processing_mode?: "auto" | "image" | "geotiff"; thresholds: Record<string, number>; tile_size: number; overlap: number; max_dimension?: number; effective_max_dimension?: number; input_files: string[]; input_shape: number[]; processed_shape: number[]; registration: { method: string; matches: number; inliers: number; inlier_ratio: number; valid_ratio: number }; valid_pixel_ratio: number; raw_changed_pixels: number; changed_pixels: number; changed_pixel_ratio: number; vector_feature_count: number; rectangle_feature_count?: number; georeferenced: boolean; coordinate_basis: string; crs: string | null; elapsed_seconds: number; limitations: string[]; artifacts: { probability_raster: string; raw_mask_raster: string; mask_raster: string; overlay: string; vector: string; rectangle_vector?: string; rectangle_vector_wgs84?: string | null; features: string }; created_at?: string; }
export interface ChangeCase { id: string; label: string; note: string; artifactRoot: string; beforeImage: string; afterImage: string; createdAt: string; run: ChangeRun; features: ChangeFeature[]; }
export interface ChangeRun { schema_version: number; capability: string; classification: string; geoai_version: string; method: string; model: string; device: string; processing_mode?: "image" | "geotiff"; requested_processing_mode?: "auto" | "image" | "geotiff"; thresholds: Record<string, number>; tile_size: number; overlap: number; max_dimension?: number; effective_max_dimension?: number; input_files: string[]; input_shape: number[]; processed_shape: number[]; registration: { method: string; matches: number; inliers: number; inlier_ratio: number; valid_ratio: number }; valid_pixel_ratio: number; raw_changed_pixels: number; changed_pixels: number; changed_pixel_ratio: number; vector_feature_count: number; rectangle_feature_count?: number; generic_difference?: { candidate_pixels: number; candidate_pixel_ratio: number; components_after_filter: number }; georeferenced: boolean; coordinate_basis: string; crs: string | null; elapsed_seconds: number; limitations: string[]; artifacts: { probability_raster: string; raw_mask_raster: string; mask_raster: string; generic_difference_mask?: string; overlay: string; model_overlay?: string; before_processed_preview?: string; after_registered_preview?: string; vector: string; rectangle_vector?: string; rectangle_vector_wgs84?: string | null; features: string }; created_at?: string; }
export interface ChangeCase { id: string; label: string; note: string; artifactRoot: string; beforeImage: string; afterImage: string; rawAfterImage?: string; registeredBeforeImage?: string; registeredAfterImage?: string; createdAt: string; run: ChangeRun; features: ChangeFeature[]; }
export interface ChangeScanResult { id: string; label: string; threshold: number; minimumAreaPixels: number; cleanedComponents: number; changedPixels: number; changedPixelRatio: number; vectorFeatureCount: number; fullVectorFeatureCount?: number | null; rectangleFeatureCount?: number | null; overlay: string; mask: string; regions: string; vector?: string | null; rectangleVector?: string | null; rectangleVectorWgs84?: string | null; }
export interface ChangeParameterScan { id: string; label: string; note: string; artifactRoot: string; sourceRun?: string; userSubmitted?: boolean; contactSheet?: string | null; results: ChangeScanResult[]; }
export interface ChangeScanJob { id: string; status: "queued" | "running" | "completed" | "failed"; phase?: "inference" | "parameter-scan" | "vectorization" | "done" | "error"; scanId?: string; error?: string; thresholds?: number[]; minimumAreas?: number[]; }
@@ -28,12 +38,22 @@
interface DetectionDefinition extends CaseDefinition { inputRoot: string; }
interface SemanticDefinition extends CaseDefinition { inputRoot: string; rawInputRoot: string; }
interface MeasurementDefinition extends CaseDefinition {}
interface PointCloudDefinition extends CaseDefinition {}
interface RiskRuleDefinition extends CaseDefinition {}
interface ChangeDefinition extends CaseDefinition { beforeImage: string; afterImage: string; }
interface AnomalyDefinition extends CaseDefinition { inputRoot: string; referenceRoot: string; }
export interface UploadFilePayload { name: string; content: string; }
export interface ChangeUploadRef { uploadId: string; role: "before" | "after"; name: string; size: number; }
export interface AnomalyUploadRef { uploadId: string; role: "reference" | "input"; name: string; size: number; sha256: string; }
export interface AnomalyJob { id: string; runId: string; status: "queued" | "running" | "complete" | "failed"; createdAt: string; finishedAt?: string; error?: string; run?: AnomalyDefinition; }
export interface PhotoReconstructionUploadRef { uploadId: string; role: "photo"; name: string; size: number; sha256: string; }
export interface PointCloudUploadRef { uploadId: string; role: "pointcloud"; name: string; size: number; sha256: string; }
export interface PhotoReconstructionJob { id: string; runId: string; inputImages: number; usePositionPriors: boolean; status: "queued" | "running" | "complete" | "failed"; stage: "queued" | "sparse_sfm" | "dense_mvs" | "complete" | "failed"; createdAt: string; startedAt?: string; finishedAt?: string; error?: string; run?: PointCloudDefinition; }
export interface PointCloudAnnotationSource { id: string; runId: string; label: string; artifactRoot: string; file: string; url: string; sha256: string; pointCount: number; sourceKind: string; }
export interface PointCloudAnnotation { id: string; sourceId: string; createdAt: string; labelCount: number; classCounts: Record<string, number>; path: string; }
export interface PointCloudTrainingJob { id: string; annotationId: string; status: "queued" | "running" | "complete" | "failed"; stage: string; device: string; createdAt: string; error?: string; artifactRoot?: string; metrics?: string; model?: string; preview?: string; }
export interface PointCloudSemanticModel { id: string; label: string; artifactRoot: string; model: string; metrics: string; createdAt: string; classes: Record<string, { key: string; label: string; color: number[] }>; testF1: Record<string, number>; }
export interface PointCloudInferenceJob { id: string; runId: string; modelId: string; inputName: string; status: "queued" | "running" | "complete" | "failed"; stage: string; device: string; createdAt: string; error?: string; artifactRoot?: string; metadata?: string; preview?: string; classifiedLas?: string; classCounts?: string; summary?: string; }
export const artifactUrl = (path: string) => `/${path.replace(/\\/g, "/").split("/").map(encodeURIComponent).join("/")}`;
@@ -80,6 +100,29 @@
  return Object.fromEntries(cases.map((item) => [item.id, item]));
}
export async function loadPointCloudArtifacts(): Promise<Record<string, PointCloudCase>> {
  const { runs } = await getJson<{ runs: PointCloudDefinition[] }>("api/3d-pointcloud/runs");
  const cases = await Promise.all(runs.map(async (definition) => {
    const run = await getJson<PointCloudRun>(`${definition.artifactRoot}/run_metadata.json`);
    const features = (await Promise.all((run.point_clouds ?? []).map(async (item) => {
      const payload = await getJson<{ features?: GeoFeature[] }>(`${definition.artifactRoot}/${item.vector_file}`);
      return payload.features ?? [];
    }))).flat();
    return { ...definition, run, features } satisfies PointCloudCase;
  }));
  return Object.fromEntries(cases.map((item) => [item.id, item]));
}
export async function loadRiskRuleArtifacts(): Promise<Record<string, RiskRuleCase>> {
  const { runs } = await getJson<{ runs: RiskRuleDefinition[] }>("api/risk-rule-engine/runs");
  const cases = await Promise.all(runs.map(async (definition) => {
    const run = await getJson<RiskRuleRun>(`${definition.artifactRoot}/run_metadata.json`);
    const vector = await getJson<{ features?: RiskScoreFeature[] }>(`${definition.artifactRoot}/${run.artifacts.risk_vector}`);
    return { ...definition, run, features: vector.features ?? [] } satisfies RiskRuleCase;
  }));
  return Object.fromEntries(cases.map((item) => [item.id, item]));
}
export async function loadChangeArtifacts(): Promise<Record<string, ChangeCase>> {
  const { runs } = await getJson<{ runs: ChangeDefinition[] }>("api/change-detection/runs");
  const cases = await Promise.all(runs.map(async (definition) => {
@@ -111,6 +154,30 @@
export async function createDetectionRun(images: UploadFilePayload[]) { return postRun<{ run: DetectionDefinition }>("api/object-detection/runs", { images }); }
export async function createSemanticRun(images: UploadFilePayload[], taskId: string) { return postRun<{ run: SemanticDefinition }>("api/semantic-mapping/runs", { images, taskId }); }
export async function createMeasurementRun(rasters: UploadFilePayload[]) { return postRun<{ run: MeasurementDefinition }>("api/spatial-measurement/runs", { rasters }); }
export async function createPointCloudRun(pointClouds: Array<UploadFilePayload | PointCloudUploadRef>) { return postRun<{ run: PointCloudDefinition }>("api/3d-pointcloud/runs", { pointClouds }); }
export async function createDensePointCloudClassification(sourceDenseRunId: string) { return postRun<{ run: PointCloudDefinition }>("api/3d-pointcloud/runs", { sourceDenseRunId }); }
export async function uploadPointCloudFile(file: File): Promise<PointCloudUploadRef> {
  const uploadId = globalThis.crypto.randomUUID().replaceAll("-", "");
  const response = await fetch(`/api/3d-pointcloud/pointcloud-uploads/${uploadId}?role=pointcloud`, { method: "PUT", headers: { "Content-Type": "application/octet-stream", "X-Upload-Name": encodeURIComponent(file.name) }, body: file });
  return responseJson<PointCloudUploadRef>(response);
}
export async function uploadPhotoReconstructionFile(file: File): Promise<PhotoReconstructionUploadRef> {
  const uploadId = globalThis.crypto.randomUUID().replaceAll("-", "");
  const response = await fetch(`/api/3d-pointcloud/photo-uploads/${uploadId}?role=photo`, { method: "PUT", headers: { "Content-Type": "application/octet-stream", "X-Upload-Name": encodeURIComponent(file.name) }, body: file });
  return responseJson<PhotoReconstructionUploadRef>(response);
}
export async function createPhotoReconstructionRun(photos: PhotoReconstructionUploadRef[], usePositionPriors: boolean) { return postRun<{ job: PhotoReconstructionJob }>("api/3d-pointcloud/photo-reconstruction-runs", { photos, usePositionPriors }); }
export async function loadPhotoReconstructionJob(jobId: string) { return (await getJson<{ job: PhotoReconstructionJob }>(`api/3d-pointcloud/photo-reconstruction-jobs/${jobId}`)).job; }
export async function loadPointCloudAnnotationSources() { return (await getJson<{ sources: PointCloudAnnotationSource[] }>("api/3d-pointcloud/annotation-sources")).sources; }
export async function loadPointCloudAnnotations() { return (await getJson<{ annotations: PointCloudAnnotation[] }>("api/3d-pointcloud/annotations")).annotations; }
export async function createPointCloudAnnotation(sourceId: string, labels: Array<[number, number]>) { return postRun<{ annotation: PointCloudAnnotation }>("api/3d-pointcloud/annotations", { sourceId, labels }); }
export async function deletePointCloudAnnotation(annotationId: string) { return responseJson<{ deletedId: string }>(await fetch(artifactUrl(`api/3d-pointcloud/annotations/${encodeURIComponent(annotationId)}`), { method: "DELETE" })); }
export async function createPointCloudTrainingRun(annotationId: string, device: "auto" | "cpu" | "cuda" = "cpu") { return postRun<{ job: PointCloudTrainingJob }>("api/3d-pointcloud/training-runs", { annotationId, device }); }
export async function loadPointCloudTrainingJob(jobId: string) { return (await getJson<{ job: PointCloudTrainingJob }>(`api/3d-pointcloud/training-jobs/${jobId}`)).job; }
export async function loadPointCloudSemanticModels() { return (await getJson<{ models: PointCloudSemanticModel[] }>("api/3d-pointcloud/semantic-models")).models; }
export async function createPointCloudModelInference(modelId: string, pointCloud: PointCloudUploadRef) { return postRun<{ job: PointCloudInferenceJob }>("api/3d-pointcloud/model-inference-runs", { modelId, pointCloud }); }
export async function loadPointCloudModelInferenceJob(jobId: string) { return (await getJson<{ job: PointCloudInferenceJob }>(`api/3d-pointcloud/model-inference-jobs/${jobId}`)).job; }
export async function createRiskRuleRun(files: { observations: UploadFilePayload; zones: UploadFilePayload; rules: UploadFilePayload }) { return postRun<{ run: RiskRuleDefinition }>("api/risk-rule-engine/runs", { files }); }
export async function createChangeRun(files: { before: UploadFilePayload; after: UploadFilePayload }, threshold = 0.5, maxDimension = 0, processingMode: "auto" | "image" | "geotiff" = "auto") { return postRun<{ run: ChangeDefinition }>("api/change-detection/runs", { files, threshold, maxDimension, processingMode }); }
export async function uploadChangeFile(file: File, role: "before" | "after"): Promise<ChangeUploadRef> {
  const uploadId = globalThis.crypto.randomUUID().replaceAll("-", "");