1use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
7use qualia_core_db::container_10d::provenance_section::ProvenanceSidecar;
8use qualia_core_db::render::assets::{mesh_to_nquins_with_digests, Mesh};
9use qualia_core_db::render::compile_10d::{
10 compile_mesh_to_10d_vision, compile_mesh_to_10d_vision_with_provenance,
11};
12use qualia_core_db::sparql_library::vision_shacl::{
13 validate_vision_observation_graph, VisionShaclReport,
14};
15use qualia_core_db::specialized_libs::computational_geometry::{
16 decimate_qem, DecimateOptions, Point3,
17};
18use qualia_core_db::tensor::Tensor10D;
19use qualia_core_db::NQuin;
20use qualia_vision::detector::{
21 GridMultiObjectDetector, CLASS_MOSTLY_BLUE, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_RED,
22};
23use qualia_vision::generator::{compile_generation_receipt_quins, NativeImageGenerator};
24use qualia_vision::media_store::{MediaStore, RetentionClass};
25use qualia_vision::metrics::evaluate_synthetic;
26use qualia_vision::overlay::{box_css_percent, compose_rgb_overlay_rgba8, encode_bmp_rgba8};
27use qualia_vision::query_instances_in_region;
28use qualia_vision::semantic::{compile_observation_quins_full, media_digest, VisionQuin};
29use qualia_vision::spatial::{
30 cleanup_mesh_ir, detections_to_node_hints, image_to_heightfield_mesh, mesh_ir_to_export,
31 mesh_ir_to_obj, pack_geometry_export_for_10d, MeshCleanupOptions, MeshIR, NodeHint,
32};
33use qualia_vision::synthetic::{generate_scene_rgb8, sample_id, DatasetSplit, SyntheticSampleId};
34use qualia_vision::tracker::BoundedTracker;
35use qualia_vision::types::{Detection, ImageView, PixelFormat, VisualModel, MAX_DETECTIONS};
36use qualia_vision::weights::{ProductionVision, VisionBackendKind, VisionWeightBundle};
37use serde::Serialize;
38
39use crate::vision_ingest::{
40 append_human_attestation, append_native_observation_quins, human_correct_quin,
41 human_reject_quin, NativeDetection,
42};
43
44#[derive(Debug, Clone, Serialize)]
45pub struct OverlayBoxDto {
46 pub class_hash: String,
47 pub instance_hash: String,
48 pub score: f32,
49 pub track_id: u32,
50 pub frame_index: u32,
51 pub left: f32,
53 pub top: f32,
54 pub width: f32,
55 pub height: f32,
56 pub rejected: bool,
57}
58
59#[derive(Debug, Clone, Serialize)]
60pub struct VisionDemoResult {
61 pub width: u32,
62 pub height: u32,
63 pub seed: u64,
64 pub split: String,
65 pub model_hash: String,
66 pub media_hash: String,
67 pub detections: Vec<OverlayBoxDto>,
68 pub n_gt: usize,
69 pub n_pred: usize,
70 pub quins_written: usize,
71 pub shacl_ok: bool,
72 pub shacl_observations: u32,
73 pub shacl_human: u32,
74 pub overlay_data_url: String,
76 pub note: String,
77 pub backend: String,
79 pub is_reference_backend: bool,
80 pub synthetic_match_acc: Option<f32>,
81}
82
83#[derive(Debug, Clone, Serialize)]
84pub struct GenerateResult {
85 pub width: u32,
86 pub height: u32,
87 pub seed: u64,
88 pub steps: u32,
89 pub model_hash: String,
90 pub prompt_hash: String,
91 pub output_hash: String,
92 pub is_reference_generator: bool,
93 pub image_data_url: String,
94 pub note: String,
95}
96
97#[derive(Debug, Clone, Serialize)]
98pub struct ImageTo3dResult {
99 pub vertex_count: u32,
100 pub triangle_count: u32,
101 pub mesh_hash: String,
102 pub model_hash: String,
103 pub validation_ok: bool,
104 pub validation_status: String,
105 pub is_reference_recon: bool,
106 pub note: String,
107}
108
109#[derive(Debug, Clone, Serialize)]
111pub struct GsContinuumResult {
112 pub generate: GenerateResult,
113 pub mesh: ImageTo3dResult,
114 pub media_digest_hex: String,
115 pub media_stored: bool,
116 pub obj_bytes: usize,
117 pub container_10d_bytes: usize,
118 pub geometry_quins: usize,
119 pub generation_quins: usize,
120 pub obj_path: Option<String>,
121 pub container_10d_path: Option<String>,
122 pub note: String,
123}
124
125fn mesh_ir_to_core_mesh(ir: &MeshIR) -> Result<Mesh, String> {
127 let export = mesh_ir_to_export(ir).map_err(|e| format!("mesh_ir_to_export: {e:?}"))?;
128 let g = pack_geometry_export_for_10d(&export);
129 Ok(Mesh {
130 positions: g.positions,
131 triangles: g.triangles,
132 min: g.min,
133 max: g.max,
134 })
135}
136
137pub fn node_hint_to_tensor10d(h: &NodeHint) -> Tensor10D {
141 Tensor10D::parallel_context(
142 1.0, 0.0, 0.0, h.x,
146 h.y,
147 h.z,
148 h.t,
149 1.0, 0.0,
151 h.sigma.clamp(0.0, 1.0),
152 )
153}
154
155fn maybe_decimate_mesh(mesh: &mut Mesh, max_faces: usize) -> Result<Option<String>, String> {
157 if mesh.triangle_count() <= max_faces || max_faces == 0 {
158 return Ok(None);
159 }
160 let verts: Vec<Point3> = mesh
161 .positions
162 .iter()
163 .map(|p| Point3::new(p[0] as f64, p[1] as f64, p[2] as f64))
164 .collect();
165 let tris = mesh.triangles.clone();
166 let mut out_v = vec![Point3::new(0.0, 0.0, 0.0); verts.len()];
167 let mut out_t = vec![[0u32; 3]; tris.len()];
168 let report = decimate_qem(
169 &verts,
170 &tris,
171 DecimateOptions::to_faces(max_faces),
172 &mut out_v,
173 &mut out_t,
174 )
175 .map_err(|e| format!("decimate_qem: {e:?}"))?;
176 mesh.positions = out_v[..report.vertices]
177 .iter()
178 .map(|p| [p.x as f32, p.y as f32, p.z as f32])
179 .collect();
180 mesh.triangles = out_t[..report.faces].to_vec();
181 let mut min = [f32::INFINITY; 3];
183 let mut max = [f32::NEG_INFINITY; 3];
184 for p in &mesh.positions {
185 for k in 0..3 {
186 min[k] = min[k].min(p[k]);
187 max[k] = max[k].max(p[k]);
188 }
189 }
190 mesh.min = min;
191 mesh.max = max;
192 Ok(Some(format!(
193 "decimated faces {}→{} ({} collapses)",
194 tris.len(),
195 report.faces,
196 report.collapses
197 )))
198}
199
200fn det_to_dto(d: &Detection, rejected: bool) -> OverlayBoxDto {
201 let (left, top, width, height) = box_css_percent(d);
202 OverlayBoxDto {
203 class_hash: format!("0x{:016x}", d.class_hash),
204 instance_hash: format!("0x{:016x}", d.instance_hash),
205 score: d.score_f32(),
206 track_id: d.track_id,
207 frame_index: d.frame_index,
208 left,
209 top,
210 width,
211 height,
212 rejected,
213 }
214}
215
216fn vision_quin_to_nquin(v: &VisionQuin) -> NQuin {
217 NQuin {
218 subject: v.subject,
219 predicate: v.predicate,
220 object: v.object,
221 context: v.context,
222 metadata: v.metadata,
223 parity: v.parity,
224 }
225}
226
227pub fn run_synthetic_demo(
229 split: DatasetSplit,
230 index: u32,
231 width: u32,
232 height: u32,
233) -> Result<VisionDemoResult, String> {
234 run_synthetic_demo_with_backend(split, index, width, height, "reference")
235}
236
237pub fn run_synthetic_demo_with_backend(
238 split: DatasetSplit,
239 index: u32,
240 width: u32,
241 height: u32,
242 backend: &str,
243) -> Result<VisionDemoResult, String> {
244 let sample = sample_id(split, index, width, height);
245 run_sample_demo_with_backend(&sample, backend)
246}
247
248pub fn run_sample_demo(sample: &SyntheticSampleId) -> Result<VisionDemoResult, String> {
249 run_sample_demo_with_backend(sample, "reference")
250}
251
252pub fn run_sample_demo_with_backend(
253 sample: &SyntheticSampleId,
254 backend: &str,
255) -> Result<VisionDemoResult, String> {
256 let w = sample.width;
257 let h = sample.height;
258 let px = (w as usize) * (h as usize);
259 let mut rgb = vec![0u8; px * 3];
260 let mut gt = [Detection::empty(); MAX_DETECTIONS];
261 let n_gt = generate_scene_rgb8(sample, &mut rgb, &mut gt).map_err(|e| format!("{e:?}"))?;
262
263 let img = ImageView {
264 bytes: &rgb,
265 width: w,
266 height: h,
267 row_stride: w * 3,
268 format: PixelFormat::Rgb8,
269 };
270 let mut preds = [Detection::empty(); MAX_DETECTIONS];
271 let mut emb = [0.0f32; 32];
272 let mut ws = [0u8; MAX_DETECTIONS];
273 let use_prod = backend.eq_ignore_ascii_case("production")
274 || backend.eq_ignore_ascii_case("production_weights")
275 || backend.eq_ignore_ascii_case("weights");
276
277 let (n_pred, model_hash, backend_kind, is_ref, synth_acc) = if use_prod {
278 let classes = [CLASS_MOSTLY_RED, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_BLUE];
279 let bundle = VisionWeightBundle::from_seed(0x01D1_FACE_u64, 16, &classes);
280 let mh = bundle.model_hash();
281 let mut prod = ProductionVision::new(bundle);
282 let counts = prod
283 .infer(img, &mut preds, &mut emb, &mut ws)
284 .map_err(|e| format!("{e:?}"))?;
285 let mut m2 =
286 ProductionVision::new(VisionWeightBundle::from_seed(0x01D1_FACE_u64, 16, &classes));
287 let metrics =
288 evaluate_synthetic(&mut m2, VisionBackendKind::ProductionWeights, mh, 4, 32, 24);
289 (
290 counts.detections,
291 mh,
292 "production_weights",
293 false,
294 Some(metrics.mean_match_acc),
295 )
296 } else {
297 let det = GridMultiObjectDetector::new(4, 3);
298 let n_pred = det
299 .detect(img, 0, &mut preds, &mut ws)
300 .map_err(|e| format!("{e:?}"))?;
301 let mh = det.model_hash();
302 let mut det2 = GridMultiObjectDetector::new(4, 3);
303 let metrics = evaluate_synthetic(&mut det2, VisionBackendKind::Reference, mh, 4, 32, 24);
304 (n_pred, mh, "reference", true, Some(metrics.mean_match_acc))
305 };
306
307 let mut tracker = BoundedTracker::new();
308 tracker.update(0, &mut preds, n_pred);
309
310 let digest = media_digest(&rgb);
311 let mut vquins = [VisionQuin::with_parity(0, 0, 0, 0, 0); 256];
312 let n_q = compile_observation_quins_full(digest, &preds[..n_pred], model_hash, &mut vquins);
313
314 let mut nquins = Vec::with_capacity(n_q);
315 for q in vquins.iter().take(n_q) {
316 nquins.push(vision_quin_to_nquin(q));
317 }
318 let report = validate_vision_observation_graph(&nquins);
319
320 let mut rgba = vec![0u8; px * 4];
321 compose_rgb_overlay_rgba8(w, h, &rgb, &preds, n_pred, [0, 255, 180, 255], 2, &mut rgba)
322 .map_err(|e| format!("{e:?}"))?;
323 let mut bmp = vec![0u8; 54 + px * 4];
324 let bmp_n = encode_bmp_rgba8(w, h, &rgba, &mut bmp).map_err(|e| format!("{e:?}"))?;
325 let b64 = B64.encode(&bmp[..bmp_n]);
326
327 let split_s = match sample.split {
328 DatasetSplit::Train => "train",
329 DatasetSplit::Test => "test",
330 };
331
332 let note = if is_ref {
333 "Backend=reference (grid). Epistemic only — not ground truth. H1 real eval not run."
334 } else {
335 "Backend=production_weights (QVWT seed fixture). Synthetic metrics only until H1 corpus."
336 };
337
338 Ok(VisionDemoResult {
339 width: w,
340 height: h,
341 seed: sample.seed,
342 split: split_s.to_string(),
343 model_hash: format!("0x{:016x}", model_hash),
344 media_hash: format!("0x{:016x}", digest.hash),
345 detections: preds[..n_pred]
346 .iter()
347 .map(|d| det_to_dto(d, false))
348 .collect(),
349 n_gt,
350 n_pred,
351 quins_written: n_q,
352 shacl_ok: report.ok,
353 shacl_observations: report.observation_count,
354 shacl_human: report.human_attestation_count,
355 overlay_data_url: format!("data:image/bmp;base64,{b64}"),
356 note: note.into(),
357 backend: backend_kind.into(),
358 is_reference_backend: is_ref,
359 synthetic_match_acc: synth_acc,
360 })
361}
362
363pub fn generate_image(
365 prompt: &str,
366 seed: u64,
367 steps: u32,
368 width: u32,
369 height: u32,
370) -> Result<GenerateResult, String> {
371 let w = width.clamp(8, 256);
372 let h = height.clamp(8, 256);
373 let mut rgb = vec![0u8; (w * h * 3) as usize];
374 let g = NativeImageGenerator::new();
375 let rec = g
376 .generate_rgb8(prompt, seed, steps, w, h, &mut rgb)
377 .map_err(|e| format!("{e:?}"))?;
378 let mut rgba = vec![0u8; (w * h * 4) as usize];
379 for i in 0..(w * h) as usize {
380 rgba[i * 4] = rgb[i * 3];
381 rgba[i * 4 + 1] = rgb[i * 3 + 1];
382 rgba[i * 4 + 2] = rgb[i * 3 + 2];
383 rgba[i * 4 + 3] = 255;
384 }
385 let mut bmp = vec![0u8; 54 + rgba.len()];
386 let n = encode_bmp_rgba8(w, h, &rgba, &mut bmp).map_err(|e| format!("{e:?}"))?;
387 Ok(GenerateResult {
388 width: w,
389 height: h,
390 seed,
391 steps: rec.steps,
392 model_hash: format!("0x{:016x}", rec.model_hash),
393 prompt_hash: format!("0x{:016x}", rec.prompt_hash),
394 output_hash: format!("0x{:016x}", rec.output_digest.hash),
395 is_reference_generator: rec.is_reference_generator,
396 image_data_url: format!("data:image/bmp;base64,{}", B64.encode(&bmp[..n])),
397 note: "Native reference generator (seeded). Not a foundation DiT; swap weights under G0 licence.".into(),
398 })
399}
400
401pub fn image_to_3d_from_rgb(
403 width: u32,
404 height: u32,
405 rgb: &[u8],
406 grid: u32,
407) -> Result<ImageTo3dResult, String> {
408 let img = ImageView {
409 bytes: rgb,
410 width,
411 height,
412 row_stride: width * 3,
413 format: PixelFormat::Rgb8,
414 };
415 let (mesh, rec, rep) = image_to_heightfield_mesh(img, grid).map_err(|e| format!("{e:?}"))?;
416 let status = format!("{:?}", rep.status);
417 Ok(ImageTo3dResult {
418 vertex_count: mesh.vertex_count() as u32,
419 triangle_count: mesh.triangle_count() as u32,
420 mesh_hash: format!("0x{:016x}", mesh.content_hash),
421 model_hash: format!("0x{:016x}", rec.model_hash),
422 validation_ok: rep.ok(),
423 validation_status: status,
424 is_reference_recon: rec.is_reference_recon,
425 note: "Heightfield recon is epistemic proposal; validated before any Q42 geometry commit."
426 .into(),
427 })
428}
429
430pub fn generate_and_reconstruct(
432 prompt: &str,
433 seed: u64,
434) -> Result<(GenerateResult, ImageTo3dResult), String> {
435 let gen = generate_image(prompt, seed, 4, 32, 32)?;
436 let mut rgb = vec![0u8; 32 * 32 * 3];
437 let g = NativeImageGenerator::new();
438 g.generate_rgb8(prompt, seed, 4, 32, 32, &mut rgb)
439 .map_err(|e| format!("{e:?}"))?;
440 let mesh = image_to_3d_from_rgb(32, 32, &rgb, 8)?;
441 Ok((gen, mesh))
442}
443
444pub fn run_gs_continuum(
448 storage_root: &std::path::Path,
449 prompt: &str,
450 seed: u64,
451 steps: u32,
452 width: u32,
453 height: u32,
454 recon_grid: u32,
455 media_time_ms: u64,
456) -> Result<GsContinuumResult, String> {
457 let w = width.clamp(8, 128);
458 let h = height.clamp(8, 128);
459 let mut rgb = vec![0u8; (w * h * 3) as usize];
460 let gen = NativeImageGenerator::new();
461 let rec = gen
462 .generate_rgb8_cancellable(prompt, seed, steps, w, h, &mut rgb, None, media_time_ms)
463 .map_err(|e| format!("{e:?}"))?;
464
465 let media_dir = storage_root.join("vision_media");
467 let store = MediaStore::open(&media_dir).map_err(|e| e)?;
468 let now = std::time::SystemTime::now()
469 .duration_since(std::time::UNIX_EPOCH)
470 .map(|d| d.as_secs())
471 .unwrap_or(0);
472 let record = store
473 .import_bytes(
474 &rgb,
475 "application/octet-stream",
476 w,
477 h,
478 RetentionClass::Restricted,
479 now,
480 )
481 .map_err(|e| e)?;
482
483 let mut gen_quins = [VisionQuin::with_parity(0, 0, 0, 0, 0); 8];
484 let n_gen_q = compile_generation_receipt_quins(&rec, &mut gen_quins);
485
486 let mut rgba = vec![0u8; (w * h * 4) as usize];
488 for i in 0..(w * h) as usize {
489 rgba[i * 4] = rgb[i * 3];
490 rgba[i * 4 + 1] = rgb[i * 3 + 1];
491 rgba[i * 4 + 2] = rgb[i * 3 + 2];
492 rgba[i * 4 + 3] = 255;
493 }
494 let mut bmp = vec![0u8; 54 + rgba.len()];
495 let bmp_n = encode_bmp_rgba8(w, h, &rgba, &mut bmp).map_err(|e| format!("{e:?}"))?;
496 let generate = GenerateResult {
497 width: w,
498 height: h,
499 seed,
500 steps: rec.steps,
501 model_hash: format!("0x{:016x}", rec.model_hash),
502 prompt_hash: format!("0x{:016x}", rec.prompt_hash),
503 output_hash: format!("0x{:016x}", rec.output_digest.hash),
504 is_reference_generator: rec.is_reference_generator,
505 image_data_url: format!("data:image/bmp;base64,{}", B64.encode(&bmp[..bmp_n])),
506 note: format!(
507 "Stored media digest {}; media_time_ms={media_time_ms} for cross-modal timeline.",
508 record.digest_hex
509 ),
510 };
511
512 let img = ImageView {
513 bytes: &rgb,
514 width: w,
515 height: h,
516 row_stride: w * 3,
517 format: PixelFormat::Rgb8,
518 };
519 let (mut mesh_ir, recon_rec, rep) =
520 image_to_heightfield_mesh(img, recon_grid).map_err(|e| format!("{e:?}"))?;
521 if !rep.ok() {
522 return Err(format!("mesh validation failed: {:?}", rep.status));
523 }
524
525 let quality = cleanup_mesh_ir(
527 &mut mesh_ir,
528 MeshCleanupOptions {
529 weld_epsilon: 1e-6,
530 min_area: 0.0,
531 },
532 )
533 .map_err(|e| format!("mesh quality cleanup: {e:?}"))?;
534
535 let mut obj_buf = vec![0u8; mesh_ir.positions.len() * 64 + mesh_ir.indices.len() * 24 + 256];
536 let obj_n = mesh_ir_to_obj(&mesh_ir, &mut obj_buf).map_err(|e| format!("{e:?}"))?;
537 obj_buf.truncate(obj_n);
538
539 let mut core_mesh = mesh_ir_to_core_mesh(&mesh_ir)?;
540 let decimate_note = maybe_decimate_mesh(&mut core_mesh, 2048)?;
542
543 let centre = Tensor10D::parallel_context(
546 1.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.35, );
548 let mut version_hash = [0u8; 32];
550 let mh = recon_rec.model_hash.to_le_bytes();
551 version_hash[..8].copy_from_slice(&mh);
552 let dig = record.digest_u64.to_le_bytes();
553 version_hash[8..16].copy_from_slice(&dig);
554 let source_tag = format!(
556 "qualia-vision-recon;media={};model=0x{:016x}",
557 record.digest_hex, recon_rec.model_hash
558 );
559 let provenance = ProvenanceSidecar::new(
560 source_tag.into_bytes(),
561 "application/x-qualia-vision-recon",
562 "PermissiveReady-local", )
564 .with_metadata(
565 format!(
566 r#"{{"media_digest":"{}","model_hash":"0x{:016x}"}}"#,
567 record.digest_hex, recon_rec.model_hash
568 )
569 .into_bytes(),
570 0,
571 version_hash,
572 );
573 let container = compile_mesh_to_10d_vision_with_provenance(&core_mesh, &[centre], &provenance)
575 .map_err(|e| e.to_string())?;
576 let compiled_digest = {
578 let mut h: u32 = 0;
579 for chunk in container.chunks(4) {
580 let mut b = [0u8; 4];
581 b[..chunk.len()].copy_from_slice(chunk);
582 h ^= u32::from_le_bytes(b);
583 }
584 h
585 };
586 let source_digest = (record.digest_u64 & 0xFFFF_FFFF) as u32;
587 let asset_uri = format!("urn:qualia:vision:recon:{}", record.digest_hex);
588 let (geo_quins, _lex) = mesh_to_nquins_with_digests(
589 &core_mesh,
590 &asset_uri,
591 "obj",
592 source_digest,
593 compiled_digest,
594 );
595
596 let out_dir = storage_root
597 .join("vision_geometry")
598 .join(&record.digest_hex);
599 std::fs::create_dir_all(&out_dir).map_err(|e| e.to_string())?;
600 let obj_path = out_dir.join("recon.obj");
601 let c10_path = out_dir.join("recon.10d");
602 std::fs::write(&obj_path, &obj_buf).map_err(|e| e.to_string())?;
603 std::fs::write(&c10_path, &container).map_err(|e| e.to_string())?;
604
605 let mut nquin_buf = Vec::with_capacity(n_gen_q + geo_quins.len());
607 for q in gen_quins.iter().take(n_gen_q) {
608 nquin_buf.push(NQuin {
609 subject: q.subject,
610 predicate: q.predicate,
611 object: q.object,
612 context: q.context,
613 metadata: q.metadata,
614 parity: q.parity,
615 });
616 }
617 nquin_buf.extend(geo_quins.iter().cloned());
618 let wal_path = storage_root.join("models").join("vision_native.wal");
619 if let Some(parent) = wal_path.parent() {
620 let _ = std::fs::create_dir_all(parent);
621 }
622 if let Ok(mut wal) = qualia_core_db::wal::WriteAheadLog::open(&wal_path) {
623 for q in &nquin_buf {
624 let _ = wal.append_mutation(q);
625 }
626 }
627
628 let mesh = ImageTo3dResult {
629 vertex_count: mesh_ir.vertex_count() as u32,
630 triangle_count: mesh_ir.triangle_count() as u32,
631 mesh_hash: format!("0x{:016x}", mesh_ir.content_hash),
632 model_hash: format!("0x{:016x}", recon_rec.model_hash),
633 validation_ok: true,
634 validation_status: format!("{:?}", rep.status),
635 is_reference_recon: recon_rec.is_reference_recon,
636 note: format!(
637 "Validated MeshIR → cleanup(deg={},weld={}) → OBJ + sealed .10d with Tensor10DNodes; digests on quins.",
638 quality.degenerates_removed, quality.vertices_welded
639 ),
640 };
641
642 let mut note = String::from(
643 "G→S continuum closed: store + validate + cleanup + compile(mesh+nodes). Ready for 10d browse.",
644 );
645 if let Some(d) = decimate_note {
646 note.push(' ');
647 note.push_str(&d);
648 }
649
650 Ok(GsContinuumResult {
651 generate,
652 mesh,
653 media_digest_hex: record.digest_hex,
654 media_stored: true,
655 obj_bytes: obj_n,
656 container_10d_bytes: container.len(),
657 geometry_quins: nquin_buf.len().saturating_sub(n_gen_q),
658 generation_quins: n_gen_q,
659 obj_path: Some(obj_path.display().to_string()),
660 container_10d_path: Some(c10_path.display().to_string()),
661 note,
662 })
663}
664
665pub fn seal_vision_mesh_with_detections(
667 mesh_ir: &MeshIR,
668 dets: &[Detection],
669) -> Result<Vec<u8>, String> {
670 let mut cleaned = mesh_ir.clone();
671 let _ = cleanup_mesh_ir(
672 &mut cleaned,
673 MeshCleanupOptions {
674 weld_epsilon: 1e-6,
675 min_area: 0.0,
676 },
677 );
678 let mut core = mesh_ir_to_core_mesh(&cleaned)?;
679 let _ = maybe_decimate_mesh(&mut core, 4096)?;
680 let mut hints = vec![
681 NodeHint {
682 x: 0.0,
683 y: 0.0,
684 z: 0.0,
685 t: 0.0,
686 sigma: 0.0,
687 };
688 dets.len().min(256)
689 ];
690 let n = detections_to_node_hints(dets, &mut hints);
691 let nodes: Vec<Tensor10D> = hints[..n].iter().map(node_hint_to_tensor10d).collect();
692 compile_mesh_to_10d_vision(&core, &nodes).map_err(|e| e.to_string())
693}
694
695pub fn ingest_demo_to_wal(
697 storage_root: &std::path::Path,
698 demo: &VisionDemoResult,
699) -> Result<VisionShaclReport, String> {
700 let media_hash = u64::from_str_radix(demo.media_hash.trim_start_matches("0x"), 16)
701 .map_err(|e| e.to_string())?;
702 let model_hash = u64::from_str_radix(demo.model_hash.trim_start_matches("0x"), 16)
703 .map_err(|e| e.to_string())?;
704 let mut natives = Vec::new();
705 for d in &demo.detections {
706 let instance_hash = u64::from_str_radix(d.instance_hash.trim_start_matches("0x"), 16)
707 .map_err(|e| e.to_string())?;
708 let class_hash = u64::from_str_radix(d.class_hash.trim_start_matches("0x"), 16)
709 .map_err(|e| e.to_string())?;
710 let (x0, y0, x1, y1) = css_to_u16(d.left, d.top, d.width, d.height);
711 natives.push(NativeDetection {
712 class_hash,
713 instance_hash,
714 score_u16: (d.score.clamp(0.0, 1.0) * 65535.0) as u16,
715 x_min_u16: x0,
716 y_min_u16: y0,
717 x_max_u16: x1,
718 y_max_u16: y1,
719 frame_index: d.frame_index,
720 track_id: d.track_id,
721 flags: 0,
722 });
723 }
724 let n = append_native_observation_quins(
725 storage_root,
726 media_hash,
727 (demo.width as u64) * (demo.height as u64) * 3,
728 model_hash,
729 &natives,
730 )
731 .map_err(|e| e.to_string())?;
732 let mut buf = [NQuin {
734 subject: 0,
735 predicate: 0,
736 object: 0,
737 context: 0,
738 metadata: 0,
739 parity: 0,
740 }; 256];
741 let written = crate::vision_ingest::compile_native_observation_quins(
742 media_hash,
743 (demo.width as u64) * (demo.height as u64) * 3,
744 model_hash,
745 &natives,
746 &mut buf,
747 )
748 .map_err(|e| e.to_string())?;
749 let _ = n;
750 Ok(validate_vision_observation_graph(&buf[..written]))
751}
752
753fn css_to_u16(left: f32, top: f32, width: f32, height: f32) -> (u16, u16, u16, u16) {
754 let x0 = ((left / 100.0) * 65535.0).clamp(0.0, 65535.0) as u16;
755 let y0 = ((top / 100.0) * 65535.0).clamp(0.0, 65535.0) as u16;
756 let x1 = (((left + width) / 100.0) * 65535.0).clamp(0.0, 65535.0) as u16;
757 let y1 = (((top + height) / 100.0) * 65535.0).clamp(0.0, 65535.0) as u16;
758 (x0, y0, x1, y1)
759}
760
761pub fn reject_instance(
763 storage_root: &std::path::Path,
764 human_did_hash: u64,
765 instance_hash: u64,
766 reason_hash: u64,
767) -> Result<(), String> {
768 let q = human_reject_quin(human_did_hash, instance_hash, reason_hash);
769 append_human_attestation(storage_root, &q).map_err(|e| e.to_string())
770}
771
772pub fn correct_instance(
773 storage_root: &std::path::Path,
774 human_did_hash: u64,
775 instance_hash: u64,
776 new_class_hash: u64,
777) -> Result<(), String> {
778 let q = human_correct_quin(human_did_hash, instance_hash, new_class_hash);
779 append_human_attestation(storage_root, &q).map_err(|e| e.to_string())
780}
781
782pub fn demo_train(index: u32) -> Result<VisionDemoResult, String> {
784 run_synthetic_demo(DatasetSplit::Train, index, 96, 64)
785}
786
787pub fn demo_test(index: u32) -> Result<VisionDemoResult, String> {
788 run_synthetic_demo(DatasetSplit::Test, index, 96, 64)
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 #[test]
796 fn synthetic_demo_produces_overlay_and_shacl() {
797 let r = demo_test(0).expect("demo");
798 assert!(r.n_pred >= 1 || r.n_gt >= 1);
799 assert!(r.overlay_data_url.starts_with("data:image/bmp;base64,"));
800 assert!(r.shacl_ok, "shacl should pass on full compile");
801 assert!(r.quins_written >= 1);
802 assert_eq!(r.backend, "reference");
803 }
804
805 #[test]
806 fn production_backend_labelled() {
807 let r = run_synthetic_demo_with_backend(DatasetSplit::Test, 0, 48, 32, "production")
808 .expect("prod");
809 assert_eq!(r.backend, "production_weights");
810 assert!(!r.is_reference_backend);
811 assert!(r.synthetic_match_acc.is_some());
812 }
813
814 #[test]
815 fn generate_and_recon_smoke() {
816 let (g, m) = generate_and_reconstruct("test field", 7).expect("g+s");
817 assert!(g.is_reference_generator);
818 assert!(m.validation_ok);
819 assert!(m.triangle_count > 0);
820 }
821
822 #[test]
823 fn gs_continuum_writes_obj_and_10d() {
824 let dir = std::env::temp_dir().join(format!(
825 "qv-gs-{}",
826 std::time::SystemTime::now()
827 .duration_since(std::time::UNIX_EPOCH)
828 .unwrap()
829 .as_nanos()
830 ));
831 let _ = std::fs::remove_dir_all(&dir);
832 std::fs::create_dir_all(&dir).unwrap();
833 let r = run_gs_continuum(&dir, "hills", 11, 3, 24, 24, 6, 1000).expect("continuum");
834 assert!(r.media_stored);
835 assert!(r.obj_bytes > 0);
836 assert!(r.container_10d_bytes > 64);
837 assert!(r.geometry_quins >= 1);
838 assert!(r.generation_quins == 3);
839 let obj = r.obj_path.as_ref().unwrap();
840 assert!(std::path::Path::new(obj).is_file());
841 let c10 = r.container_10d_path.as_ref().unwrap();
842 assert!(std::path::Path::new(c10).is_file());
843 let _ = std::fs::remove_dir_all(&dir);
844 }
845
846 #[test]
847 fn section15_smoke_passes() {
848 let s = section15_smoke().expect("§15");
849 assert!(s.contains("OK"));
850 }
851
852 #[test]
853 fn rgb_import_detects() {
854 let dir = std::env::temp_dir().join(format!(
855 "qv-imp-{}",
856 std::time::SystemTime::now()
857 .duration_since(std::time::UNIX_EPOCH)
858 .unwrap()
859 .as_nanos()
860 ));
861 let _ = std::fs::create_dir_all(&dir);
862 let mut rgb = vec![0u8; 16 * 16 * 3];
863 for p in rgb.chunks_mut(3) {
864 p[0] = 220;
865 p[1] = 20;
866 p[2] = 20;
867 }
868 let r = detect_from_rgb8(&dir, &rgb, 16, 16, "reference", true).unwrap();
869 assert!(r.n_pred >= 1);
870 assert!(r.shacl_ok);
871 let _ = std::fs::remove_dir_all(&dir);
872 }
873}
874
875pub fn detect_from_rgb8(
877 storage_root: &std::path::Path,
878 rgb: &[u8],
879 width: u32,
880 height: u32,
881 backend: &str,
882 persist: bool,
883) -> Result<VisionDemoResult, String> {
884 if rgb.len() < (width as usize) * (height as usize) * 3 || width == 0 || height == 0 {
885 return Err("bad rgb geometry".into());
886 }
887 let img = ImageView {
888 bytes: rgb,
889 width,
890 height,
891 row_stride: width * 3,
892 format: PixelFormat::Rgb8,
893 };
894 let mut preds = [Detection::empty(); MAX_DETECTIONS];
895 let mut emb = [0.0f32; 32];
896 let mut ws = [0u8; MAX_DETECTIONS];
897 let use_prod = backend.eq_ignore_ascii_case("production")
898 || backend.eq_ignore_ascii_case("production_weights");
899 let (n_pred, model_hash, backend_kind, is_ref) = if use_prod {
900 let classes = [CLASS_MOSTLY_RED, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_BLUE];
901 let bundle = VisionWeightBundle::from_seed(0x01D1_FACE_u64, 16, &classes);
902 let mh = bundle.model_hash();
903 let mut prod = ProductionVision::new(bundle);
904 let counts = prod
905 .infer(img, &mut preds, &mut emb, &mut ws)
906 .map_err(|e| format!("{e:?}"))?;
907 (counts.detections, mh, "production_weights", false)
908 } else {
909 let det = GridMultiObjectDetector::new(4, 3);
910 let n = det
911 .detect(img, 0, &mut preds, &mut ws)
912 .map_err(|e| format!("{e:?}"))?;
913 (n, det.model_hash(), "reference", true)
914 };
915 let mut tracker = BoundedTracker::new();
916 tracker.update(0, &mut preds, n_pred);
917 let digest = media_digest(rgb);
918 if persist {
919 let store = MediaStore::open(storage_root.join("vision_media")).map_err(|e| e)?;
920 let now = std::time::SystemTime::now()
921 .duration_since(std::time::UNIX_EPOCH)
922 .map(|d| d.as_secs())
923 .unwrap_or(0);
924 let _ = store.import_bytes(
925 rgb,
926 "image/x-rgb8",
927 width,
928 height,
929 RetentionClass::Restricted,
930 now,
931 );
932 }
933 let mut vquins = [VisionQuin::with_parity(0, 0, 0, 0, 0); 256];
934 let n_q = compile_observation_quins_full(digest, &preds[..n_pred], model_hash, &mut vquins);
935 let mut nq = Vec::new();
936 for q in vquins.iter().take(n_q) {
937 nq.push(vision_quin_to_nquin(q));
938 }
939 let report = validate_vision_observation_graph(&nq);
940 let mut rgba = vec![0u8; (width * height * 4) as usize];
941 compose_rgb_overlay_rgba8(
942 width,
943 height,
944 rgb,
945 &preds,
946 n_pred,
947 [0, 255, 180, 255],
948 2,
949 &mut rgba,
950 )
951 .map_err(|e| format!("{e:?}"))?;
952 let mut bmp = vec![0u8; 54 + rgba.len()];
953 let bmp_n = encode_bmp_rgba8(width, height, &rgba, &mut bmp).map_err(|e| format!("{e:?}"))?;
954 Ok(VisionDemoResult {
955 width,
956 height,
957 seed: 0,
958 split: "import".into(),
959 model_hash: format!("0x{:016x}", model_hash),
960 media_hash: format!("0x{:016x}", digest.hash),
961 detections: preds[..n_pred]
962 .iter()
963 .map(|d| det_to_dto(d, false))
964 .collect(),
965 n_gt: 0,
966 n_pred,
967 quins_written: n_q,
968 shacl_ok: report.ok,
969 shacl_observations: report.observation_count,
970 shacl_human: report.human_attestation_count,
971 overlay_data_url: format!("data:image/bmp;base64,{}", B64.encode(&bmp[..bmp_n])),
972 note: format!("RGB import path. Backend={backend_kind}. Epistemic only."),
973 backend: backend_kind.into(),
974 is_reference_backend: is_ref,
975 synthetic_match_acc: None,
976 })
977}
978
979pub fn detect_from_image_file(
981 storage_root: &std::path::Path,
982 path: &std::path::Path,
983 backend: &str,
984) -> Result<VisionDemoResult, String> {
985 let img = image::open(path).map_err(|e| e.to_string())?;
986 let rgb = img.to_rgb8();
987 let (w, h) = rgb.dimensions();
988 detect_from_rgb8(storage_root, rgb.as_raw(), w, h, backend, true)
989}
990
991pub fn query_vision_region(quins: &[VisionQuin], x0: u16, y0: u16, x1: u16, y1: u16) -> Vec<u64> {
993 let mut out = [0u64; 64];
994 let n = query_instances_in_region(quins, x0, y0, x1, y1, &mut out);
995 out[..n].to_vec()
996}
997
998pub fn section15_smoke() -> Result<String, String> {
1000 let r = demo_test(0)?;
1001 if !r.shacl_ok {
1002 return Err("SHACL failed".into());
1003 }
1004 if r.n_pred == 0 && r.n_gt == 0 {
1005 return Err("no detections".into());
1006 }
1007 if !r.overlay_data_url.starts_with("data:image/bmp") {
1008 return Err("no overlay".into());
1009 }
1010 Ok(format!(
1011 "section15_smoke OK backend={} dets={} quins={}",
1012 r.backend, r.n_pred, r.quins_written
1013 ))
1014}
1015
1016pub fn twin_elasticity_demo() -> Result<serde_json::Value, String> {
1018 use qualia_vision::spatial::{
1019 closed_form_bar_stretch, promote_elasticity_preview, refuse_fea_unless_eligible,
1020 run_elasticity_preview_if_eligible, BarStretchInput, MeshIR,
1021 };
1022 let mut m = MeshIR::empty();
1023 m.positions = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1024 m.indices = vec![0, 1, 2];
1025 m.recompute_bounds_and_hash();
1026 let viz = qualia_vision::spatial::assess_twin_eligibility(&m);
1027 let promoted = promote_elasticity_preview(&m);
1028 let refuse_viz = refuse_fea_unless_eligible(viz).is_err();
1029 let r = run_elasticity_preview_if_eligible(
1030 promoted,
1031 BarStretchInput {
1032 force_n: 1000.0,
1033 length_m: 1.0,
1034 area_m2: 0.01,
1035 youngs_pa: 2.0e11,
1036 },
1037 )
1038 .map_err(|e| e.to_string())?;
1039 let _ = closed_form_bar_stretch(BarStretchInput {
1040 force_n: 1.0,
1041 length_m: 1.0,
1042 area_m2: 1.0,
1043 youngs_pa: 1.0e9,
1044 });
1045 Ok(serde_json::json!({
1046 "viz_only_refuses_fea": refuse_viz,
1047 "promoted_domain": format!("{:?}", promoted.domain),
1048 "displacement_m": r.displacement_m,
1049 "assurance": r.assurance_note,
1050 "note": "A1 closed-form bar stretch only — not mesh FEA / not A4."
1051 }))
1052}
1053
1054pub fn ensure_vision_weights(storage_root: &std::path::Path) -> Result<String, String> {
1056 use qualia_vision::detector::{CLASS_MOSTLY_BLUE, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_RED};
1057 let path = storage_root.join("models").join("vision_seed.qvwt");
1058 let classes = [CLASS_MOSTLY_RED, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_BLUE];
1059 if path.is_file() {
1060 let b = VisionWeightBundle::load_path(&path, &classes)?;
1061 return Ok(format!(
1062 "loaded QVWT hash=0x{:016x} path={}",
1063 b.content_hash,
1064 path.display()
1065 ));
1066 }
1067 let b = VisionWeightBundle::from_seed(0x01D1_FACE_u64, 16, &classes);
1068 b.save_path(&path)?;
1069 Ok(format!(
1070 "wrote QVWT seed hash=0x{:016x} path={}",
1071 b.content_hash,
1072 path.display()
1073 ))
1074}
1075
1076pub fn detect_with_disk_weights(
1078 storage_root: &std::path::Path,
1079 rgb: &[u8],
1080 width: u32,
1081 height: u32,
1082) -> Result<VisionDemoResult, String> {
1083 use qualia_vision::detector::{CLASS_MOSTLY_BLUE, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_RED};
1084 let path = storage_root.join("models").join("vision_seed.qvwt");
1085 let classes = [CLASS_MOSTLY_RED, CLASS_MOSTLY_GREEN, CLASS_MOSTLY_BLUE];
1086 let bundle = if path.is_file() {
1087 VisionWeightBundle::load_path(&path, &classes)?
1088 } else {
1089 let b = VisionWeightBundle::from_seed(0x01D1_FACE_u64, 16, &classes);
1090 b.save_path(&path)?;
1091 b
1092 };
1093 let img = ImageView {
1094 bytes: rgb,
1095 width,
1096 height,
1097 row_stride: width * 3,
1098 format: PixelFormat::Rgb8,
1099 };
1100 let model_hash = bundle.content_hash;
1101 let mut prod = ProductionVision::new(bundle);
1102 let mut preds = [Detection::empty(); MAX_DETECTIONS];
1103 let mut emb = [0.0f32; 32];
1104 let mut ws = [0u8; MAX_DETECTIONS];
1105 let counts = prod
1106 .infer(img, &mut preds, &mut emb, &mut ws)
1107 .map_err(|e| format!("{e:?}"))?;
1108 let digest = media_digest(rgb);
1109 let mut tracker = BoundedTracker::new();
1110 tracker.update(0, &mut preds, counts.detections);
1111 let mut vquins = [VisionQuin::with_parity(0, 0, 0, 0, 0); 256];
1112 let n_q = compile_observation_quins_full(
1113 digest,
1114 &preds[..counts.detections],
1115 model_hash,
1116 &mut vquins,
1117 );
1118 let mut nq = Vec::new();
1119 for q in vquins.iter().take(n_q) {
1120 nq.push(vision_quin_to_nquin(q));
1121 }
1122 let report = validate_vision_observation_graph(&nq);
1123 let mut rgba = vec![0u8; (width * height * 4) as usize];
1124 compose_rgb_overlay_rgba8(
1125 width,
1126 height,
1127 rgb,
1128 &preds,
1129 counts.detections,
1130 [0, 255, 180, 255],
1131 2,
1132 &mut rgba,
1133 )
1134 .map_err(|e| format!("{e:?}"))?;
1135 let mut bmp = vec![0u8; 54 + rgba.len()];
1136 let bmp_n = encode_bmp_rgba8(width, height, &rgba, &mut bmp).map_err(|e| format!("{e:?}"))?;
1137 Ok(VisionDemoResult {
1138 width,
1139 height,
1140 seed: 0,
1141 split: "disk-qvwt".into(),
1142 model_hash: format!("0x{:016x}", model_hash),
1143 media_hash: format!("0x{:016x}", digest.hash),
1144 detections: preds[..counts.detections]
1145 .iter()
1146 .map(|d| det_to_dto(d, false))
1147 .collect(),
1148 n_gt: 0,
1149 n_pred: counts.detections,
1150 quins_written: n_q,
1151 shacl_ok: report.ok,
1152 shacl_observations: report.observation_count,
1153 shacl_human: report.human_attestation_count,
1154 overlay_data_url: format!("data:image/bmp;base64,{}", B64.encode(&bmp[..bmp_n])),
1155 note: "QVWT loaded from models/vision_seed.qvwt (seed weights, not foundation).".into(),
1156 backend: "production_weights".into(),
1157 is_reference_backend: false,
1158 synthetic_match_acc: None,
1159 })
1160}
1161
1162#[derive(Debug, Clone, Serialize)]
1164pub struct BiosensePulseDemo {
1165 pub bpm: f32,
1166 pub confidence: f32,
1167 pub snr: f32,
1168 pub abstained: bool,
1169 pub used_evm: bool,
1170 pub reason: String,
1171}
1172
1173pub fn biosense_self_monitor_pulse_demo(use_evm: bool) -> Result<BiosensePulseDemo, String> {
1175 use qualia_vision::{self_monitor_pulse_evm, synthetic_pulse_sequence, BiosenseConsent};
1176 let seq = synthetic_pulse_sequence(32, 32, 90, 30.0, 72.0).map_err(|e| format!("{e}"))?;
1177 let consent = BiosenseConsent::grant_security_template(1);
1178 let r = self_monitor_pulse_evm(
1179 consent,
1180 seq.as_packed_rgb(),
1181 seq.n_frames,
1182 seq.width,
1183 seq.height,
1184 seq.fps,
1185 use_evm,
1186 0.15,
1187 );
1188 Ok(BiosensePulseDemo {
1189 bpm: r.bpm,
1190 confidence: r.confidence,
1191 snr: r.snr,
1192 abstained: r.abstained,
1193 used_evm: r.used_evm,
1194 reason: r
1195 .reason
1196 .map(|a| format!("{a:?}"))
1197 .unwrap_or_else(|| "ok".into()),
1198 })
1199}
1200
1201#[derive(Debug, Clone, serde::Serialize)]
1203pub struct SrResultDto {
1204 pub before_data_url: String,
1205 pub after_data_url: String,
1206 pub backend_id: String,
1207 pub device: String,
1208 pub generative: bool,
1209 pub out_width: u32,
1210 pub out_height: u32,
1211 pub degraded: bool,
1212}
1213
1214pub fn super_resolve_image(
1220 bytes: &[u8],
1221 scale: u8,
1222 kernel: &str,
1223 prefer_gpu: bool,
1224) -> Result<SrResultDto, String> {
1225 use qualia_core_db::specialized_libs::computer_vision::cv::buffer::RgbView;
1226 use qualia_core_db::specialized_libs::computer_vision::cv::codecs::encode_png;
1227 use qualia_core_db::specialized_libs::computer_vision::gpu::dispatch::VisionComputeDevice;
1228 use qualia_core_db::specialized_libs::computer_vision::gpu::policy::{
1229 ThermalHint, VisionVramBudget,
1230 };
1231 use qualia_core_db::specialized_libs::computer_vision::sr::device_policy::super_resolve_with_policy;
1232 use qualia_core_db::specialized_libs::computer_vision::sr::super_resolve::{
1233 ClassicalKernel, EnhancementMode, SrBackend, SrRequest,
1234 };
1235
1236 if !(2..=4).contains(&scale) {
1237 return Err("scale must be 2..=4".into());
1238 }
1239
1240 let img = image::load_from_memory(bytes).map_err(|e| format!("decode image: {e}"))?;
1242 let rgb_img = img.to_rgb8();
1243 let (w, h) = rgb_img.dimensions();
1244 if w == 0 || h == 0 {
1245 return Err("empty image".into());
1246 }
1247 let rgb: Vec<u8> = rgb_img.into_raw();
1248
1249 let ck = match kernel {
1251 "nearest" => ClassicalKernel::Nearest,
1252 "bilinear" => ClassicalKernel::Bilinear,
1253 "lanczos" | "lanczos3" => ClassicalKernel::Lanczos3,
1254 _ => ClassicalKernel::Bicubic,
1255 };
1256 let req = SrRequest {
1257 rgb: &rgb,
1258 width: w,
1259 height: h,
1260 scale,
1261 backend: SrBackend::Classical(ck),
1262 mode: EnhancementMode::Sharpen,
1263 };
1264
1265 let ow = w.checked_mul(scale as u32).ok_or("output width overflow")?;
1267 let oh = h
1268 .checked_mul(scale as u32)
1269 .ok_or("output height overflow")?;
1270 let out_len = (ow as usize)
1271 .checked_mul(oh as usize)
1272 .and_then(|n| n.checked_mul(3))
1273 .ok_or("output size overflow")?;
1274 let mut out = vec![0u8; out_len];
1275 let (report, compute) = super_resolve_with_policy(
1276 &req,
1277 prefer_gpu,
1278 ThermalHint::Cool,
1279 VisionVramBudget::default(),
1280 &mut out,
1281 )
1282 .map_err(|e| format!("super_resolve: {e:?}"))?;
1283
1284 let before_view = RgbView::new(w, h, w.saturating_mul(3), &rgb).ok_or("bad input rgb view")?;
1286 let before_png = encode_png(before_view).map_err(|e| format!("encode before png: {e:?}"))?;
1287 let after_view = RgbView::new(
1288 report.out_width,
1289 report.out_height,
1290 ow.saturating_mul(3),
1291 &out,
1292 )
1293 .ok_or("bad output rgb view")?;
1294 let after_png = encode_png(after_view).map_err(|e| format!("encode after png: {e:?}"))?;
1295
1296 let device = match compute.device {
1297 VisionComputeDevice::Cpu => "cpu",
1298 VisionComputeDevice::SharedGpu => "shared_gpu",
1299 VisionComputeDevice::Unavailable => "unavailable",
1300 };
1301
1302 Ok(SrResultDto {
1303 before_data_url: format!("data:image/png;base64,{}", B64.encode(&before_png)),
1304 after_data_url: format!("data:image/png;base64,{}", B64.encode(&after_png)),
1305 backend_id: report.backend_id.to_string(),
1306 device: device.to_string(),
1307 generative: report.generative,
1308 out_width: report.out_width,
1309 out_height: report.out_height,
1310 degraded: compute.degraded,
1311 })
1312}
1313
1314pub fn vision_local_embed_demo(
1316 rgb: &[u8],
1317 width: u32,
1318 height: u32,
1319) -> Result<serde_json::Value, String> {
1320 use qualia_vision::{
1321 ahash_u64, color_hist_embed_rgb, dhash_u64, GrayView, RgbView, COLOR_HIST_EMBED_DIM,
1322 };
1323 let n = (width * height) as usize;
1324 if rgb.len() < n * 3 {
1325 return Err("buffer too small".into());
1326 }
1327 let mut gray = vec![0u8; n];
1328 for i in 0..n {
1329 let o = i * 3;
1330 gray[i] = ((rgb[o] as u16 + rgb[o + 1] as u16 + rgb[o + 2] as u16) / 3) as u8;
1331 }
1332 let g = GrayView::new(width, height, width, &gray).ok_or("bad gray view")?;
1333 let ah = ahash_u64(g).map_err(|e| format!("{e:?}"))?;
1334 let dh = dhash_u64(g).map_err(|e| format!("{e:?}"))?;
1335 let mut hist = [0.0f32; COLOR_HIST_EMBED_DIM];
1336 let rv = RgbView::new(width, height, width * 3, rgb).ok_or("bad rgb view")?;
1337 color_hist_embed_rgb(rv, &mut hist).map_err(|e| format!("{e:?}"))?;
1338 Ok(serde_json::json!({
1339 "ahash": format!("{:016x}", ah),
1340 "dhash": format!("{:016x}", dh),
1341 "hist_dim": COLOR_HIST_EMBED_DIM,
1342 "hist0": hist[0],
1343 "note": "local CBIR proxy; not foundation CLIP"
1344 }))
1345}