From cd5f41913f94918a22b74714b54badbb20e6008e Mon Sep 17 00:00:00 2001 From: Tarek Date: Wed, 13 Dec 2023 14:55:45 +0100 Subject: [PATCH] make bbox to component --- src/lenses/BoundingBoxLens/index.tsx | 257 +++++++++++---------------- 1 file changed, 102 insertions(+), 155 deletions(-) diff --git a/src/lenses/BoundingBoxLens/index.tsx b/src/lenses/BoundingBoxLens/index.tsx index df44f03b..f67b7b49 100644 --- a/src/lenses/BoundingBoxLens/index.tsx +++ b/src/lenses/BoundingBoxLens/index.tsx @@ -1,9 +1,8 @@ -import { useRef, useCallback, useState, useEffect } from 'react'; +import { useColorTransferFunction } from '../../hooks'; +import { useRef, useCallback, useState } from 'react'; import { Lens } from '../../types'; import tw, { styled, theme } from 'twin.macro'; -import { CategoricalDataType, SequenceDataType } from '../../datatypes'; -import { ColorsState, useColors } from '../../stores/colors'; -import { Dataset, useDataset } from '../../stores/dataset'; +import { CategoricalDataType } from '../../datatypes'; import chroma from 'chroma-js'; const Container = styled.div` @@ -16,6 +15,52 @@ const Container = styled.div` } `; +interface BBoxProps { + width: number; + height: number; + x: number; + y: number; + color: string; + label: string; +} + +const BBox = ({ width, height, x, y, color, label }: BBoxProps) => { + // Box format [x, y, w, h] normalized. + const white = chroma(theme`colors.white`); + const black = chroma(theme`colors.black`); + + const textColor = + chroma.contrast(color ?? black, white) > chroma.contrast(color ?? white, black) + ? white + : black; + + return ( + + + + + {label} + + + ); +}; + const BoundingBoxLens: Lens = ({ urls, values, columns }) => { const container = useRef(null); const svgRef = useRef(null); @@ -38,182 +83,84 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => { const imageColumnIndex = columns.findIndex((col) => col.type.kind === 'Image'); const url = urls[imageColumnIndex]; - let boxes: [number[]] | []; - let categories: number[] | []; - let invertedCategories = (index: number) => index.toString(); + let boxes: [number[]] | [] = []; + let categories: number[] | [] = []; // Check if single bounding box or multiple if (bboxColumnIndex != -1) { boxes = [values[bboxColumnIndex] as number[]]; } else if (bboxesColumnIndex != -1) { boxes = values[bboxesColumnIndex] as [number[]]; - } else { - boxes = []; } if (categoryColumnIndex != -1) { categories = [values[categoryColumnIndex] as number]; - - invertedCategories = (index) => - (columns[categoryColumnIndex].type as CategoricalDataType) - .invertedCategories[index]; } else if (categoriesColumnIndex != -1) { categories = values[categoriesColumnIndex] as [number]; - - invertedCategories = (index) => - ( - (columns[categoriesColumnIndex].type as SequenceDataType) - .dtype as CategoricalDataType - ).invertedCategories[index]; - } else { - categories = []; } - // eslint-disable-next-line react-hooks/exhaustive-deps - const colorTransferFunctionSelector = useCallback( - (d: Dataset) => { - if (categoryColumnIndex != -1) { - return d.colorTransferFunctions[columns[categoryColumnIndex].key]?.[ - 'full' - ]; - } - return undefined; - }, - [columns, categoryColumnIndex] - ); + // Natural dimensions of the image + const naturalWidth = imgSize.width ?? 1; + const naturalHeight = imgSize.height ?? 1; + const imageAspectRatio = naturalWidth / naturalHeight; - const colorPaletteSelector = (c: ColorsState) => c.continuousPalette; - const colorPalette = useColors(colorPaletteSelector); - const colorTransferFunction = useDataset(colorTransferFunctionSelector); + //Dimensions of the parent element + const parentWidth = container.current?.offsetWidth ?? 0; + const parentHeight = container.current?.offsetHeight ?? 1; - let colorFunc: (i: number) => string; + const parentAspectRatio = parentWidth / parentHeight; - if (categories.length === 0) { - colorFunc = (i: number) => colorPalette.scale().colors(boxes.length)[i]; + let renderedWidth: number, renderedHeight: number; + + if (imageAspectRatio > parentAspectRatio) { + renderedWidth = parentWidth; + renderedHeight = parentWidth / imageAspectRatio; } else { - if (colorTransferFunction !== undefined) { - colorFunc = (i: number) => colorTransferFunction(categories[i]).hex(); - } else { - colorFunc = (i: number) => { - const dtype = (columns[categoriesColumnIndex].type as SequenceDataType) - .dtype as CategoricalDataType; - const index = dtype.categories[invertedCategories(categories[i])]; - return colorPalette - .scale() - .colors(Object.keys(dtype.categories).length)[index]; - }; - } + renderedHeight = parentHeight; + renderedWidth = parentHeight * imageAspectRatio; } + const offsetWidth = (parentWidth - renderedWidth) / 2; + const offsetHeight = (parentHeight - renderedHeight) / 2; + + const categoricalColumn = + columns[categoryColumnIndex] ?? columns[categoriesColumnIndex]; + const categoricalDtype = ( + categoricalColumn?.type?.kind === 'Sequence' + ? categoricalColumn?.type?.dtype + : categoricalColumn?.type + ) as CategoricalDataType | undefined; + + const colorTransferFunction = useColorTransferFunction( + categories, + categoricalDtype + ); - useEffect(() => { - const handleLoad = () => { - setImgSize({ - width: imgRef.current?.naturalWidth ?? 0, - height: imgRef.current?.naturalHeight ?? 1, - }); - }; - - const image = imgRef.current; - - if (image) { - image.addEventListener('load', handleLoad); - } - - return () => { - if (image) { - image.removeEventListener('load', handleLoad); - } - }; + const handleLoad = useCallback(() => { + setImgSize({ + width: imgRef.current?.naturalWidth ?? 0, + height: imgRef.current?.naturalHeight ?? 1, + }); }, []); - //}, [boxes, categories, colorFunc]); return ( - URL not found! + URL not found! - {boxes.map((box, index) => { - if (!svgRef.current) return; - if (!container.current) return; - if (!boxes) return; - - console.log('draw a box'); - //const image = imgRef.current; - - // Natural dimensions of the image - //const naturalWidth = image?.naturalWidth ?? 1; - //const naturalHeight = image?.naturalHeight ?? 1; - const naturalWidth = imgSize.width; - const naturalHeight = imgSize.height; - const imageAspectRatio = naturalWidth / naturalHeight; - - // Dimensions of the parent element - const parentWidth = container.current.offsetWidth; - const parentHeight = container.current.offsetHeight; - - const parentAspectRatio = parentWidth / parentHeight; - - let renderedWidth, renderedHeight; - - if (imageAspectRatio > parentAspectRatio) { - renderedWidth = parentWidth; - renderedHeight = parentWidth / imageAspectRatio; - } else { - renderedHeight = parentHeight; - renderedWidth = parentHeight * imageAspectRatio; - } - const offsetWidth = (parentWidth - renderedWidth) / 2; - const offsetHeight = (parentHeight - renderedHeight) / 2; - - // Box format [x, y, w, h] normalized. - const x = box[0] * renderedWidth + offsetWidth; - const y = box[1] * renderedHeight + offsetHeight; - const width = (box[2] - box[0]) * renderedWidth; - const height = (box[3] - box[1]) * renderedHeight; - const boxColor = colorFunc(index); - const text = invertedCategories(categories[index]); - const white = chroma(theme`colors.white`); - const black = chroma(theme`colors.black`); - - const textColor = - chroma.contrast(boxColor, white) > - chroma.contrast(boxColor, black) - ? white - : black; - - return ( - - - - - {text} - - - ); - })} + + {boxes.map((box, index) => ( + + ))} );