finalproject / utils /image_visualization.py
jarondon82's picture
Initial commit for EmotionMirror finalproject
f7e620e
"""
Image Visualization Module for EmotionMirror application.
This module contains functions for visualizing images with interactive controls
such as zoom, pan, and reset functionality.
"""
import streamlit as st
import numpy as np
from PIL import Image
import io
import logging
import base64
from typing import Dict, Any, Tuple, Optional
logger = logging.getLogger(__name__)
def get_image_download_link(img, filename="emotion_mirror_image.png", text="Download Image"):
"""
Generate a download link for an image.
Args:
img: PIL Image or numpy array
filename: Name of the file to download
text: Text to display for the download link
Returns:
HTML string with download link
"""
try:
# Convert numpy array to PIL Image if necessary
if isinstance(img, np.ndarray):
img_pil = Image.fromarray(img)
else:
img_pil = img
# Create a byte buffer
buffered = io.BytesIO()
img_pil.save(buffered, format="PNG")
# Encode the bytes to base64
img_str = base64.b64encode(buffered.getvalue()).decode()
# Create download link HTML
href = f'<a href="data:file/png;base64,{img_str}" download="{filename}">{text}</a>'
return href
except Exception as e:
logger.error(f"Error creating image download link: {e}")
return None
def display_image_with_controls(image,
title: str = "Image Viewer",
allow_zoom: bool = True,
allow_download: bool = True) -> Dict[str, Any]:
"""
Display an image with zoom, pan and reset controls.
Args:
image: PIL Image or numpy array to display
title: Title to display above the image
allow_zoom: Whether to show zoom controls
allow_download: Whether to show download option
Returns:
Dict containing the status and any relevant info
"""
try:
# Ensure image is not None
if image is None:
return {"success": False, "message": "No image provided"}
# Convert numpy array to PIL Image if necessary
if isinstance(image, np.ndarray):
pil_image = Image.fromarray(image)
else:
pil_image = image
# Store original image dimensions
original_width, original_height = pil_image.size
# Create container for image display
st.subheader(title)
# Image controls in columns for better layout
col1, col2, col3 = st.columns([1, 2, 1])
# Zoom functionality
zoom_factor = 1.0
if allow_zoom:
with col1:
zoom_factor = st.slider(
"Zoom",
min_value=0.5,
max_value=2.0,
value=1.0,
step=0.1,
help="Adjust the zoom level of the image"
)
# Reset button in the third column
with col3:
reset_pressed = st.button("Reset View", help="Reset zoom and view settings")
if reset_pressed:
zoom_factor = 1.0
st.session_state.zoom_factor = 1.0 # Store in session state
# Apply zoom if needed
if zoom_factor != 1.0:
# Calculate new dimensions
new_width = int(original_width * zoom_factor)
new_height = int(original_height * zoom_factor)
# Resize the image
resized_image = pil_image.resize((new_width, new_height), Image.LANCZOS)
else:
resized_image = pil_image
# Display the image
st.image(resized_image, use_column_width=True)
# Add download option
if allow_download:
download_link = get_image_download_link(pil_image)
if download_link:
st.markdown(download_link, unsafe_allow_html=True)
return {
"success": True,
"zoom_factor": zoom_factor,
"image_dimensions": (original_width, original_height),
"displayed_dimensions": resized_image.size
}
except Exception as e:
logger.error(f"Error displaying image with controls: {e}")
return {"success": False, "message": str(e)}
def handle_image_viewer(image_data,
title: str = "Image Viewer",
description: str = None,
allow_zoom: bool = True,
allow_reset: bool = True,
allow_download: bool = True) -> Dict[str, Any]:
"""
Complete image viewer with all controls and options.
Args:
image_data: PIL Image or numpy array
title: Title for the image viewer
description: Optional description text
allow_zoom: Whether to include zoom controls
allow_reset: Whether to include reset button
allow_download: Whether to include download option
Returns:
Dict containing status and control information
"""
st.subheader(title)
if description:
st.markdown(description)
# Create container for the viewer
viewer_container = st.container()
with viewer_container:
# Create a two-column layout for controls and image
control_col, image_col = st.columns([1, 3])
with control_col:
st.markdown("### Controls")
# Zoom controls
zoom_factor = 1.0
if allow_zoom:
zoom_factor = st.slider(
"Zoom",
min_value=0.5,
max_value=3.0,
value=1.0,
step=0.1,
help="Adjust the zoom level"
)
# Reset button
if allow_reset:
if st.button("Reset View", key="reset_view"):
zoom_factor = 1.0
# Reset any other relevant state
# Add some spacing
st.markdown("<br>", unsafe_allow_html=True)
# Download option
if allow_download and image_data is not None:
download_link = get_image_download_link(image_data)
if download_link:
st.markdown("### Download")
st.markdown(download_link, unsafe_allow_html=True)
with image_col:
if image_data is not None:
# Process the image based on controls
try:
# Convert to PIL if numpy array
if isinstance(image_data, np.ndarray):
pil_image = Image.fromarray(image_data)
else:
pil_image = image_data
# Apply zoom if needed
if zoom_factor != 1.0:
original_width, original_height = pil_image.size
new_width = int(original_width * zoom_factor)
new_height = int(original_height * zoom_factor)
displayed_image = pil_image.resize((new_width, new_height), Image.LANCZOS)
else:
displayed_image = pil_image
# Display the image
st.image(displayed_image, use_column_width=True)
return {
"success": True,
"zoom_factor": zoom_factor,
"image_displayed": True
}
except Exception as e:
st.error(f"Error displaying image: {str(e)}")
logger.error(f"Error in image viewer: {e}")
return {"success": False, "message": str(e)}
else:
st.info("No image to display. Please upload an image first.")
return {"success": False, "message": "No image data provided"}
def create_image_tabs(original_image, processed_image=None):
"""
Create tabs to display original and processed images side by side.
Args:
original_image: The original image (PIL or numpy array)
processed_image: The processed image (PIL or numpy array), optional
Returns:
Dict with status information
"""
# Create tabs
if processed_image is not None:
tab1, tab2 = st.tabs(["Original Image", "Processed Image"])
with tab1:
result1 = display_image_with_controls(
original_image,
title="Original Image",
allow_zoom=True,
allow_download=True
)
with tab2:
result2 = display_image_with_controls(
processed_image,
title="Processed Image",
allow_zoom=True,
allow_download=True
)
return {
"success": result1.get("success", False) and result2.get("success", False),
"tabs_created": True
}
else:
# Only original image available
result = display_image_with_controls(
original_image,
title="Uploaded Image",
allow_zoom=True,
allow_download=True
)
return {
"success": result.get("success", False),
"tabs_created": False
}