1
+ import * as RadixTooltip from '@radix-ui/react-tooltip' ;
1
2
import { LineStyle , Style } from '@wandb/weave/common/components/MediaCard' ;
2
3
import { BoundingBox2D , LayoutType } from '@wandb/weave/common/types/media' ;
3
4
import { colorFromString } from '@wandb/weave/common/util/colors' ;
4
5
import { boxColor } from '@wandb/weave/common/util/media' ;
6
+ import * as Tooltip from '@wandb/weave/components/RadixTooltip' ;
5
7
import {
6
8
constString ,
7
9
File ,
@@ -392,6 +394,7 @@ const SegmentationMaskFromCG: FC<SegmentationMaskFromCGProps> = props => {
392
394
393
395
export interface BoundingBoxCanvasProps {
394
396
mediaSize : { width : number ; height : number } ;
397
+ cardSize ?: { width : number ; height : number } ;
395
398
boxData : BoundingBox2D [ ] ;
396
399
bboxControls : Controls . BoxControlState ;
397
400
sliders ?: Controls . BoxSliderState ;
@@ -440,6 +443,7 @@ const isBoundingBoxHidden = (
440
443
441
444
export const BoundingBoxesCanvas : FC < BoundingBoxCanvasProps > = ( {
442
445
mediaSize,
446
+ cardSize,
443
447
boxData,
444
448
bboxControls,
445
449
classStates,
@@ -449,7 +453,7 @@ export const BoundingBoxesCanvas: FC<BoundingBoxCanvasProps> = ({
449
453
const { lineStyle, classOverlayStates} = bboxControls ;
450
454
451
455
useEffect ( ( ) => {
452
- if ( canvasRef . current == null ) {
456
+ if ( canvasRef . current == null || cardSize != null ) {
453
457
return ;
454
458
}
455
459
@@ -482,9 +486,120 @@ export const BoundingBoxesCanvas: FC<BoundingBoxCanvasProps> = ({
482
486
bboxControls ,
483
487
mediaSize ,
484
488
sliderControls ,
489
+ cardSize ,
485
490
] ) ;
486
491
487
- return < OverlayCanvas { ...mediaSize } ref = { canvasRef } /> ;
492
+ const collisionBoundary = useRef < HTMLDivElement > ( null ) ;
493
+
494
+ // Scale the image to fit the card size
495
+ const scale = Math . min (
496
+ ( cardSize ?. width ?? 0 ) / mediaSize . width ,
497
+ ( cardSize ?. height ?? 0 ) / mediaSize . height
498
+ ) ;
499
+ const imageWidth = mediaSize . width * scale ;
500
+ const imageHeight = mediaSize . height * scale ;
501
+
502
+ return (
503
+ < div className = "relative flex h-full w-full items-center" >
504
+ { cardSize && Number . isFinite ( scale ) && (
505
+ < div
506
+ className = "relative overflow-hidden"
507
+ ref = { collisionBoundary }
508
+ style = { {
509
+ width : imageWidth ,
510
+ height : imageHeight ,
511
+ minWidth : imageWidth ,
512
+ } } >
513
+ { boxData . map ( box => {
514
+ const isHidden = isBoundingBoxHidden (
515
+ box ,
516
+ bboxControls ,
517
+ sliderControls
518
+ ) ;
519
+ if ( isHidden ) {
520
+ return null ;
521
+ }
522
+
523
+ const { class_id : classId } = box ;
524
+ const name = classStates ?. [ classId ] ?. name ?? `ID: ${ box . class_id } ` ;
525
+
526
+ let color = boxColor ( classId ) ;
527
+ if ( classStates ?. [ classId ] ?. color ) {
528
+ color = classStates ?. [ classId ] ?. color ;
529
+ }
530
+ const position = box . position ;
531
+ const widthMultiplier = box . domain === 'pixel' ? scale : imageWidth ;
532
+ const heightMultiplier =
533
+ box . domain === 'pixel' ? scale : imageHeight ;
534
+
535
+ let left , top , width , height ;
536
+ if ( 'minX' in position ) {
537
+ left = position . minX * widthMultiplier ;
538
+ top = position . minY * heightMultiplier ;
539
+ width = ( position . maxX - position . minX ) * widthMultiplier ;
540
+ height = ( position . maxY - position . minY ) * heightMultiplier ;
541
+ } else {
542
+ left =
543
+ ( position . middle [ 0 ] - position . width / 2 ) * widthMultiplier ;
544
+ top =
545
+ ( position . middle [ 1 ] - position . height / 2 ) * heightMultiplier ;
546
+ width = position . width * widthMultiplier ;
547
+ height = position . height * heightMultiplier ;
548
+ }
549
+ // Scale the outline width based on the card size. If it's a small card, we probably want a thinner outline so
550
+ // the image isn't obstructed too much. But if it's a large card, we want a thicker outline so it's more visible.
551
+ const outlineWidth = Math . min ( 6 , Math . max ( 2 , cardSize . width / 100 ) ) ;
552
+
553
+ return (
554
+ < div
555
+ data-test = "single-bounding-box"
556
+ key = { classId }
557
+ className = "absolute"
558
+ style = { {
559
+ left,
560
+ top : top ,
561
+ } } >
562
+ < div className = "relative" >
563
+ < Tooltip . Provider delayDuration = { 0 } >
564
+ < RadixTooltip . Root >
565
+ < Tooltip . Trigger >
566
+ < div
567
+ style = { {
568
+ outline : outlineWidth ,
569
+ outlineColor : color ,
570
+ outlineStyle :
571
+ lineStyle === 'line'
572
+ ? 'solid'
573
+ : lineStyle ?? 'solid' ,
574
+ width,
575
+ height,
576
+ } }
577
+ />
578
+ </ Tooltip . Trigger >
579
+ < Tooltip . Content
580
+ hideWhenDetached
581
+ style = { { backgroundColor : color , padding : 4 } }
582
+ avoidCollisions
583
+ side = "top"
584
+ align = "start"
585
+ collisionBoundary = { collisionBoundary . current } >
586
+ < div
587
+ style = { { backgroundColor : color } }
588
+ className = "text-white" >
589
+ { name }
590
+ </ div >
591
+ </ Tooltip . Content >
592
+ </ RadixTooltip . Root >
593
+ </ Tooltip . Provider >
594
+ </ div >
595
+ </ div >
596
+ ) ;
597
+ } ) }
598
+ </ div >
599
+ ) }
600
+ < OverlayCanvas { ...mediaSize } ref = { canvasRef } />
601
+ </ div >
602
+ ) ;
488
603
} ;
489
604
490
605
export interface BoundingBoxesProps {
0 commit comments