intrusionx-backend / utils /visualizer.py
ayucancode's picture
Upload 18 files
56c08f3 verified
Raw
History Blame Contribute Delete
4.84 kB
"""
Tattva.AI — Visualizer
Generates heatmaps and visual overlays for detection results.
Uses Error Level Analysis (ELA) as a lightweight, no-ML-required technique
to highlight potentially manipulated regions.
"""
import numpy as np
from PIL import Image, ImageChops, ImageEnhance, ImageFilter
import matplotlib
matplotlib.use('Agg') # Non-interactive backend
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import io
import tempfile
def generate_ela_heatmap(image: Image.Image, quality: int = 90, scale: int = 15) -> Image.Image:
"""
Generate an Error Level Analysis (ELA) heatmap overlay.
ELA works by re-saving the image at a known quality level and then
comparing the difference. Manipulated regions often show higher
error levels than the rest of the image.
Parameters
----------
image : PIL.Image
Input image.
quality : int
JPEG compression quality for re-save.
scale : int
Brightness multiplier for the difference image.
Returns
-------
PIL.Image — the ELA heatmap overlay.
"""
if image.mode != "RGB":
image = image.convert("RGB")
# Save to buffer at specified quality
buffer = io.BytesIO()
image.save(buffer, format="JPEG", quality=quality)
buffer.seek(0)
resaved = Image.open(buffer)
# Compute pixel-wise difference
diff = ImageChops.difference(image, resaved)
# Enhance the difference to make artifacts visible
extrema = diff.getextrema()
max_diff = max([ex[1] for ex in extrema]) if extrema else 1
if max_diff == 0:
max_diff = 1
# Scale up the difference
enhancer = ImageEnhance.Brightness(diff)
diff_enhanced = enhancer.enhance(scale)
return diff_enhanced
def generate_heatmap_overlay(image: Image.Image, quality: int = 90) -> Image.Image:
"""
Generate a colored heatmap overlaid on the original image.
Red regions = higher error = potential manipulation.
Returns
-------
PIL.Image — original image with colored heatmap overlay.
"""
if image.mode != "RGB":
image = image.convert("RGB")
# Get ELA image
ela = generate_ela_heatmap(image, quality=quality, scale=20)
# Convert to grayscale intensity map
ela_gray = ela.convert("L")
ela_array = np.array(ela_gray, dtype=np.float32)
# Normalize to 0-1
max_val = ela_array.max()
if max_val > 0:
ela_array = ela_array / max_val
# Apply a slight blur for smoother heatmap
ela_array_img = Image.fromarray((ela_array * 255).astype(np.uint8))
ela_array_img = ela_array_img.filter(ImageFilter.GaussianBlur(radius=3))
ela_array = np.array(ela_array_img, dtype=np.float32) / 255.0
# Apply colormap (red = hot = manipulated)
colored = cm.jet(ela_array) # Returns RGBA float array
colored_rgb = (colored[:, :, :3] * 255).astype(np.uint8)
heatmap_img = Image.fromarray(colored_rgb)
# Blend with original
blended = Image.blend(image, heatmap_img, alpha=0.4)
return blended
def generate_confidence_gauge(confidence: float, verdict: str) -> Image.Image:
"""
Generate a visual confidence gauge using matplotlib.
Returns
-------
PIL.Image — rendered gauge chart.
"""
fig, ax = plt.subplots(figsize=(4, 2.5), subplot_kw={'projection': 'polar'})
fig.patch.set_facecolor('#0f0f19')
# Gauge settings
colors_map = {
"DEEPFAKE": "#ff5064",
"SUSPICIOUS": "#ffd23c",
"AUTHENTIC": "#00e6a0",
"ERROR": "#666666",
}
color = colors_map.get(verdict, "#666666")
# Draw gauge (half circle)
theta = np.linspace(np.pi, 0, 100)
radii = np.ones(100)
# Background arc (grey)
ax.barh(1, np.pi, height=0.5, left=0, color='#1a1a2e', edgecolor='none')
# Foreground arc (colored by confidence)
fill_angle = np.pi * (confidence / 100)
ax.barh(1, fill_angle, height=0.5, left=np.pi - fill_angle,
color=color, edgecolor='none', alpha=0.9)
# Center text
ax.text(np.pi / 2, 0.3, f"{confidence:.1f}%",
ha='center', va='center', fontsize=20, fontweight='bold',
color=color, family='monospace')
ax.text(np.pi / 2, -0.2, verdict,
ha='center', va='center', fontsize=10, fontweight='bold',
color='white', family='monospace')
# Clean up
ax.set_ylim(0, 1.5)
ax.set_thetamin(0)
ax.set_thetamax(180)
ax.set_rticks([])
ax.set_thetagrids([])
ax.spines['polar'].set_visible(False)
ax.grid(False)
# Render to PIL Image
buf = io.BytesIO()
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight',
facecolor='#0f0f19', edgecolor='none', transparent=False)
plt.close(fig)
buf.seek(0)
gauge_img = Image.open(buf).convert("RGB")
return gauge_img