Spaces:
Sleeping
Sleeping
| """ | |
| All code contributed to Exifa.net is © 2024 by Sahir Maharaj. | |
| The content is licensed under the Creative Commons Attribution 4.0 International License. | |
| This allows for sharing and adaptation, provided appropriate credit is given, and any changes made are indicated. | |
| When using the code from Exifa.net, please credit as follows: "Code sourced from Exifa.net, authored by Sahir Maharaj, 2024." | |
| For reporting bugs, requesting features, or further inquiries, please reach out to Sahir Maharaj at sahir@sahirmaharaj.com. | |
| Connect with Sahir Maharaj on LinkedIn for updates and potential collaborations: https://www.linkedin.com/in/sahir-maharaj/ | |
| Hire Sahir Maharaj: https://topmate.io/sahirmaharaj/362667 | |
| """ | |
| import streamlit as st | |
| import replicate | |
| import os | |
| import pdfplumber | |
| from docx import Document | |
| import pandas as pd | |
| from io import BytesIO | |
| from transformers import AutoTokenizer | |
| import exifread | |
| import requests | |
| from PIL import Image | |
| import numpy as np | |
| import plotly.express as px | |
| import matplotlib.colors as mcolors | |
| import plotly.graph_objs as go | |
| import streamlit.components.v1 as components | |
| import random | |
| config = { | |
| "toImageButtonOptions": { | |
| "format": "png", | |
| "filename": "custom_image", | |
| "height": 720, | |
| "width": 480, | |
| "scale": 6, | |
| } | |
| } | |
| icons = { | |
| "assistant": "https://raw.githubusercontent.com/sahirmaharaj/exifa/2f685de7dffb583f2b2a89cb8ee8bc27bf5b1a40/img/assistant-done.svg", | |
| "user": "https://raw.githubusercontent.com/sahirmaharaj/exifa/2f685de7dffb583f2b2a89cb8ee8bc27bf5b1a40/img/user-done.svg", | |
| } | |
| particles_js = """<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Particles.js</title> | |
| <style> | |
| #particles-js { | |
| position: fixed; | |
| width: 100vw; | |
| height: 100vh; | |
| top: 0; | |
| left: 0; | |
| z-index: -1; /* Send the animation to the back */ | |
| } | |
| .content { | |
| position: relative; | |
| z-index: 1; | |
| color: white; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="particles-js"></div> | |
| <div class="content"> | |
| <!-- Placeholder for Streamlit content --> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script> | |
| <script> | |
| particlesJS("particles-js", { | |
| "particles": { | |
| "number": { | |
| "value": 300, | |
| "density": { | |
| "enable": true, | |
| "value_area": 800 | |
| } | |
| }, | |
| "color": { | |
| "value": "#ffffff" | |
| }, | |
| "shape": { | |
| "type": "circle", | |
| "stroke": { | |
| "width": 0, | |
| "color": "#000000" | |
| }, | |
| "polygon": { | |
| "nb_sides": 5 | |
| }, | |
| "image": { | |
| "src": "img/github.svg", | |
| "width": 100, | |
| "height": 100 | |
| } | |
| }, | |
| "opacity": { | |
| "value": 0.5, | |
| "random": false, | |
| "anim": { | |
| "enable": false, | |
| "speed": 1, | |
| "opacity_min": 0.2, | |
| "sync": false | |
| } | |
| }, | |
| "size": { | |
| "value": 2, | |
| "random": true, | |
| "anim": { | |
| "enable": false, | |
| "speed": 40, | |
| "size_min": 0.1, | |
| "sync": false | |
| } | |
| }, | |
| "line_linked": { | |
| "enable": true, | |
| "distance": 100, | |
| "color": "#ffffff", | |
| "opacity": 0.22, | |
| "width": 1 | |
| }, | |
| "move": { | |
| "enable": true, | |
| "speed": 0.2, | |
| "direction": "none", | |
| "random": false, | |
| "straight": false, | |
| "out_mode": "out", | |
| "bounce": true, | |
| "attract": { | |
| "enable": false, | |
| "rotateX": 600, | |
| "rotateY": 1200 | |
| } | |
| } | |
| }, | |
| "interactivity": { | |
| "detect_on": "canvas", | |
| "events": { | |
| "onhover": { | |
| "enable": true, | |
| "mode": "grab" | |
| }, | |
| "onclick": { | |
| "enable": true, | |
| "mode": "repulse" | |
| }, | |
| "resize": true | |
| }, | |
| "modes": { | |
| "grab": { | |
| "distance": 100, | |
| "line_linked": { | |
| "opacity": 1 | |
| } | |
| }, | |
| "bubble": { | |
| "distance": 400, | |
| "size": 2, | |
| "duration": 2, | |
| "opacity": 0.5, | |
| "speed": 1 | |
| }, | |
| "repulse": { | |
| "distance": 200, | |
| "duration": 0.4 | |
| }, | |
| "push": { | |
| "particles_nb": 2 | |
| }, | |
| "remove": { | |
| "particles_nb": 3 | |
| } | |
| } | |
| }, | |
| "retina_detect": true | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| st.set_page_config(page_title="Exifa.net", page_icon="✨", layout="wide") | |
| welcome_messages = [ | |
| "Hello! I'm Exifa, an AI assistant designed to make image metadata meaningful. Ask me anything!", | |
| "Hi! I'm Exifa, an AI-powered assistant for extracting and explaining EXIF data. How can I help you today?", | |
| "Hey! I'm Exifa, your AI-powered guide to understanding the metadata in your images. What would you like to explore?", | |
| "Hi there! I'm Exifa, an AI-powered tool built to help you make sense of your image metadata. How can I help you today?", | |
| "Hello! I'm Exifa, an AI-driven tool designed to help you understand your images' metadata. What can I do for you?", | |
| "Hi! I'm Exifa, an AI-driven assistant designed to make EXIF data easy to understand. How can I help you today?", | |
| "Welcome! I'm Exifa, an intelligent AI-powered tool for extracting and explaining EXIF data. How can I assist you today?", | |
| "Hello! I'm Exifa, your AI-powered guide for understanding image metadata. Ask me anything!", | |
| "Hi! I'm Exifa, an intelligent AI assistant ready to help you understand your images' metadata. What would you like to explore?", | |
| "Hey! I'm Exifa, an AI assistant for extracting and explaining EXIF data. How can I help you today?", | |
| ] | |
| message = random.choice(welcome_messages) | |
| if "messages" not in st.session_state: | |
| st.session_state["messages"] = [{"role": "assistant", "content": message}] | |
| if "exif_df" not in st.session_state: | |
| st.session_state["exif_df"] = pd.DataFrame() | |
| if "url_exif_df" not in st.session_state: | |
| st.session_state["url_exif_df"] = pd.DataFrame() | |
| if "show_expanders" not in st.session_state: | |
| st.session_state.show_expanders = True | |
| if "reset_trigger" not in st.session_state: | |
| st.session_state.reset_trigger = False | |
| if "uploaded_files" not in st.session_state: | |
| st.session_state["uploaded_files"] = None | |
| if "image_url" not in st.session_state: | |
| st.session_state["image_url"] = "" | |
| if "follow_up" not in st.session_state: | |
| st.session_state.follow_up = False | |
| if "show_animation" not in st.session_state: | |
| st.session_state.show_animation = True | |
| def clear_url(): | |
| st.session_state["image_url"] = "" | |
| def clear_files(): | |
| st.session_state["uploaded_files"] = None | |
| st.session_state["file_uploader_key"] = not st.session_state.get( | |
| "file_uploader_key", False | |
| ) | |
| def download_image(data): | |
| st.download_button( | |
| label="⇩ Download Image", | |
| data=data, | |
| file_name="image_no_exif.jpg", | |
| mime="image/jpeg", | |
| ) | |
| def clear_chat_history(): | |
| st.session_state.reset_trigger = not st.session_state.reset_trigger | |
| st.session_state.show_expanders = True | |
| st.session_state.show_animation = True | |
| st.session_state.messages = [{"role": "assistant", "content": message}] | |
| st.session_state["exif_df"] = pd.DataFrame() | |
| st.session_state["url_exif_df"] = pd.DataFrame() | |
| uploaded_files = "" | |
| if "uploaded_files" in st.session_state: | |
| del st.session_state["uploaded_files"] | |
| if "image_url" in st.session_state: | |
| st.session_state["image_url"] = "" | |
| st.cache_data.clear() | |
| st.success("Chat History Cleared!") | |
| def clear_exif_data(image_input): | |
| if isinstance(image_input, BytesIO): | |
| image_input.seek(0) | |
| image = Image.open(image_input) | |
| elif isinstance(image_input, Image.Image): | |
| image = image_input | |
| else: | |
| raise ValueError("Unsupported image input type") | |
| data = list(image.getdata()) | |
| image_without_exif = Image.new(image.mode, image.size) | |
| image_without_exif.putdata(data) | |
| buffered = BytesIO() | |
| image_without_exif.save(buffered, format="JPEG", quality=100, optimize=True) | |
| buffered.seek(0) | |
| return buffered.getvalue() | |
| with st.sidebar: | |
| image_url = ( | |
| "https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/Exifa.gif" | |
| ) | |
| st.markdown( | |
| f""" | |
| <div style='display: flex; align-items: center;'> | |
| <img src='{image_url}' style='width: 50px; height: 50px; margin-right: 30px;'> | |
| <h1 style='margin: 0;'>Exifa.net</h1> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| expander = st.expander("🗀 File Input") | |
| with expander: | |
| image_url = st.text_input( | |
| "Enter image URL for EXIF analysis:", | |
| key="image_url", | |
| on_change=clear_files, | |
| value=st.session_state.image_url, | |
| ) | |
| file_uploader_key = "file_uploader_{}".format( | |
| st.session_state.get("file_uploader_key", False) | |
| ) | |
| uploaded_files = st.file_uploader( | |
| "Upload local files:", | |
| type=["txt", "pdf", "docx", "csv", "jpg", "png", "jpeg"], | |
| key=file_uploader_key, | |
| on_change=clear_url, | |
| accept_multiple_files=True, | |
| ) | |
| if uploaded_files is not None: | |
| st.session_state["uploaded_files"] = uploaded_files | |
| expander = st.expander("⚒ Model Configuration") | |
| with expander: | |
| if "REPLICATE_API_TOKEN" in st.secrets: | |
| replicate_api = st.secrets["REPLICATE_API_TOKEN"] | |
| else: | |
| replicate_api = st.text_input("Enter Replicate API token:", type="password") | |
| if not (replicate_api.startswith("r8_") and len(replicate_api) == 40): | |
| st.warning("Please enter your Replicate API token.", icon="⚠️") | |
| st.markdown( | |
| "**Don't have an API token?** Head over to [Replicate](https://replicate.com/account/api-tokens) to sign up for one." | |
| ) | |
| os.environ["REPLICATE_API_TOKEN"] = replicate_api | |
| st.subheader("Adjust model parameters") | |
| temperature = st.slider( | |
| "Temperature", min_value=0.01, max_value=5.0, value=0.3, step=0.01 | |
| ) | |
| top_p = st.slider("Top P", min_value=0.01, max_value=1.0, value=0.2, step=0.01) | |
| max_new_tokens = st.number_input( | |
| "Max New Tokens", min_value=1, max_value=1024, value=512 | |
| ) | |
| min_new_tokens = st.number_input( | |
| "Min New Tokens", min_value=0, max_value=512, value=0 | |
| ) | |
| presence_penalty = st.slider( | |
| "Presence Penalty", min_value=0.0, max_value=2.0, value=1.15, step=0.05 | |
| ) | |
| frequency_penalty = st.slider( | |
| "Frequency Penalty", min_value=0.0, max_value=2.0, value=0.2, step=0.05 | |
| ) | |
| stop_sequences = st.text_area("Stop Sequences", value="<|im_end|>", height=100) | |
| if uploaded_files and not st.session_state["exif_df"].empty: | |
| with st.expander("🗏 EXIF Details"): | |
| st.dataframe(st.session_state["exif_df"]) | |
| if image_url and not st.session_state["url_exif_df"].empty: | |
| with st.expander("🗏 EXIF Details"): | |
| st.dataframe(st.session_state["url_exif_df"]) | |
| base_prompt = """ | |
| You are an expert EXIF Analyser. The user will provide an image file and you will explain the file EXIF in verbose detail. | |
| Pay careful attention to the data of the EXIF image and create a profile for the user who took this image. | |
| 1. Make inferences on things like location, budget, experience, etc. (2 paragraphs) | |
| 2. Make as many inferences as possible about the exif data in the next 3 paragraphs. | |
| 3. Please follow this format, style, pacing and structure. | |
| 4. In addition to the content above, provide 1 more paragraph about the users financial standing based on the equipment they are using and estimate their experience. | |
| DO NOT skip any steps. | |
| FORMAT THE RESULT IN MULTIPLE PARAGRAPHS | |
| Do not keep talking and rambling on - Get to the point. | |
| """ | |
| if uploaded_files: | |
| for uploaded_file in uploaded_files: | |
| if uploaded_file.type == "application/pdf": | |
| with pdfplumber.open(uploaded_file) as pdf: | |
| pages = [page.extract_text() for page in pdf.pages] | |
| file_text = "\n".join(pages) if pages else "" | |
| elif uploaded_file.type == "text/plain": | |
| file_text = str(uploaded_file.read(), "utf-8") | |
| elif ( | |
| uploaded_file.type | |
| == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" | |
| ): | |
| doc = Document(uploaded_file) | |
| file_text = "\n".join([para.text for para in doc.paragraphs]) | |
| elif uploaded_file.type == "text/csv": | |
| df = pd.read_csv(uploaded_file) | |
| file_text = df.to_string(index=False) | |
| elif uploaded_file.type in ["image/jpeg", "image/png", "image/jpg"]: | |
| import tempfile | |
| with tempfile.NamedTemporaryFile(delete=False) as temp: | |
| temp.write(uploaded_file.read()) | |
| temp.flush() | |
| temp.close() | |
| with open(temp.name, "rb") as file: | |
| tags = exifread.process_file(file) | |
| exif_data = {} | |
| for tag in tags.keys(): | |
| if tag not in [ | |
| "JPEGThumbnail", | |
| "TIFFThumbnail", | |
| "Filename", | |
| "EXIF MakerNote", | |
| ]: | |
| exif_data[tag] = str(tags[tag]) | |
| df = pd.DataFrame(exif_data, index=[0]) | |
| df.insert(loc=0, column="Image Feature", value=["Value"] * len(df)) | |
| df = df.transpose() | |
| df.columns = df.iloc[0] | |
| df = df.iloc[1:] | |
| st.session_state["exif_df"] = df | |
| file_text = "\n".join( | |
| [ | |
| f"{tag}: {tags[tag]}" | |
| for tag in tags.keys() | |
| if tag | |
| not in ( | |
| "JPEGThumbnail", | |
| "TIFFThumbnail", | |
| "Filename", | |
| "EXIF MakerNote", | |
| ) | |
| ] | |
| ) | |
| os.unlink(temp.name) | |
| base_prompt += "\n" + file_text | |
| if image_url: | |
| try: | |
| response = requests.head(image_url) | |
| if response.headers["Content-Type"] in [ | |
| "image/jpeg", | |
| "image/png", | |
| "image/jpg", | |
| ]: | |
| response = requests.get(image_url) | |
| response.raise_for_status() | |
| image_data = BytesIO(response.content) | |
| image = Image.open(image_data) | |
| image.load() | |
| tags = exifread.process_file(image_data) | |
| exif_data = {} | |
| for tag in tags.keys(): | |
| if tag not in [ | |
| "JPEGThumbnail", | |
| "TIFFThumbnail", | |
| "Filename", | |
| "EXIF MakerNote", | |
| ]: | |
| exif_data[tag] = str(tags[tag]) | |
| df = pd.DataFrame(exif_data, index=[0]) | |
| df.insert(loc=0, column="Image Feature", value=["Value"] * len(df)) | |
| df = df.transpose() | |
| df.columns = df.iloc[0] | |
| df = df.iloc[1:] | |
| st.session_state["url_exif_df"] = df | |
| file_text = "\n".join( | |
| [ | |
| f"{tag}: {tags[tag]}" | |
| for tag in tags.keys() | |
| if tag | |
| not in ( | |
| "JPEGThumbnail", | |
| "TIFFThumbnail", | |
| "Filename", | |
| "EXIF MakerNote", | |
| ) | |
| ] | |
| ) | |
| base_prompt += "\n" + file_text | |
| else: | |
| pass | |
| except requests.RequestException: | |
| pass | |
| def load_image(file): | |
| if isinstance(file, str): | |
| response = requests.get(file) | |
| response.raise_for_status() | |
| return Image.open(BytesIO(response.content)) | |
| elif isinstance(file, bytes): | |
| return Image.open(BytesIO(file)) | |
| else: | |
| return Image.open(file) | |
| uploaded_file = image | |
| with st.expander("⛆ RGB Channel"): | |
| def get_channel_image(image, channels): | |
| data = np.array(image) | |
| channel_data = np.zeros_like(data) | |
| for channel in channels: | |
| channel_data[:, :, channel] = data[:, :, channel] | |
| return Image.fromarray(channel_data) | |
| channels = st.multiselect( | |
| "Select channels:", | |
| ["Red", "Green", "Blue"], | |
| default=["Red", "Green", "Blue"], | |
| ) | |
| if channels: | |
| channel_indices = [ | |
| 0 if channel == "Red" else 1 if channel == "Green" else 2 | |
| for channel in channels | |
| ] | |
| combined_image = get_channel_image(image, channel_indices) | |
| st.image(combined_image, use_column_width=True) | |
| else: | |
| st.image(image, use_column_width=True) | |
| with st.expander("〽 HSV Distribution"): | |
| def get_hsv_histogram(image): | |
| hsv_image = image.convert("HSV") | |
| data = np.array(hsv_image) | |
| hue_hist, _ = np.histogram(data[:, :, 0], bins=256, range=(0, 256)) | |
| saturation_hist, _ = np.histogram( | |
| data[:, :, 1], bins=256, range=(0, 256) | |
| ) | |
| value_hist, _ = np.histogram(data[:, :, 2], bins=256, range=(0, 256)) | |
| histogram_df = pd.DataFrame( | |
| { | |
| "Hue": hue_hist, | |
| "Saturation": saturation_hist, | |
| "Value": value_hist, | |
| } | |
| ) | |
| return histogram_df | |
| hsv_histogram_df = get_hsv_histogram(image) | |
| st.line_chart(hsv_histogram_df) | |
| with st.expander("☄ Color Distribution"): | |
| if image_url: | |
| image = load_image(image_url) | |
| if image: | |
| def color_distribution_sunburst(data): | |
| data = np.array(data) | |
| red, green, blue = data[:, :, 0], data[:, :, 1], data[:, :, 2] | |
| color_intensity = {"color": [], "intensity": [], "count": []} | |
| for name, channel in zip( | |
| ["Red", "Green", "Blue"], [red, green, blue] | |
| ): | |
| unique, counts = np.unique(channel, return_counts=True) | |
| color_intensity["color"].extend([name] * len(unique)) | |
| color_intensity["intensity"].extend(unique) | |
| color_intensity["count"].extend(counts) | |
| df = pd.DataFrame(color_intensity) | |
| fig = px.sunburst( | |
| df, | |
| path=["color", "intensity"], | |
| values="count", | |
| color="color", | |
| color_discrete_map={ | |
| "Red": "#ff6666", | |
| "Green": "#85e085", | |
| "Blue": "#6666ff", | |
| }, | |
| ) | |
| return fig | |
| fig = color_distribution_sunburst(image) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with st.expander("🕸 3D Color Space"): | |
| def plot_3d_color_space(data, skip_factor): | |
| sample = data[::skip_factor, ::skip_factor].reshape(-1, 3) | |
| normalized_colors = sample / 255.0 | |
| trace = go.Scatter3d( | |
| x=sample[:, 0], | |
| y=sample[:, 1], | |
| z=sample[:, 2], | |
| mode="markers", | |
| marker=dict( | |
| size=5, | |
| color=["rgb({},{},{})".format(r, g, b) for r, g, b in sample], | |
| opacity=0.8, | |
| ), | |
| ) | |
| layout = go.Layout( | |
| scene=dict( | |
| xaxis=dict(title="Red"), | |
| yaxis=dict(title="Green"), | |
| zaxis=dict(title="Blue"), | |
| camera=dict(eye=dict(x=1.25, y=1.25, z=1.25)), | |
| ), | |
| margin=dict(l=0, r=0, b=0, t=30), | |
| ) | |
| fig = go.Figure(data=[trace], layout=layout) | |
| return fig | |
| skip_factor = 8 | |
| if isinstance(uploaded_file, Image.Image): | |
| data = np.array(uploaded_file) | |
| else: | |
| data = np.array(Image.open(uploaded_file)) | |
| fig = plot_3d_color_space(data, skip_factor) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("𖦹 Pixel Density Polar"): | |
| def pixel_density_polar_plot(image): | |
| image_data = np.array(image) | |
| hsv_data = mcolors.rgb_to_hsv(image_data / 255.0) | |
| hue = hsv_data[:, :, 0].flatten() | |
| hist, bins = np.histogram(hue, bins=360, range=(0, 1)) | |
| theta = np.linspace(0, 360, len(hist), endpoint=False) | |
| fig = px.bar_polar( | |
| r=hist, | |
| theta=theta, | |
| template="seaborn", | |
| color_discrete_sequence=["red"], | |
| ) | |
| fig.update_traces(marker=dict(line=dict(color="red", width=1))) | |
| fig.update_layout() | |
| return fig | |
| if uploaded_file is not None: | |
| if isinstance(uploaded_file, Image.Image): | |
| image = uploaded_file | |
| else: | |
| image = Image.open(uploaded_file) | |
| fig = pixel_density_polar_plot(image) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("ᨒ 3D Surface (Color Intensities)"): | |
| def surface_plot_image_intensity(data): | |
| intensity = np.mean(data, axis=2) | |
| sample_size = int(intensity.shape[0] * 0.35) | |
| intensity_sample = intensity[:sample_size, :sample_size] | |
| fig = go.Figure( | |
| data=[go.Surface(z=intensity_sample, colorscale="Viridis")] | |
| ) | |
| fig.update_layout(autosize=True) | |
| return fig | |
| if isinstance(uploaded_file, Image.Image): | |
| data = np.array(uploaded_file) | |
| else: | |
| data = np.array(Image.open(uploaded_file)) | |
| fig = surface_plot_image_intensity(data) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("🖌 Color Palette"): | |
| def extract_color_palette(image, num_colors=6): | |
| image = image.resize((100, 100)) | |
| result = image.quantize(colors=num_colors) | |
| palette = result.getpalette() | |
| color_counts = result.getcolors() | |
| colors = [palette[i * 3 : (i + 1) * 3] for i in range(num_colors)] | |
| counts = [ | |
| count | |
| for count, _ in sorted( | |
| color_counts, reverse=True, key=lambda x: x[0] | |
| ) | |
| ] | |
| return colors, counts | |
| def plot_color_palette(colors, counts): | |
| fig = go.Figure() | |
| for i, (color, count) in enumerate(zip(colors, counts)): | |
| hex_color = "#%02x%02x%02x" % tuple(color) | |
| fig.add_trace( | |
| go.Bar( | |
| x=[1], | |
| y=[hex_color], | |
| orientation="h", | |
| marker=dict(color=hex_color), | |
| hoverinfo="text", | |
| hovertext=f"<b>HEX:</b> {hex_color}<br><b>Count:</b> {count}", | |
| name="", | |
| ) | |
| ) | |
| fig.update_layout( | |
| xaxis=dict(showticklabels=False), | |
| yaxis=dict(showticklabels=True), | |
| showlegend=False, | |
| template="plotly_dark", | |
| height=400, | |
| ) | |
| return fig | |
| num_colors = st.slider("Number of Colors", 2, 10, 6) | |
| if isinstance(uploaded_file, Image.Image): | |
| image = uploaded_file.convert("RGB") | |
| else: | |
| image = Image.open(uploaded_file).convert("RGB") | |
| colors, counts = extract_color_palette(image, num_colors) | |
| fig = plot_color_palette(colors, counts) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| if uploaded_file is not None: | |
| col1, col2 = st.columns(2) | |
| clean_img = clear_exif_data(image) | |
| with col1: | |
| st.button("🗑 Clear Chat History", on_click=clear_chat_history) | |
| with col2: | |
| download_image(clean_img) | |
| st.session_state.reset_trigger = True | |
| if st.session_state.show_expanders: | |
| if uploaded_files and not st.session_state["exif_df"].empty: | |
| with st.expander("⛆ RGB Channel"): | |
| for uploaded_file in uploaded_files: | |
| if uploaded_file.type in ["image/jpeg", "image/png", "image/jpg"]: | |
| def load_image(image_file): | |
| return Image.open(image_file) | |
| image = load_image(uploaded_file) | |
| def get_channel_image(image, channels): | |
| data = np.array(image) | |
| channel_data = np.zeros_like(data) | |
| for channel in channels: | |
| channel_data[:, :, channel] = data[:, :, channel] | |
| return Image.fromarray(channel_data) | |
| channels = st.multiselect( | |
| "Select channels:", | |
| ["Red", "Green", "Blue"], | |
| default=["Red", "Green", "Blue"], | |
| ) | |
| if channels: | |
| channel_indices = [ | |
| 0 if channel == "Red" else 1 if channel == "Green" else 2 | |
| for channel in channels | |
| ] | |
| combined_image = get_channel_image(image, channel_indices) | |
| st.image(combined_image, use_column_width=True) | |
| else: | |
| st.image(image, use_column_width=True) | |
| with st.expander("〽 HSV Distribution"): | |
| def get_hsv_histogram(image): | |
| hsv_image = image.convert("HSV") | |
| data = np.array(hsv_image) | |
| hue_hist, _ = np.histogram(data[:, :, 0], bins=256, range=(0, 256)) | |
| saturation_hist, _ = np.histogram( | |
| data[:, :, 1], bins=256, range=(0, 256) | |
| ) | |
| value_hist, _ = np.histogram( | |
| data[:, :, 2], bins=256, range=(0, 256) | |
| ) | |
| histogram_df = pd.DataFrame( | |
| { | |
| "Hue": hue_hist, | |
| "Saturation": saturation_hist, | |
| "Value": value_hist, | |
| } | |
| ) | |
| return histogram_df | |
| hsv_histogram_df = get_hsv_histogram(image) | |
| st.line_chart(hsv_histogram_df) | |
| with st.expander("☄ Color Distribution"): | |
| def color_distribution_sunburst(data): | |
| data = np.array(data) | |
| red, green, blue = data[:, :, 0], data[:, :, 1], data[:, :, 2] | |
| color_intensity = {"color": [], "intensity": [], "count": []} | |
| for name, channel in zip( | |
| ["Red", "Green", "Blue"], [red, green, blue] | |
| ): | |
| unique, counts = np.unique(channel, return_counts=True) | |
| color_intensity["color"].extend([name] * len(unique)) | |
| color_intensity["intensity"].extend(unique) | |
| color_intensity["count"].extend(counts) | |
| df = pd.DataFrame(color_intensity) | |
| fig = px.sunburst( | |
| df, | |
| path=["color", "intensity"], | |
| values="count", | |
| color="color", | |
| color_discrete_map={ | |
| "Red": "#ff6666", | |
| "Green": "#85e085", | |
| "Blue": "#6666ff", | |
| }, | |
| ) | |
| return fig | |
| image = load_image(uploaded_file) | |
| fig = color_distribution_sunburst(image) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("🕸 3D Color Space"): | |
| def plot_3d_color_space(data, skip_factor): | |
| sample = data[::skip_factor, ::skip_factor].reshape(-1, 3) | |
| normalized_colors = sample / 255.0 | |
| trace = go.Scatter3d( | |
| x=sample[:, 0], | |
| y=sample[:, 1], | |
| z=sample[:, 2], | |
| mode="markers", | |
| marker=dict( | |
| size=5, | |
| color=[ | |
| "rgb({},{},{})".format(r, g, b) for r, g, b in sample | |
| ], | |
| opacity=0.8, | |
| ), | |
| ) | |
| layout = go.Layout( | |
| scene=dict( | |
| xaxis=dict(title="Red"), | |
| yaxis=dict(title="Green"), | |
| zaxis=dict(title="Blue"), | |
| camera=dict(eye=dict(x=1.25, y=1.25, z=1.25)), | |
| ), | |
| margin=dict(l=0, r=0, b=0, t=30), | |
| ) | |
| fig = go.Figure(data=[trace], layout=layout) | |
| return fig | |
| skip_factor = 8 | |
| data = np.array(Image.open(uploaded_file)) | |
| fig = plot_3d_color_space(data, skip_factor) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("𖦹 Pixel Density Polar"): | |
| def pixel_density_polar_plot(data): | |
| image_data = np.array(Image.open(data)) | |
| hsv_data = mcolors.rgb_to_hsv(image_data / 255.0) | |
| hue = hsv_data[:, :, 0].flatten() | |
| hist, bins = np.histogram(hue, bins=360, range=(0, 1)) | |
| theta = np.linspace(0, 360, len(hist), endpoint=False) | |
| fig = px.bar_polar( | |
| r=hist, | |
| theta=theta, | |
| template="seaborn", | |
| color_discrete_sequence=["red"], | |
| ) | |
| fig.update_traces(marker=dict(line=dict(color="red", width=1))) | |
| fig.update_layout() | |
| return fig | |
| if uploaded_file is not None: | |
| fig = pixel_density_polar_plot(uploaded_file) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("ᨒ 3D Surface (Color Intensities)"): | |
| def surface_plot_image_intensity(data): | |
| intensity = np.mean(data, axis=2) | |
| sample_size = int(intensity.shape[0] * 0.35) | |
| intensity_sample = intensity[:sample_size, :sample_size] | |
| fig = go.Figure( | |
| data=[go.Surface(z=intensity_sample, colorscale="Viridis")] | |
| ) | |
| fig.update_layout(autosize=True) | |
| return fig | |
| data = np.array(Image.open(uploaded_file)) | |
| fig = surface_plot_image_intensity(data) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| with st.expander("🖌 Color Palette"): | |
| def extract_color_palette(image, num_colors=6): | |
| image = image.resize((100, 100)) | |
| result = image.quantize(colors=num_colors) | |
| palette = result.getpalette() | |
| color_counts = result.getcolors() | |
| colors = [palette[i * 3 : (i + 1) * 3] for i in range(num_colors)] | |
| counts = [ | |
| count | |
| for count, _ in sorted( | |
| color_counts, reverse=True, key=lambda x: x[0] | |
| ) | |
| ] | |
| return colors, counts | |
| def plot_color_palette(colors, counts): | |
| fig = go.Figure() | |
| for i, (color, count) in enumerate(zip(colors, counts)): | |
| hex_color = "#%02x%02x%02x" % tuple(color) | |
| fig.add_trace( | |
| go.Bar( | |
| x=[1], | |
| y=[hex_color], | |
| orientation="h", | |
| marker=dict(color=hex_color), | |
| hoverinfo="text", | |
| hovertext=f"<b>HEX:</b> {hex_color}<br><b>Count:</b> {count}", | |
| name="", | |
| ) | |
| ) | |
| fig.update_layout( | |
| xaxis=dict(showticklabels=False), | |
| yaxis=dict(showticklabels=True), | |
| showlegend=False, | |
| template="plotly_dark", | |
| height=400, | |
| ) | |
| return fig | |
| num_colors = st.slider("Number of Colors", 2, 10, 6) | |
| image = Image.open(uploaded_file).convert("RGB") | |
| colors, counts = extract_color_palette(image, num_colors) | |
| fig = plot_color_palette(colors, counts) | |
| st.plotly_chart(fig, use_container_width=True, config=config) | |
| st.session_state.reset_trigger = True | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.button("🗑 Clear Chat History", on_click=clear_chat_history) | |
| with col2: | |
| clear = clear_exif_data(image) | |
| download_image(clear) | |
| def show_video(item): | |
| video_url = "https://www.youtube.com/watch?v=CS7rkWu7LNY" | |
| st.video(video_url, loop=False, autoplay=True, muted=False) | |
| for message in st.session_state.messages: | |
| with st.chat_message(message["role"], avatar=icons[message["role"]]): | |
| st.write(message["content"]) | |
| if message == st.session_state["messages"][0]: | |
| if st.button("How can I use Exifa?"): | |
| show_video("") | |
| st.sidebar.caption( | |
| "Built by [Sahir Maharaj](https://www.linkedin.com/in/sahir-maharaj/). Like this? [Hire me!](https://topmate.io/sahirmaharaj/362667)" | |
| ) | |
| linkedin = "https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/linkedin.gif" | |
| topmate = "https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/topmate.gif" | |
| email = "https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/email.gif" | |
| newsletter = ( | |
| "https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/newsletter.gif" | |
| ) | |
| share = "https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/share.gif" | |
| uptime = "https://uptime.betterstack.com/status-badges/v1/monitor/196o6.svg" | |
| st.sidebar.caption( | |
| f""" | |
| <div style='display: flex; align-items: center;'> | |
| <a href = 'https://www.linkedin.com/in/sahir-maharaj/'><img src='{linkedin}' style='width: 35px; height: 35px; margin-right: 25px;'></a> | |
| <a href = 'https://topmate.io/sahirmaharaj/362667'><img src='{topmate}' style='width: 32px; height: 32px; margin-right: 25px;'></a> | |
| <a href = 'mailto:sahir@sahirmaharaj.com'><img src='{email}' style='width: 28px; height: 28px; margin-right: 25px;'></a> | |
| <a href = 'https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7163516439096733696'><img src='{newsletter}' style='width: 28px; height: 28px; margin-right: 25px;'></a> | |
| <a href = 'https://www.kaggle.com/sahirmaharajj'><img src='{share}' style='width: 28px; height: 28px; margin-right: 25px;'></a> | |
| </div> | |
| <br> | |
| <a href = 'https://exifa.betteruptime.com/'><img src='{uptime}'></a> | |
| <a href="https://www.producthunt.com/posts/exifa-net?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-exifa-net" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=474560&theme=dark" alt="Exifa.net - Your AI assistant for understanding EXIF data | Product Hunt" style="width: 125px; height: 27px;" width="125" height="27" /></a> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def get_tokenizer(): | |
| return AutoTokenizer.from_pretrained("huggyllama/llama-7b") | |
| def get_num_tokens(prompt): | |
| tokenizer = get_tokenizer() | |
| tokens = tokenizer.tokenize(prompt) | |
| return len(tokens) | |
| def generate_arctic_response_follow_up(): | |
| follow_up_response = "" | |
| last_three_messages = st.session_state.messages[-3:] | |
| for message in last_three_messages: | |
| follow_up_response += "\n\n {}".format(message) | |
| prompt = [ | |
| "Please generate one question based on the conversation thus far that the user might ask next. Ensure the question is short, less than 8 words, stays on the topic of EXIF and its importance and dangers, and is formatted with underscores instead of spaces, e.g., What_does_EXIF_mean? Conversation Info = {}. Please generate one question based on the conversation thus far that the user might ask next. Ensure the question is short, less than 8 words, stays on the topic of EXIF and its importance and dangers, and is formatted with underscores instead of spaces".format( | |
| follow_up_response | |
| ) | |
| ] | |
| prompt.append("assistant\n") | |
| prompt_str = "\n".join(prompt) | |
| full_response = [] | |
| for event in replicate.stream( | |
| "snowflake/snowflake-arctic-instruct", | |
| input={ | |
| "prompt": prompt_str, | |
| "prompt_template": r"{prompt}", | |
| "temperature": temperature, | |
| "top_p": top_p, | |
| "max_new_tokens": max_new_tokens, | |
| "min_new_tokens": min_new_tokens, | |
| "presence_penalty": presence_penalty, | |
| "frequency_penalty": frequency_penalty, | |
| "stop_sequences": stop_sequences, | |
| }, | |
| ): | |
| full_response.append(str(event).strip()) | |
| complete_response = "".join(full_response) | |
| return complete_response | |
| def generate_arctic_response(): | |
| prompt = [base_prompt] if base_prompt else [] | |
| for dict_message in st.session_state.messages: | |
| if dict_message["role"] == "user": | |
| prompt.append("user\n" + dict_message["content"]) | |
| else: | |
| prompt.append("assistant\n" + dict_message["content"]) | |
| prompt.append("assistant\n") | |
| prompt_str = "\n".join(prompt) | |
| if get_num_tokens(prompt_str) >= 1000000: | |
| st.error("Conversation length too long. Please keep it under 1000000 tokens.") | |
| st.button( | |
| "🗑 Clear Chat History", | |
| on_click=clear_chat_history, | |
| key="clear_chat_history", | |
| ) | |
| st.stop() | |
| for event in replicate.stream( | |
| "snowflake/snowflake-arctic-instruct", | |
| input={ | |
| "prompt": prompt_str, | |
| "prompt_template": r"{prompt}", | |
| "temperature": temperature, | |
| "top_p": top_p, | |
| "max_new_tokens": max_new_tokens, | |
| "min_new_tokens": min_new_tokens, | |
| "presence_penalty": presence_penalty, | |
| "frequency_penalty": frequency_penalty, | |
| "stop_sequences": stop_sequences, | |
| }, | |
| ): | |
| yield str(event) | |
| def display_question(): | |
| st.session_state.follow_up = True | |
| if prompt := st.chat_input(disabled=not replicate_api): | |
| st.session_state.show_animation = False | |
| st.session_state.messages.append({"role": "user", "content": prompt}) | |
| with st.chat_message( | |
| "user", | |
| avatar="https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/user.gif", | |
| ): | |
| st.write(prompt) | |
| if st.session_state.follow_up: | |
| st.session_state.show_animation = False | |
| unique_key = "chat_input_" + str(hash("Snowflake Arctic is cool")) | |
| complete_question = generate_arctic_response_follow_up() | |
| formatted_question = complete_question.replace("_", " ").strip() | |
| st.session_state.messages.append({"role": "user", "content": formatted_question}) | |
| with st.chat_message( | |
| "user", | |
| avatar="https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/user.gif", | |
| ): | |
| st.write(formatted_question) | |
| st.session_state.follow_up = False | |
| with st.chat_message( | |
| "assistant", | |
| avatar="https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/assistant.gif", | |
| ): | |
| response = generate_arctic_response() | |
| full_response = st.write_stream(response) | |
| message = {"role": "assistant", "content": full_response} | |
| st.session_state.messages.append(message) | |
| full_response_prompt = generate_arctic_response_follow_up() | |
| message_prompt = {"content": full_response_prompt} | |
| st.button( | |
| str(message_prompt["content"]).replace("_", " ").strip(), | |
| on_click=display_question, | |
| ) | |
| if st.session_state.messages[-1]["role"] != "assistant": | |
| st.session_state.show_animation = False | |
| with st.chat_message( | |
| "assistant", | |
| avatar="https://raw.githubusercontent.com/sahirmaharaj/exifa/main/img/assistant.gif", | |
| ): | |
| response = generate_arctic_response() | |
| full_response = st.write_stream(response) | |
| message = {"role": "assistant", "content": full_response} | |
| full_response_prompt = generate_arctic_response_follow_up() | |
| message_prompt = {"content": full_response_prompt} | |
| st.button( | |
| str(message_prompt["content"]).replace("_", " ").strip(), | |
| on_click=display_question, | |
| ) | |
| st.session_state.messages.append(message) | |
| if st.session_state.reset_trigger: | |
| unique_key = "chat_input_" + str(hash("Snowflake Arctic is cool")) | |
| complete_question = generate_arctic_response_follow_up() | |
| st.session_state.show_animation = False | |
| if "has_snowed" not in st.session_state: | |
| st.snow() | |
| st.session_state["has_snowed"] = True | |
| if st.session_state.show_animation: | |
| components.html(particles_js, height=370, scrolling=False) | |