Decoding-CNN / app.py
Niharmahesh's picture
Update app.py
bf47815 verified
import streamlit as st
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Layer
from tensorflow.keras.layers import LeakyReLU, ELU
import imageio
import json
from streamlit_lottie import st_lottie
import tensorflow.keras.activations as activations
from tensorflow.keras.layers import Activation
# Function to load a Lottie animation from a local file
def load_lottiefile(filepath: str):
with open(filepath, "r") as file:
return json.load(file)
# Function to display a Lottie animation in the sidebar
def display_lottiefile_sidebar(lottie_json, unique_key):
st_lottie(lottie_json, speed=1, width=250, height=250, key=unique_key)
# Function to parse and preprocess the uploaded image
def parse_image(uploaded_file):
img = Image.open(uploaded_file)
img = img.resize((64, 64)).convert('L') # Convert to grayscale
img = np.array(img)
img = np.expand_dims(img, axis=0) # Shape (1, 64, 64)
img = np.expand_dims(img, axis=-1) # Shape (1, 64, 64, 1)
img = img / 255.0 # Normalize
return img
# Function to dynamically add an activation layer based on user selection
def add_activation_layer(model, activation_name):
if activation_name == 'leakyrelu':
model.add(LeakyReLU())
elif activation_name == 'elu':
model.add(ELU())
else:
# For 'relu', 'sigmoid', 'tanh', 'softmax', 'selu'
if hasattr(activations, activation_name):
activation_function = getattr(activations, activation_name)
model.add(Activation(activation_function))
else:
raise ValueError(f"Unsupported activation function: {activation_name}")
# Function to create a model and return the feature map
def display_feature_map(img, num_filters, kernel_size, activation, dropout_rate):
model = Sequential()
model.add(Conv2D(num_filters, (kernel_size, kernel_size), input_shape=(64, 64, 1)))
add_activation_layer(model, activation)
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(dropout_rate))
conv2d_output = model.predict(img)
return conv2d_output
def main():
st.title("Convolutional Neural Network Visualizer")
with st.sidebar:
lottie_animation = load_lottiefile("Animation - 1707640885996.json")
display_lottiefile_sidebar(lottie_animation, "lottie_animation_key")
st.sidebar.markdown("""
# Interactive CNN Visualizer Explanation
This interactive tool allows you to visualize how different parameters of a Convolutional Neural Network (CNN) affect the features detected in an input image. Here's a brief overview of the parameters you can adjust:
## Filters/Kernels
- **What they are**: Small matrices of weights that slide over the input image to produce a feature map. Each filter is trained to detect a specific feature in the image, such as edges, corners, or textures.
## Kernel Size
- **What it is**: Determines the size of the filter. For example, a kernel size of 3 means the filter is a 3x3 matrix. The kernel size affects the level of detail the filter can capture. Smaller kernels can capture fine-grained details, while larger kernels capture more abstract features.
## Number of Filters
- **What it is**: Determines the number of feature maps that will be produced by a Conv2D layer. Each filter is trained to detect a different feature, so having more filters allows the model to recognize a wider variety of features.
## Activation Function
- **What it is**: Applied to the feature maps after the convolution operation. It introduces non-linearity into the model, which allows the model to learn more complex patterns. Common choices include ReLU (Rectified Linear Unit), sigmoid, and tanh.
## MaxPooling
- **What it is**: This operation reduces the spatial dimensions (i.e., width and height) of the input by taking the maximum value in each window of a certain size. This helps to make the model invariant to small translations and reduces the computational complexity of the model.
In the interactive visualization you've created, you can adjust the number of filters, the kernel size, and the activation function to see how these parameters affect the features that the model detects in the input image.
""", unsafe_allow_html=True)
# Architecture parameters input
num_filters = st.slider('Number of Filters:', 16, 256, 32)
kernel_size = st.slider('Kernel Size:', 2, 7, 3)
activation = st.selectbox('Activation Function:', ['relu', 'sigmoid', 'tanh', 'leakyrelu', 'elu'])
dropout_rate = st.slider('Dropout Rate:', 0.0, 0.5, 0.25)
# Image upload section
st.subheader("Upload Image")
uploaded_file = st.file_uploader("", type=["png", "jpg", "jpeg"], help="Choose an image to upload")
if uploaded_file is not None:
img = parse_image(uploaded_file)
st.session_state['uploaded_image'] = img
st.success("Image uploaded successfully!")
# Process button to visualize the model
if st.button("Process"):
if 'uploaded_image' in st.session_state:
visualize_activation_overlays(st.session_state['uploaded_image'], num_filters, kernel_size, activation, dropout_rate)
else:
st.error("Please upload an image first.")
# Reset button to clear session state and start over
if st.button("Reset"):
st.session_state.clear()
st.rerun()
def visualize_activation_overlays(img, num_filters, kernel_size, activation, dropout_rate):
conv2d_output = display_feature_map(img, num_filters, kernel_size, activation, dropout_rate)
# Define the new size for the output images
new_size = (800, 800) # Example new size, adjust as needed
# Assuming img was normalized to [0, 1], convert back to [0, 255], RGB, and resize
original_img = np.squeeze(img) * 255.0
original_img = Image.fromarray(np.uint8(original_img)).convert('RGB').resize(new_size)
frames = [] # To hold each frame for the GIF
for i in range(conv2d_output.shape[-1]): # Iterate through each feature map
feature_map = conv2d_output[0, :, :, i]
# Normalize the feature map to enhance visualization
normalized_feature_map = (feature_map - np.min(feature_map)) / (np.max(feature_map) - np.min(feature_map))
# Resize to new output size
resized_feature_map = Image.fromarray(np.uint8(normalized_feature_map * 255)).resize(new_size, Image.NEAREST)
# Create a mask where high activations are marked
mask = np.array(resized_feature_map) > 128 # Threshold to identify high activations
# Create an overlay image with red color in the high activation areas
overlay = np.array(original_img).copy()
overlay[mask] = [255, 0, 0] # Red color for high activation areas
# Convert numpy array back to PIL Image for display
overlay_img = Image.fromarray(overlay)
# Draw filter number or text on the image
draw = ImageDraw.Draw(overlay_img)
# Specify font size and type (default font here, you can specify a path to a .ttf file for custom fonts)
font = ImageFont.load_default()
# Position for the text (bottom left corner in this case)
text_position = (100, new_size[1] - 30)
# Drawing text
draw.text(text_position, f"Filter {i+1}", fill=(255,255,255), font=font)
frames.append(overlay_img)
# Create a GIF from the frames
gif_path = 'activation_overlay_large.gif'
imageio.mimsave(gif_path, frames, fps=1) # Adjust fps as needed
# Display the GIF in Streamlit
st.image(gif_path)
if __name__ == "__main__":
main()