Spaces:
Runtime error
Runtime error
| import React, { useState, useEffect } from 'react'; | |
| import { Box, Paper, Typography, Tooltip } from '@mui/material'; | |
| import { styled } from '@mui/system'; | |
| import axios from 'axios'; | |
| const ImageContainer = styled(Paper)({ | |
| width: '40vw', | |
| height: '40vw', | |
| display: 'flex', | |
| justifyContent: 'center', | |
| alignItems: 'center', | |
| position: 'relative', | |
| border: '3px dashed #ccc', | |
| margin: '10px', | |
| }); | |
| const ScrollableList = styled('div')({ | |
| width: '20vw', | |
| height: '17vw', | |
| overflowY: 'scroll', | |
| display: 'flex', | |
| flexDirection: 'column', | |
| // alignItems: 'center', | |
| border: '2px solid #ccc', | |
| margin: '10px', | |
| }); | |
| const TextItem = styled(Typography)({ | |
| margin: '5px 0', | |
| cursor: 'pointer', | |
| transition: 'color 0.3s ease', | |
| '&:hover': { | |
| color: '#1976d2', | |
| }, | |
| }); | |
| const TextItemPlain = styled(Typography)({ | |
| margin: '5px 0', | |
| }); | |
| const DotContainer = styled('div')({ | |
| display: 'flex', | |
| flexDirection: 'column', | |
| justifyContent: 'center', // Center vertically | |
| // alignItems: 'center', // Center horizontally | |
| margin: '0 10px', | |
| // height: '100%', // Ensure it takes up the full height of the parent container | |
| height: '40vw', | |
| }); | |
| const DotWrapper = styled('div')({ | |
| display: 'flex', | |
| alignItems: 'center', | |
| margin: '10px 0', | |
| }); | |
| const Dot = styled('div')(({ selected }) => ({ | |
| width: '20px', | |
| height: '20px', | |
| borderRadius: '50%', | |
| backgroundColor: selected ? '#1976d2' : '#ccc', | |
| marginRight: '10px', | |
| cursor: 'pointer', | |
| transition: 'background-color 0.3s ease', | |
| })); | |
| const LevelLabel = styled(Typography)({ | |
| fontSize: '14px', | |
| color: '#666', | |
| fontWeight: '500', | |
| }); | |
| const ImageGrid = styled('div')({ | |
| display: 'flex', | |
| justifyContent: 'flex-start', | |
| alignItems: 'center', | |
| marginTop: '20px', | |
| width: '80vw', | |
| overflowX: 'scroll', | |
| whiteSpace: 'nowrap', | |
| }); | |
| const Thumbnail = styled('img')({ | |
| width: '10vw', | |
| height: '10vw', | |
| margin: '5px', | |
| cursor: 'pointer', | |
| objectFit: 'cover', | |
| border: '2px solid transparent', | |
| transition: 'border-color 0.3s ease', | |
| '&:hover': { | |
| borderColor: '#1976d2', | |
| }, | |
| }); | |
| const CloseButton = styled('div')({ | |
| position: 'absolute', | |
| top: '10px', | |
| right: '10px', | |
| cursor: 'pointer', | |
| backgroundColor: '#f5f5f5', | |
| borderRadius: '50%', | |
| padding: '5px', | |
| display: 'flex', | |
| justifyContent: 'center', | |
| alignItems: 'center', | |
| '&:hover': { | |
| backgroundColor: '#e0e0e0', | |
| }, | |
| }); | |
| const App = () => { | |
| const [mainImage, setMainImage] = useState(null); | |
| const [selectedIdx, setSelectedIdx] = useState(-1); | |
| const [thumbnails, setThumbnails] = useState([]); | |
| const [selectedDot, setSelectedDot] = useState(-1); | |
| const [selectedValidIndices, setSelectedValidIndices] = useState([]); | |
| const [validItems, setValidItems] = useState([]); | |
| const [invalidItems, setInvalidItems] = useState([]); | |
| // Fetch thumbnails from backend | |
| useEffect(() => { | |
| const fetchThumbnails = async () => { | |
| try { | |
| const response = await axios.get('/api/return_thumbnails'); | |
| const { thumbnails } = response.data; | |
| setThumbnails(thumbnails.map((base64) => `data:image/png;base64,${base64}`)); // Decode Base64 | |
| } catch (error) { | |
| console.error('Error fetching thumbnails:', error); | |
| // retry in 1 second | |
| console.log('Retrying in 1 second...'); | |
| setTimeout(fetchThumbnails, 1000); | |
| } | |
| }; | |
| fetchThumbnails(); | |
| }, []); | |
| const fetchStateData = async () => { | |
| try { | |
| console.log('Fetching state data:', selectedIdx, selectedDot, selectedValidIndices); | |
| // join the valid indices with commas | |
| const validIndicesStr = selectedValidIndices.length === 0 ? 'None' : selectedValidIndices.join(','); | |
| const response = await axios.get('/api/return_state_data', { | |
| params: { | |
| image_index: selectedIdx, | |
| detail_level: selectedDot, | |
| object_list: validIndicesStr, | |
| }, | |
| }); | |
| const { mask_overlayed_image, valid_object_color_tuples, invalid_objects } = response.data; | |
| setMainImage(`data:image/png;base64,${mask_overlayed_image}`); | |
| setValidItems(valid_object_color_tuples); | |
| setInvalidItems(invalid_objects); | |
| } catch (error) { | |
| console.error('Error fetching state data:', error); | |
| } | |
| }; | |
| // UseEffect to fetch data after state changes | |
| useEffect(() => { | |
| if (selectedIdx !== -1 && selectedDot !== -1) { | |
| fetchStateData(); | |
| } | |
| }, [selectedIdx, selectedDot, selectedValidIndices]); | |
| const handleThumbnailClick = (index) => { | |
| console.log('Selected image:', index); | |
| setSelectedIdx(index); // Updates state | |
| setSelectedDot(2); // Updates state | |
| setSelectedValidIndices([]); // Updates state | |
| // fetchStateData will run automatically after state updates | |
| }; | |
| const handleCloseImage = () => { | |
| setMainImage(null); | |
| setSelectedIdx(-1); | |
| setSelectedDot(-1); | |
| setSelectedValidIndices([]); | |
| setValidItems([]); | |
| setInvalidItems([]); | |
| // No need to fetch state data since the image is closed | |
| }; | |
| const handleDotClick = (index) => { | |
| if (selectedIdx === -1) { | |
| console.log('Please select an image first'); | |
| return; | |
| } | |
| setSelectedDot(index); | |
| setSelectedValidIndices([]); | |
| // fetchStateData will run automatically after state updates | |
| }; | |
| const handleItemClick = (index) => { | |
| setSelectedValidIndices((prevSelected) => | |
| prevSelected.includes(index) | |
| ? prevSelected.filter((item) => item !== index) // Remove if already selected | |
| : [...prevSelected, index] // Add to selected items | |
| ); | |
| console.log('Toggled item:', index); | |
| // fetchStateData will run automatically after state updates | |
| }; | |
| const levels = ['Coarse', 'Mid', 'Fine']; | |
| return ( | |
| <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', p: 2 }}> | |
| <Box sx={{ display: 'flex', justifyContent: 'center', width: '100%' }}> | |
| <ImageContainer> | |
| {mainImage ? ( | |
| <> | |
| <img src={mainImage} alt="Selected" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> | |
| <CloseButton onClick={handleCloseImage}>X</CloseButton> | |
| </> | |
| ) : ( | |
| <Typography variant="h6" color="textSecondary">Please Select an Image Below</Typography> | |
| )} | |
| </ImageContainer> | |
| <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}> | |
| <Typography variant="h6"> | |
| Segmented | |
| </Typography> | |
| <ScrollableList> | |
| {validItems.map((tuple, index) => ( | |
| <TextItem | |
| key={index} | |
| onClick={() => handleItemClick(index)} | |
| style={{ | |
| color: selectedValidIndices.includes(index) ? '#1976d2' : '#000', // Change text color if selected | |
| borderLeft: `10px solid ${tuple[1]}`, // Add colored line | |
| paddingLeft: '10px', // Adjust padding to make space for the line | |
| // make the text bold if selected | |
| fontWeight: selectedValidIndices.includes(index) ? 'bold' : 'normal', | |
| // add margin to the left | |
| marginLeft: '1vw', | |
| marginTop: '0.75vh', | |
| }} | |
| > | |
| {tuple[0]} | |
| </TextItem> | |
| ))} | |
| </ScrollableList> | |
| <Typography variant="h6"> | |
| Detected (but not Segmented) | |
| </Typography> | |
| <ScrollableList> | |
| {invalidItems.map((text, index) => ( | |
| <TextItemPlain | |
| key={index} | |
| style={{ | |
| color: '#000', // Change text color if selected | |
| // borderLeft: `10px solid ${segmentationColors[index % segmentationColors.length]}`, // Add colored line | |
| paddingLeft: '10px', // Adjust padding to make space for the line | |
| // make the text bold if selected | |
| fontWeight: 'normal', | |
| // add margin to the left | |
| marginLeft: '1vw', | |
| marginTop: '0.75vh', | |
| }} | |
| > | |
| {text} | |
| </TextItemPlain> | |
| ))} | |
| </ScrollableList> | |
| </Box> | |
| <DotContainer> | |
| {levels.map((label, index) => ( | |
| <DotWrapper key={index}> | |
| <Dot | |
| selected={selectedDot === index} | |
| onClick={() => handleDotClick(index)} | |
| /> | |
| <LevelLabel>{label}</LevelLabel> | |
| </DotWrapper> | |
| ))} | |
| </DotContainer> | |
| </Box> | |
| <ImageGrid> | |
| {thumbnails.map((image, index) => ( | |
| <Thumbnail | |
| key={index} | |
| src={image} | |
| alt={`Thumbnail ${index}`} | |
| onClick={() => handleThumbnailClick(index)} | |
| /> | |
| ))} | |
| </ImageGrid> | |
| </Box> | |
| ); | |
| }; | |
| export default App; | |