@@ -348,31 +348,26 @@ export class ApiClient {
348348 const v = item ?. value || { } ;
349349 if ( item ?. type === 'rectanglelabels' && v ) {
350350 // Some servers return normalized [0..1]; others use percents [0..100].
351- // Detect heuristically: if any value > 1, treat as percent .
351+ // Detect fractions when all values are <= 1. Otherwise assume percents .
352352 const vx = Number ( v . x || 0 ) ;
353353 const vy = Number ( v . y || 0 ) ;
354354 const vw = Number ( v . width || 0 ) ;
355355 const vh = Number ( v . height || 0 ) ;
356- const usePercent = [ vx , vy , vw , vh ] . some ( val => Math . abs ( val ) > 1 ) ;
357- const sx = usePercent ? 0.01 * w : w ;
358- const sy = usePercent ? 0.01 * h : h ;
359- const bxFloat = vx * sx ;
360- const byFloat = vy * sy ;
361- const bwFloat = vw * sx ;
362- const bhFloat = vh * sy ;
363- let bx = Math . max ( 0 , Math . floor ( bxFloat ) ) ;
364- let by = Math . max ( 0 , Math . floor ( byFloat ) ) ;
365- let bw = Math . round ( bwFloat ) ;
366- let bh = Math . round ( bhFloat ) ;
367- // Preserve tiny but positive boxes by enforcing 1px minimum when source width/height > 0
368- if ( vw > 0 && bw === 0 ) bw = 1 ;
369- if ( vh > 0 && bh === 0 ) bh = 1 ;
356+ const vals = [ vx , vy , vw , vh ] . map ( a => Math . abs ( a ) ) ;
357+ const isFraction = vals . every ( val => val <= 1 ) ;
358+ const sx = isFraction ? w : 0.01 * w ;
359+ const sy = isFraction ? h : 0.01 * h ;
360+ const bx = Math . max ( 0 , Math . round ( vx * sx ) ) ;
361+ const by = Math . max ( 0 , Math . round ( vy * sy ) ) ;
362+ const bw = Math . round ( vw * sx ) ;
363+ const bh = Math . round ( vh * sy ) ;
364+ // Ignore degenerate boxes per guidance
365+ if ( bw <= 0 || bh <= 0 ) continue ;
370366 // Clamp to image bounds
371- if ( bx + bw > w ) bw = Math . max ( 0 , w - bx ) ;
372- if ( by + bh > h ) bh = Math . max ( 0 , h - by ) ;
367+ const bwClamped = Math . max ( 0 , Math . min ( bw , w - bx ) ) ;
368+ const bhClamped = Math . max ( 0 , Math . min ( bh , h - by ) ) ;
373369 const conf = Number ( v . score != null ? v . score : ( group ?. score ?? 0 ) ) ;
374- // Some responses might provide width/height=0 (point-like). Filter non-positive boxes later.
375- boxes . push ( { x : bx , y : by , width : bw , height : bh , confidence : Math . max ( 0 , Math . min ( 1 , conf || 0 ) ) } ) ;
370+ boxes . push ( { x : bx , y : by , width : bwClamped , height : bhClamped , confidence : Math . max ( 0 , Math . min ( 1 , conf || 0 ) ) } ) ;
376371 }
377372 }
378373 }
@@ -398,15 +393,14 @@ export class ApiClient {
398393 for ( let i = 0 ; i < boxesArr . length ; i ++ ) {
399394 const b = boxesArr [ i ] || [ ] ;
400395 const x1 = Number ( b [ 0 ] || 0 ) , y1 = Number ( b [ 1 ] || 0 ) , x2 = Number ( b [ 2 ] || 0 ) , y2 = Number ( b [ 3 ] || 0 ) ;
401- const usePercent = [ x1 , y1 , x2 , y2 ] . some ( val => Math . abs ( val ) > 1 ) ;
402- const sx = usePercent ? 0.01 * w : w ;
403- const sy = usePercent ? 0.01 * h : h ;
396+ const vals = [ x1 , y1 , x2 , y2 ] . map ( a => Math . abs ( a ) ) ;
397+ const isFraction = vals . every ( val => val <= 1 ) ;
398+ const sx = isFraction ? w : 0.01 * w ;
399+ const sy = isFraction ? h : 0.01 * h ;
404400 const px1 = Math . round ( x1 * sx ) , py1 = Math . round ( y1 * sy ) ;
405401 const px2 = Math . round ( x2 * sx ) , py2 = Math . round ( y2 * sy ) ;
406- let bw = Math . max ( 0 , px2 - px1 ) , bh = Math . max ( 0 , py2 - py1 ) ;
407- // Keep tiny but positive boxes visible
408- if ( ( px2 - px1 ) > 0 && bw === 0 ) bw = 1 ;
409- if ( ( py2 - py1 ) > 0 && bh === 0 ) bh = 1 ;
402+ const bw = Math . max ( 0 , px2 - px1 ) , bh = Math . max ( 0 , py2 - py1 ) ;
403+ if ( bw <= 0 || bh <= 0 ) continue ;
410404 const conf = Math . max ( 0 , Math . min ( 1 , Number ( scores [ i ] || 0 ) ) ) ;
411405 boxes . push ( { x : px1 , y : py1 , width : bw , height : bh , confidence : conf } ) ;
412406 }
@@ -419,18 +413,16 @@ export class ApiClient {
419413
420414 let primary , others = [ ] ;
421415 if ( ordered . length > 0 ) {
422- // If top box lacks area, fallback to point primary (use center when possible)
416+ // If top box somehow lacks area (should be filtered) , fallback to top-left point
423417 const top = ordered [ 0 ] ;
424418 if ( ( top . width || 0 ) > 0 && ( top . height || 0 ) > 0 ) {
425419 primary = { type : 'bbox' , ...top } ;
426420 } else {
427- const cx = Math . round ( top . x + ( Math . max ( 1 , top . width || 0 ) / 2 ) ) ;
428- const cy = Math . round ( top . y + ( Math . max ( 1 , top . height || 0 ) / 2 ) ) ;
429- primary = { type : 'point' , x : cx , y : cy , confidence : top . confidence } ;
421+ primary = { type : 'point' , x : Math . round ( top . x ) , y : Math . round ( top . y ) , confidence : top . confidence } ;
430422 }
431423 others = ordered . slice ( 1 ) . map ( b => ( ( b . width || 0 ) > 0 && ( b . height || 0 ) > 0 )
432424 ? ( { type : 'bbox' , ...b } )
433- : ( { type : 'point' , x : Math . round ( b . x + ( Math . max ( 1 , b . width || 0 ) / 2 ) ) , y : Math . round ( b . y + ( Math . max ( 1 , b . height || 0 ) / 2 ) ) , confidence : b . confidence } ) ) ;
425+ : ( { type : 'point' , x : Math . round ( b . x ) , y : Math . round ( b . y ) , confidence : b . confidence } ) ) ;
434426 } else {
435427 // Fallback to center point guess
436428 primary = { type : 'point' , x : Math . round ( w / 2 ) , y : Math . round ( h / 2 ) , confidence : 0.1 } ;
0 commit comments