'use client'; import { useState, useRef, useEffect } from 'react'; import axios from 'axios'; import useImage from 'use-image'; import Konva from 'konva'; import { Stage, Image, Layer } from 'react-konva'; import { BASE_URL } from './constants'; import Dropdown from './dropdown'; interface RGB { r: number; g: number; b: number; } interface Box { x: number; y: number; width: number; height: number; } const maskFilter = (imageData: ImageData, color: RGB) => { const nPixels = imageData.data.length; for (let i = 0; i < nPixels; i += 4) { const r = imageData.data[i]; const g = imageData.data[i + 1]; const b = imageData.data[i + 2]; if (r <= 1 && g <= 1 && b <= 1) { imageData.data[i + 3] = 0; } else { imageData.data[i] = color.r; imageData.data[i + 1] = color.g; imageData.data[i + 2] = color.b; imageData.data[i + 3] = 128; } } }; export default function Canvas({ imageUrl, imageName }: { imageUrl: string, imageName: string }) { const [image] = useImage(imageUrl, 'anonymous'); const stageRef = useRef(null); const layerRef = useRef(null); const groupRef = useRef([]); const pointsRef = useRef>([]); const labelsRef = useRef>([]); const instanceColorsRef = useRef([]); const [classList, setClassList] = useState([]); const classOptionsRef = useRef([]); const classSelectionRef = useRef(''); const [useLatest, setUseLatest] = useState(false); const selectedInstanceRef = useRef(-1); const [selectedInstanceStyle, setSelectedInstanceStyle] = useState(false); const clearLayers = () => { groupRef.current.forEach((group) => { group.destroy(); }); groupRef.current = []; layerRef.current?.draw(); pointsRef.current = []; labelsRef.current = []; instanceColorsRef.current = [Konva.Util.getRandomColor()]; setClassList(_ => ([])); } useEffect(() => { clearLayers(); }, [imageUrl]); const showMask = (data: string, group: Konva.Group) => { const layer = layerRef.current; if (!layer) return; const color = Konva.Util.getRGB( instanceColorsRef.current[instanceColorsRef.current.length - 1] ); const width = image?.width; const height = image?.height; const maskObj = new window.Image(); maskObj.onload = () => { const image = new Konva.Image({ x: 0, y: 0, image: maskObj, width: width, height: height, }); image.filters([(imageData: ImageData) => maskFilter(imageData, color)]); image.cache(); group.add(image); if (groupRef.current.length === 0) groupRef.current.push(group); layer.add(group); layer.draw() }; maskObj.src = `data:image/png;base64,${data}`; setUseLatest(true); } const showBox = (box: Box, group: Konva.Group) => { const layer = layerRef.current; if (!layer) return; const color = Konva.Util.getRGB( instanceColorsRef.current[instanceColorsRef.current.length - 1] ); const width = image?.width; const height = image?.height; function rgbToHex(r, g, b) { return '#' + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1); } const rect = new Konva.Rect({ x: box.x, y: box.y, width: box.width, height: box.height, stroke: rgbToHex(color.r, color.g, color.b), storkeWidth: 2, }); if (groupRef.current.length === 0) groupRef.current.push(group); group.add(rect); layer.add(group); layer.draw(); setUseLatest(true); } const handleStageClick = async (e: Konva.KonvaEventObject) => { const stage = stageRef.current; const layer = layerRef.current; if (!stage || !layer) return; const pos = e.target.getStage()?.getPointerPosition(); if (!pos) return; let labels = labelsRef.current; if (e.evt.button === 2) { labels = [...labels, 0]; } else { labels = [...labels, 1]; } labelsRef.current = labels; // re-draw last layer so we don't overlap masks on clicks if (groupRef.current.length > 0) { groupRef.current[groupRef.current.length - 1].destroy(); } else { groupRef.current.push(new Konva.Group()); } layer.draw(); let points = pointsRef.current; points = [...points, [pos.x, pos.y]]; pointsRef.current = points; const res = await axios.post( `v1/get_label_preds/${imageName}`, { points: points, labels: labels, } ); showMask(res.data.masks[0], groupRef.current[groupRef.current.length - 1]); } const pinInstance = async () => { const layer = layerRef.current; if (!layer) return; const groupList = groupRef.current; // if (groupList.length === 0) return; if (classSelectionRef.current === '') { alert('Please select a class'); } else { groupList.push(new Konva.Group()); pointsRef.current = []; labelsRef.current = []; instanceColorsRef.current.push(Konva.Util.getRandomColor()); setClassList(prev => ([...prev, classSelectionRef.current])) setUseLatest(false); } } const deleteSelectedInstance = () => { const layer = layerRef.current; if (!layer) return; if (selectedInstanceRef.current === -1) return; // always 1 extra group for the next instance if (groupRef.current.length <= 1) return; groupRef.current[selectedInstanceRef.current].destroy(); groupRef.current.splice(selectedInstanceRef.current, 1); setClassList([ ...classList.slice(0, selectedInstanceRef.current), ...classList.slice(selectedInstanceRef.current + 1) ]); instanceColorsRef.current.splice(selectedInstanceRef.current, 1); layer.draw() // reset selectedInstanceRef? selectedInstanceRef.current = -1; } const submitLabels = async () => { const groupList = groupRef.current; if (groupList.length === 0) return; const masks = []; const length = useLatest ? groupList.length : groupList.length - 1; for (let i = 0; i < length; i++) { const labelData = groupList[i].toDataURL({ x: 0, y: 0, width: image?.width, height: image?.height }); masks.push(labelData); } const res = await axios.put(`v1/label_image/${imageName}`, { masks: masks, labels: classList, } ); pointsRef.current = []; labelsRef.current = []; clearLayers(); } const getLabels = async () => { clearLayers(); try { const res = await axios.get(`v1/get_labels/${imageName}`); classOptionsRef.current = [...new Set(res.data.labels)]; // add an initial group to start with groupRef.current.push(new Konva.Group()); for (let i = 0; i < res.data.masks.length; i++) { showMask(res.data.masks[i], groupRef.current[groupRef.current.length - 1]); classSelectionRef.current = res.data.labels[i]; pinInstance(); } } catch (err) { console.log(err); } } const predLabels = async () => { const length = useLatest ? groupRef.current.length : groupRef.current.length - 1; if (groupRef.current.length === 0 || classList.length === 0) { alert('Please pin an instance first'); return } const mask = groupRef.current[length - 1].toDataURL({ x: 0, y: 0, width: image?.width, height: image?.height }); clearLayers(); try { const res = await axios.post(`v1/get_multi_label_preds/${imageName}`, { mask: mask, label: classList[length - 1], }); groupRef.current.push(new Konva.Group()); for (let i = 0; i < res.data.masks.length; i++) { showMask(res.data.masks[i], groupRef.current[groupRef.current.length - 1]); classSelectionRef.current = res.data.labels[i]; pinInstance(); } } catch (err) { console.log(err); } } const getBboxes = async () => { clearLayers(); try { const res = await axios.get(`v1/get_labels/${imageName}`); classOptionsRef.current = [...new Set(res.data.labels)]; groupRef.current.push(new Konva.Group()); for (let i = 0; i < res.data.bboxes.length; i++) { showBox(res.data.bboxes[i], groupRef.current[groupRef.current.length - 1]); classSelectionRef.current = res.data.labels[i]; pinInstance(); } } catch (err) { console.log(err); } } return (

Instance Class List

    {classList.map((item, index) => (
  • { selectedInstanceRef.current = index; setSelectedInstanceStyle(prev => !prev) }} >{index} {item}
  • ))}

e.evt.preventDefault()} > image
           
); };