multisense_df / app /frontend /src /components /GradCamViewer.jsx
vijay-2132's picture
Clean deployment without large checkpoints
a94bd47
Raw
History Blame Contribute Delete
4.54 kB
// src/components/GradCamViewer.jsx
export default function GradCamViewer({ gradcamImage, filename }) {
const hasImage = gradcamImage && gradcamImage.length > 0;
return (
<div
className="gradcam-card glass-card"
role="figure"
aria-label="Grad-CAM saliency heatmap"
>
<h3>Grad-CAM Heatmap</h3>
<p className="gradcam-subtitle">
Gradient-weighted Class Activation Mapping — highlights regions influencing the deepfake decision
</p>
<div className="gradcam-image-wrap">
{hasImage ? (
<img
className="gradcam-image"
src={`data:image/png;base64,${gradcamImage}`}
alt="Grad-CAM saliency heatmap highlighting manipulated facial regions"
/>
) : (
<div className="gradcam-placeholder" aria-label="No heatmap available">
<div
className="icon"
aria-hidden="true"
style={{ fontSize: '3rem', opacity: 0.35 }}
>
🌡️
</div>
<p>
{filename === 'demo_video.mp4'
? 'Grad-CAM heatmap is generated from real video frames. Run the backend with an actual video file to see facial manipulation highlighting.'
: 'No heatmap data returned by the server. The backend may not have Grad-CAM enabled.'}
</p>
{/* Placeholder visual illustration */}
<div
aria-hidden="true"
style={{
width: '100%',
maxWidth: 320,
height: 180,
borderRadius: '8px',
background: `
radial-gradient(ellipse 60% 50% at 50% 45%, rgba(239,68,68,0.25) 0%, transparent 60%),
radial-gradient(ellipse 30% 20% at 35% 35%, rgba(239,68,68,0.4) 0%, transparent 40%),
radial-gradient(ellipse 30% 20% at 65% 35%, rgba(239,68,68,0.4) 0%, transparent 40%),
radial-gradient(ellipse 20% 15% at 50% 60%, rgba(239,68,68,0.3) 0%, transparent 40%),
linear-gradient(135deg, #1a1030 0%, #0d1520 100%)
`,
position: 'relative',
overflow: 'hidden',
border: '1px solid rgba(239,68,68,0.15)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{/* Face outline placeholder */}
<svg width="80" height="100" viewBox="0 0 80 100" fill="none" opacity="0.25">
<ellipse cx="40" cy="45" rx="28" ry="35" stroke="#ef4444" strokeWidth="1.5" />
<ellipse cx="28" cy="38" rx="5" ry="4" stroke="#ef4444" strokeWidth="1" />
<ellipse cx="52" cy="38" rx="5" ry="4" stroke="#ef4444" strokeWidth="1" />
<path d="M30 62 Q40 70 50 62" stroke="#ef4444" strokeWidth="1.5" strokeLinecap="round" />
</svg>
<span
style={{
position: 'absolute',
bottom: 8,
left: '50%',
transform: 'translateX(-50%)',
fontSize: '0.65rem',
color: 'rgba(239,68,68,0.5)',
whiteSpace: 'nowrap',
}}
>
Placeholder — not real heatmap
</span>
</div>
</div>
)}
</div>
{/* Legend */}
{hasImage && (
<div className="gradcam-legend">
<div className="legend-item">
<div className="legend-dot" style={{ background: '#ef4444' }} />
<span>High activation (suspicious)</span>
</div>
<div className="legend-item">
<div className="legend-dot" style={{ background: '#f97316' }} />
<span>Medium activation</span>
</div>
<div className="legend-item">
<div className="legend-dot" style={{ background: '#3b82f6' }} />
<span>Low activation</span>
</div>
</div>
)}
{!hasImage && (
<p
style={{
marginTop: '12px',
fontSize: '0.75rem',
color: 'var(--text-muted)',
textAlign: 'center',
}}
>
Heatmaps available when backend returns <code style={{ color: 'var(--accent-cyan-light)', fontSize: '0.7rem' }}>gradcam_image</code> in response
</p>
)}
</div>
);
}