@@ -1344,4 +1344,198 @@ mod tests {
13441344
13451345 assert ! ( multi_object_candidates. len( ) > 0 , "should find some multi-object candidates" ) ;
13461346 }
1347+ #[ test]
1348+ #[ ignore]
1349+ fn test_centroid_focus_classification ( ) {
1350+ // Centroid-focused classification:
1351+ // 1. Find energy centroid around each 1/3 intersection
1352+ // 2. Extract detailed patch at centroid
1353+ // 3. Classify patch → more precise than whole-image archetype
1354+
1355+ let bytes = match std:: fs:: read ( "/tmp/tiny_imagenet_labeled.bin" ) {
1356+ Ok ( b) => b,
1357+ Err ( _) => { eprintln ! ( "SKIP: /tmp/tiny_imagenet_labeled.bin not found" ) ; return ; }
1358+ } ;
1359+
1360+ let n = u32:: from_le_bytes ( [ bytes[ 0 ] , bytes[ 1 ] , bytes[ 2 ] , bytes[ 3 ] ] ) as usize ;
1361+ let d = u32:: from_le_bytes ( [ bytes[ 4 ] , bytes[ 5 ] , bytes[ 6 ] , bytes[ 7 ] ] ) as usize ;
1362+ let n_classes = u32:: from_le_bytes ( [ bytes[ 8 ] , bytes[ 9 ] , bytes[ 10 ] , bytes[ 11 ] ] ) as usize ;
1363+
1364+ let mut labels = Vec :: with_capacity ( n) ;
1365+ for i in 0 ..n {
1366+ let off = 12 + i * 4 ;
1367+ labels. push ( u32:: from_le_bytes ( [ bytes[ off] , bytes[ off+1 ] , bytes[ off+2 ] , bytes[ off+3 ] ] ) as usize ) ;
1368+ }
1369+
1370+ let pixel_start = 12 + n * 4 ;
1371+ let img_w = 64usize ;
1372+ let img_h = 64usize ;
1373+ let ch = 3usize ;
1374+
1375+ // ── Helper: read pixel from binary ──
1376+ let pixel = |img_idx : usize , r : usize , c : usize , channel : usize | -> f64 {
1377+ let off = pixel_start + ( img_idx * d + r * img_w * ch + c * ch + channel) * 4 ;
1378+ f32:: from_le_bytes ( [ bytes[ off] , bytes[ off+1 ] , bytes[ off+2 ] , bytes[ off+3 ] ] ) as f64
1379+ } ;
1380+
1381+ // ── Helper: luminance at (r,c) ──
1382+ let luma = |img_idx : usize , r : usize , c : usize | -> f64 {
1383+ 0.299 * pixel ( img_idx, r, c, 0 ) + 0.587 * pixel ( img_idx, r, c, 1 ) + 0.114 * pixel ( img_idx, r, c, 2 )
1384+ } ;
1385+
1386+ // ── Step 1: For each image, find energy centroid around each 1/3 intersection ──
1387+ let patch_radius = 8usize ; // 16×16 patch around each intersection
1388+ let intersections = [ ( img_h/3 , img_w/3 ) , ( img_h/3 , 2 * img_w/3 ) ,
1389+ ( 2 * img_h/3 , img_w/3 ) , ( 2 * img_h/3 , 2 * img_w/3 ) ] ;
1390+
1391+ struct FocusPoint {
1392+ centroid_r : f64 ,
1393+ centroid_c : f64 ,
1394+ energy : f64 ,
1395+ }
1396+
1397+ let n_use = n. min ( 200 ) ;
1398+
1399+ // For each image, find the highest-energy intersection and its centroid
1400+ let mut focus_features: Vec < Vec < f64 > > = Vec :: with_capacity ( n_use) ;
1401+
1402+ for img_idx in 0 ..n_use {
1403+ let mut best_focus = FocusPoint { centroid_r : 32.0 , centroid_c : 32.0 , energy : 0.0 } ;
1404+
1405+ for & ( ir, ic) in & intersections {
1406+ // Compute energy centroid within patch
1407+ let mut total_energy = 0.0f64 ;
1408+ let mut weighted_r = 0.0f64 ;
1409+ let mut weighted_c = 0.0f64 ;
1410+
1411+ let r_min = ir. saturating_sub ( patch_radius) ;
1412+ let r_max = ( ir + patch_radius) . min ( img_h) ;
1413+ let c_min = ic. saturating_sub ( patch_radius) ;
1414+ let c_max = ( ic + patch_radius) . min ( img_w) ;
1415+
1416+ for r in r_min..r_max {
1417+ for c in c_min..c_max {
1418+ let e = luma ( img_idx, r, c) ;
1419+ // Use gradient magnitude as energy (edges = objects)
1420+ let grad = if r > 0 && r < img_h-1 && c > 0 && c < img_w-1 {
1421+ let dx = luma ( img_idx, r, c+1 ) - luma ( img_idx, r, c-1 ) ;
1422+ let dy = luma ( img_idx, r+1 , c) - luma ( img_idx, r-1 , c) ;
1423+ ( dx * dx + dy * dy) . sqrt ( )
1424+ } else {
1425+ 0.0
1426+ } ;
1427+ total_energy += grad;
1428+ weighted_r += r as f64 * grad;
1429+ weighted_c += c as f64 * grad;
1430+ }
1431+ }
1432+
1433+ if total_energy > best_focus. energy {
1434+ best_focus = FocusPoint {
1435+ centroid_r : if total_energy > 0.0 { weighted_r / total_energy } else { ir as f64 } ,
1436+ centroid_c : if total_energy > 0.0 { weighted_c / total_energy } else { ic as f64 } ,
1437+ energy : total_energy,
1438+ } ;
1439+ }
1440+ }
1441+
1442+ // ── Step 2: Extract detailed patch at centroid (12×12 = 144 pixels × 3ch = 432D) ──
1443+ let focus_radius = 6usize ;
1444+ let cr = best_focus. centroid_r . round ( ) as usize ;
1445+ let cc = best_focus. centroid_c . round ( ) as usize ;
1446+ let r_min = cr. saturating_sub ( focus_radius) ;
1447+ let r_max = ( cr + focus_radius) . min ( img_h) ;
1448+ let c_min = cc. saturating_sub ( focus_radius) ;
1449+ let c_max = ( cc + focus_radius) . min ( img_w) ;
1450+
1451+ let mut patch_features = Vec :: with_capacity ( 432 ) ;
1452+ for r in r_min..r_max {
1453+ for c in c_min..c_max {
1454+ for channel in 0 ..ch {
1455+ patch_features. push ( pixel ( img_idx, r, c, channel) ) ;
1456+ }
1457+ }
1458+ }
1459+ // Pad to fixed size if patch was clipped by image boundary
1460+ patch_features. resize ( 432 , 0.0 ) ;
1461+ focus_features. push ( patch_features) ;
1462+ }
1463+
1464+ let feat_d = 432 ;
1465+
1466+ // ── Step 3: Build archetypes from centroid patches ──
1467+ let mut focus_archetypes: Vec < Vec < f64 > > = vec ! [ vec![ 0.0 ; feat_d] ; n_classes] ;
1468+ let mut counts = vec ! [ 0usize ; n_classes] ;
1469+ for ( i, & label) in labels[ ..n_use] . iter ( ) . enumerate ( ) {
1470+ for j in 0 ..feat_d { focus_archetypes[ label] [ j] += focus_features[ i] [ j] ; }
1471+ counts[ label] += 1 ;
1472+ }
1473+ for c in 0 ..n_classes {
1474+ if counts[ c] > 0 { for j in 0 ..feat_d { focus_archetypes[ c] [ j] /= counts[ c] as f64 ; } }
1475+ }
1476+
1477+ // ── Step 4: Classify by nearest centroid-patch archetype ──
1478+ let mut correct_focus = 0usize ;
1479+ for ( i, & true_label) in labels[ ..n_use] . iter ( ) . enumerate ( ) {
1480+ let mut best_class = 0 ;
1481+ let mut best_dist = f64:: MAX ;
1482+ for c in 0 ..n_classes {
1483+ if counts[ c] == 0 { continue ; }
1484+ let dist: f64 = focus_features[ i] . iter ( ) . zip ( & focus_archetypes[ c] )
1485+ . map ( |( a, b) | ( a - b) * ( a - b) ) . sum :: < f64 > ( ) . sqrt ( ) ;
1486+ if dist < best_dist { best_dist = dist; best_class = c; }
1487+ }
1488+ if best_class == true_label { correct_focus += 1 ; }
1489+ }
1490+ let accuracy_focus = correct_focus as f64 / n_use as f64 ;
1491+
1492+ // ── Step 5: Compress centroid patches via golden-step ──
1493+ let base_dim = 17 ;
1494+ let golden_step = 11 ;
1495+ let compress = |v : & [ f64 ] | -> Vec < f64 > {
1496+ let fd = v. len ( ) ;
1497+ let n_oct = ( fd + base_dim - 1 ) / base_dim;
1498+ let mut sum = vec ! [ 0.0f64 ; base_dim] ;
1499+ let mut cnt = vec ! [ 0u32 ; base_dim] ;
1500+ for oct in 0 ..n_oct {
1501+ for bi in 0 ..base_dim {
1502+ let dim = oct * base_dim + ( ( bi * golden_step) % base_dim) ;
1503+ if dim < fd { sum[ bi] += v[ dim] ; cnt[ bi] += 1 ; }
1504+ }
1505+ }
1506+ sum. iter ( ) . zip ( & cnt) . map ( |( & s, & c) | if c > 0 { s / c as f64 } else { 0.0 } ) . collect ( )
1507+ } ;
1508+
1509+ let compressed_arch: Vec < Vec < f64 > > = focus_archetypes. iter ( ) . map ( |a| compress ( a) ) . collect ( ) ;
1510+ let compressed_feat: Vec < Vec < f64 > > = focus_features. iter ( ) . map ( |f| compress ( f) ) . collect ( ) ;
1511+
1512+ let mut correct_compressed = 0usize ;
1513+ for ( i, & true_label) in labels[ ..n_use] . iter ( ) . enumerate ( ) {
1514+ let mut best_class = 0 ;
1515+ let mut best_dist = f64:: MAX ;
1516+ for c in 0 ..n_classes {
1517+ if counts[ c] == 0 { continue ; }
1518+ let dist: f64 = compressed_feat[ i] . iter ( ) . zip ( & compressed_arch[ c] )
1519+ . map ( |( a, b) | ( a - b) * ( a - b) ) . sum :: < f64 > ( ) . sqrt ( ) ;
1520+ if dist < best_dist { best_dist = dist; best_class = c; }
1521+ }
1522+ if best_class == true_label { correct_compressed += 1 ; }
1523+ }
1524+ let accuracy_compressed = correct_compressed as f64 / n_use as f64 ;
1525+
1526+ eprintln ! ( "=== Centroid-Focus Classification ===" ) ;
1527+ eprintln ! ( " Images: {}, Classes: {}" , n_use, n_classes) ;
1528+ eprintln ! ( ) ;
1529+ eprintln ! ( " Centroid patch 432D: {:.1}% ({}/{})" , accuracy_focus * 100.0 , correct_focus, n_use) ;
1530+ eprintln ! ( " Compressed 17D (34B): {:.1}% ({}/{})" , accuracy_compressed * 100.0 , correct_compressed, n_use) ;
1531+ eprintln ! ( " Random baseline: {:.1}%" , 100.0 / n_classes as f64 ) ;
1532+ eprintln ! ( ) ;
1533+ eprintln ! ( " Compare to grid-line approaches:" ) ;
1534+ eprintln ! ( " Grid 768D: 29.8% (1536 bytes)" ) ;
1535+ eprintln ! ( " Grid→17D: 14.2% (34 bytes)" ) ;
1536+ eprintln ! ( " Focus 432D: {:.1}% (864 bytes) ← centroid sweet spot" , accuracy_focus * 100.0 ) ;
1537+ eprintln ! ( " Focus→17D: {:.1}% (34 bytes) ← compressed focus" , accuracy_compressed * 100.0 ) ;
1538+
1539+ assert ! ( accuracy_focus > 1.0 / n_classes as f64 , "should beat random" ) ;
1540+ }
13471541}
0 commit comments