/
AtlasProgrammer
/
pp-heat-map
Обзор
Документация
Войти
/
AtlasProgrammer
/
pp-heat-map
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
frontend/src/components/ImageAnalysis.tsx
325 строк
11 KB
AtlasProgrammer
upload files frontend
07 ноя 2025, 13:02
07 ноя 2025, 13:02
e56909e
Код
Авторство
О чём код?
import React, { useState, useCallback } from 'react'; import { Card, CardContent, Typography, Button, Box, Grid, Alert, CircularProgress, Chip, List, ListItem, ListItemText, Divider, } from '@mui/material'; import { useDropzone } from 'react-dropzone'; import { CloudUpload, PlayArrow, Image as ImageIcon } from '@mui/icons-material'; import { useAppStore } from '../store'; import { apiService } from '../services/api'; import { AnalysisResult } from '../types'; import { formatFileSize, validateImageFile } from '../utils'; const ImageAnalysis: React.FC = () => { const { settings, addToHistory, setLoading, showNotification } = useAppStore(); const [selectedFile, setSelectedFile] = useState<File | null>(null); const [analysisResult, setAnalysisResult] = useState<AnalysisResult | null>(null); const [previewUrl, setPreviewUrl] = useState<string>(''); const onDrop = useCallback((acceptedFiles: File[]) => { const file = acceptedFiles[0]; if (!file) return; const validation = validateImageFile(file); if (!validation.valid) { showNotification(validation.error!, 'error'); return; } setSelectedFile(file); setAnalysisResult(null); const url = URL.createObjectURL(file); setPreviewUrl(url); }, [showNotification]); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'image/*': ['.png', '.jpg', '.jpeg'] }, multiple: false, }); const handleAnalyze = async () => { if (!selectedFile) return; setLoading(true); try { const result = await apiService.analyzeImage( selectedFile, settings.anomalyThreshold, settings.minAreaPixels ); setAnalysisResult(result); addToHistory({ type: 'image_analysis', timestamp: new Date(), status: result.anomalies.length > 0 ? 'completed' : 'completed', fileName: selectedFile.name, fileSize: selectedFile.size, result: result.anomalies.length, date: new Date().toISOString(), details: result, }); showNotification( `Анализ завершен. Найдено аномалий: ${result.anomalies.length}`, 'success' ); } catch (error) { console.error('Analysis error:', error); showNotification('Ошибка при анализе изображения', 'error'); } finally { setLoading(false); } }; const clearSelection = () => { setSelectedFile(null); setAnalysisResult(null); if (previewUrl) { URL.revokeObjectURL(previewUrl); setPreviewUrl(''); } }; return ( <Box> <Typography variant="h4" gutterBottom sx={{ mb: 3 }}> 🔍 Анализ тепловых изображений </Typography> <Grid container spacing={3}> <Grid item xs={12} md={6}> <Card> <CardContent> <Typography variant="h6" gutterBottom> Загрузка изображения </Typography> <Box {...getRootProps()} sx={{ border: '2px dashed', borderColor: isDragActive ? 'primary.main' : 'grey.300', borderRadius: 2, p: 4, textAlign: 'center', cursor: 'pointer', bgcolor: isDragActive ? 'primary.50' : 'grey.50', transition: 'all 0.2s ease', '&:hover': { borderColor: 'primary.main', bgcolor: 'primary.50', }, }} > <input {...getInputProps()} /> <CloudUpload sx={{ fontSize: 48, color: 'grey.400', mb: 2 }} /> <Typography variant="h6" gutterBottom> {isDragActive ? 'Отпустите файл здесь' : 'Перетащите файл сюда или нажмите для выбора'} </Typography> <Typography variant="body2" color="text.secondary"> Поддерживаются форматы: PNG, JPG, JPEG (до 10MB) </Typography> </Box> {selectedFile && ( <Box sx={{ mt: 2 }}> <Alert severity="info" sx={{ mb: 2 }}> <Typography variant="body2"> <strong>Файл:</strong> {selectedFile.name} </Typography> <Typography variant="body2"> <strong>Размер:</strong> {formatFileSize(selectedFile.size)} </Typography> </Alert> <Box sx={{ display: 'flex', gap: 1 }}> <Button variant="contained" startIcon={<PlayArrow />} onClick={handleAnalyze} disabled={useAppStore.getState().loading} > Запустить анализ </Button> <Button variant="outlined" onClick={clearSelection}> Очистить </Button> </Box> </Box> )} </CardContent> </Card> </Grid> <Grid item xs={12} md={6}> <Card> <CardContent> <Typography variant="h6" gutterBottom> Предварительный просмотр </Typography> {previewUrl ? ( <Box sx={{ textAlign: 'center' }}> <img src={previewUrl} alt="Preview" style={{ maxWidth: '100%', maxHeight: '300px', borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', }} /> </Box> ) : ( <Box sx={{ height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', border: '2px dashed', borderColor: 'grey.300', borderRadius: 2, bgcolor: 'grey.50', }} > <Box sx={{ textAlign: 'center' }}> <ImageIcon sx={{ fontSize: 48, color: 'grey.400', mb: 1 }} /> <Typography variant="body2" color="text.secondary"> Изображение не выбрано </Typography> </Box> </Box> )} </CardContent> </Card> </Grid> {analysisResult && ( <Grid item xs={12}> <Card> <CardContent> <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> <Typography variant="h6" sx={{ flexGrow: 1 }}> 📊 Результаты анализа </Typography> <Chip label={`Найдено аномалий: ${analysisResult.anomalies.length}`} color={analysisResult.anomalies.length > 0 ? 'error' : 'success'} variant="filled" /> </Box> <Grid container spacing={2}> <Grid item xs={12} md={6}> <Typography variant="subtitle1" gutterBottom> Исходное изображение </Typography> <img src={analysisResult.originalImage} alt="Original" style={{ width: '100%', borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', }} /> </Grid> <Grid item xs={12} md={6}> <Typography variant="subtitle1" gutterBottom> Обнаруженные аномалии </Typography> <img src={analysisResult.resultImage} alt="Result" style={{ width: '100%', borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', }} /> </Grid> </Grid> {analysisResult.anomalies.length > 0 && ( <Box sx={{ mt: 3 }}> <Typography variant="h6" gutterBottom> Детали обнаруженных аномалий: </Typography> <List> {analysisResult.anomalies.map((anomaly, index) => ( <React.Fragment key={index}> <ListItem> <ListItemText primary={`Аномалия ${index + 1}`} secondary={ <Box> <Typography variant="body2"> Центр: ({anomaly.center[0]}, {anomaly.center[1]}) </Typography> <Typography variant="body2"> Площадь: {anomaly.area_pixels} пикселей </Typography> <Typography variant="body2"> Область: {anomaly.bbox[2]}×{anomaly.bbox[3]} пикселей </Typography> </Box> } /> </ListItem> {index < analysisResult.anomalies.length - 1 && <Divider />} </React.Fragment> ))} </List> </Box> )} </CardContent> </Card> </Grid> )} {useAppStore.getState().loading && ( <Box sx={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, bgcolor: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999, }} > <Card sx={{ p: 3, textAlign: 'center' }}> <CircularProgress sx={{ mb: 2 }} /> <Typography variant="h6">Анализируем изображение...</Typography> </Card> </Box> )} </Grid> </Box> ); }; export default ImageAnalysis;