Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- __pycache__/app.cpython-311.pyc +0 -0
- app.py +502 -80
- requirements.txt +5 -3
__pycache__/app.cpython-311.pyc
ADDED
|
Binary file (29 kB). View file
|
|
|
app.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
| 1 |
import io
|
| 2 |
import random
|
| 3 |
from typing import List, Tuple
|
| 4 |
-
|
| 5 |
import aiohttp
|
| 6 |
import panel as pn
|
|
|
|
|
|
|
|
|
|
| 7 |
from PIL import Image
|
| 8 |
from transformers import CLIPModel, CLIPProcessor
|
|
|
|
| 9 |
|
| 10 |
-
|
|
|
|
| 11 |
|
|
|
|
| 12 |
ICON_URLS = {
|
| 13 |
"brand-github": "https://github.com/holoviz/panel",
|
| 14 |
"brand-twitter": "https://twitter.com/Panel_Org",
|
|
@@ -17,15 +22,61 @@ ICON_URLS = {
|
|
| 17 |
"brand-discord": "https://discord.gg/AXRHnJU6sP",
|
| 18 |
}
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
-
pet = random.choice(["cat", "dog"])
|
| 23 |
-
api_url = f"https://api.the{pet}api.com/v1/images/search"
|
| 24 |
-
async with aiohttp.ClientSession() as session:
|
| 25 |
-
async with session.get(api_url) as resp:
|
| 26 |
-
return (await resp.json())[0]["url"]
|
| 27 |
-
|
| 28 |
|
|
|
|
| 29 |
@pn.cache
|
| 30 |
def load_processor_model(
|
| 31 |
processor_name: str, model_name: str
|
|
@@ -34,13 +85,13 @@ def load_processor_model(
|
|
| 34 |
model = CLIPModel.from_pretrained(model_name)
|
| 35 |
return processor, model
|
| 36 |
|
| 37 |
-
|
| 38 |
async def open_image_url(image_url: str) -> Image:
|
| 39 |
async with aiohttp.ClientSession() as session:
|
| 40 |
async with session.get(image_url) as resp:
|
|
|
|
|
|
|
| 41 |
return Image.open(io.BytesIO(await resp.read()))
|
| 42 |
|
| 43 |
-
|
| 44 |
def get_similarity_scores(class_items: List[str], image: Image) -> List[float]:
|
| 45 |
processor, model = load_processor_model(
|
| 46 |
"openai/clip-vit-base-patch32", "openai/clip-vit-base-patch32"
|
|
@@ -48,100 +99,471 @@ def get_similarity_scores(class_items: List[str], image: Image) -> List[float]:
|
|
| 48 |
inputs = processor(
|
| 49 |
text=class_items,
|
| 50 |
images=[image],
|
| 51 |
-
return_tensors="pt",
|
| 52 |
)
|
| 53 |
outputs = model(**inputs)
|
| 54 |
logits_per_image = outputs.logits_per_image
|
| 55 |
class_likelihoods = logits_per_image.softmax(dim=1).detach().numpy()
|
| 56 |
return class_likelihoods[0]
|
| 57 |
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
try:
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
except Exception as e:
|
| 75 |
-
yield f"##### π Something went wrong, please try a different URL!"
|
| 76 |
-
return
|
| 77 |
|
| 78 |
-
|
| 79 |
-
class_likelihoods = get_similarity_scores(class_items, pil_img)
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
|
|
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
sizing_mode="stretch_width",
|
| 91 |
-
bar_color="
|
| 92 |
-
|
| 93 |
-
design=pn.theme.Material,
|
| 94 |
)
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
)
|
| 108 |
-
class_names = pn.widgets.TextInput(
|
| 109 |
-
name="Comma separated class names",
|
| 110 |
-
placeholder="Enter possible class names, e.g. cat, dog",
|
| 111 |
-
value="cat, dog, parrot",
|
| 112 |
)
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
class_names,
|
| 118 |
-
)
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
height=600,
|
| 124 |
-
)
|
| 125 |
|
| 126 |
-
#
|
| 127 |
footer_row = pn.Row(pn.Spacer(), align="center")
|
| 128 |
for icon, url in ICON_URLS.items():
|
| 129 |
-
href_button = pn.widgets.Button(icon=icon, width=
|
| 130 |
href_button.js_on_click(code=f"window.open('{url}')")
|
| 131 |
footer_row.append(href_button)
|
| 132 |
footer_row.append(pn.Spacer())
|
| 133 |
|
| 134 |
-
#
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
)
|
| 140 |
|
| 141 |
-
|
| 142 |
-
pn.template.BootstrapTemplate(
|
| 143 |
-
title=title,
|
| 144 |
-
main=main,
|
| 145 |
-
main_max_width="min(50%, 698px)",
|
| 146 |
-
header_background="#F08080",
|
| 147 |
-
).servable(title=title)
|
|
|
|
| 1 |
import io
|
| 2 |
import random
|
| 3 |
from typing import List, Tuple
|
|
|
|
| 4 |
import aiohttp
|
| 5 |
import panel as pn
|
| 6 |
+
import pandas as pd
|
| 7 |
+
import plotly.express as px
|
| 8 |
+
import numpy as np
|
| 9 |
from PIL import Image
|
| 10 |
from transformers import CLIPModel, CLIPProcessor
|
| 11 |
+
from datetime import datetime, timedelta
|
| 12 |
|
| 13 |
+
# Enable panel extensions
|
| 14 |
+
pn.extension(design="fast", sizing_mode="stretch_width")
|
| 15 |
|
| 16 |
+
# Icons list
|
| 17 |
ICON_URLS = {
|
| 18 |
"brand-github": "https://github.com/holoviz/panel",
|
| 19 |
"brand-twitter": "https://twitter.com/Panel_Org",
|
|
|
|
| 22 |
"brand-discord": "https://discord.gg/AXRHnJU6sP",
|
| 23 |
}
|
| 24 |
|
| 25 |
+
# --- 1. SAMPLE DATA GENERATION ---
|
| 26 |
+
def generate_sample_data():
|
| 27 |
+
np.random.seed(42)
|
| 28 |
+
start_date = datetime(2024, 1, 1)
|
| 29 |
+
dates = [start_date + timedelta(days=i) for i in range(540)] # 1.5 years of daily data
|
| 30 |
+
|
| 31 |
+
regions = ['North', 'East', 'South', 'West']
|
| 32 |
+
categories = ['Electronics', 'Furniture', 'Office Supplies']
|
| 33 |
+
subcategories = {
|
| 34 |
+
'Electronics': ['Phones', 'Laptops', 'Accessories'],
|
| 35 |
+
'Furniture': ['Chairs', 'Tables', 'Bookcases'],
|
| 36 |
+
'Office Supplies': ['Paper', 'Art', 'Binders']
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
data = []
|
| 40 |
+
for date in dates:
|
| 41 |
+
num_orders = np.random.randint(1, 5)
|
| 42 |
+
for _ in range(num_orders):
|
| 43 |
+
region = np.random.choice(regions)
|
| 44 |
+
cat = np.random.choice(categories)
|
| 45 |
+
subcat = np.random.choice(subcategories[cat])
|
| 46 |
+
|
| 47 |
+
if cat == 'Electronics':
|
| 48 |
+
base_sales = np.random.uniform(200, 1500)
|
| 49 |
+
profit_factor = np.random.uniform(0.1, 0.25)
|
| 50 |
+
elif cat == 'Furniture':
|
| 51 |
+
base_sales = np.random.uniform(100, 800)
|
| 52 |
+
profit_factor = np.random.uniform(0.02, 0.15)
|
| 53 |
+
else:
|
| 54 |
+
base_sales = np.random.uniform(10, 150)
|
| 55 |
+
profit_factor = np.random.uniform(0.2, 0.45)
|
| 56 |
+
|
| 57 |
+
if date.month in [11, 12]:
|
| 58 |
+
base_sales *= np.random.uniform(1.2, 1.5)
|
| 59 |
+
|
| 60 |
+
sales = round(base_sales, 2)
|
| 61 |
+
profit = round(sales * profit_factor, 2)
|
| 62 |
+
quantity = np.random.randint(1, 8)
|
| 63 |
+
|
| 64 |
+
data.append({
|
| 65 |
+
'Date': date,
|
| 66 |
+
'Region': region,
|
| 67 |
+
'Category': cat,
|
| 68 |
+
'Sub-Category': subcat,
|
| 69 |
+
'Sales': sales,
|
| 70 |
+
'Profit': profit,
|
| 71 |
+
'Quantity': quantity,
|
| 72 |
+
'Year': date.year
|
| 73 |
+
})
|
| 74 |
+
|
| 75 |
+
return pd.DataFrame(data)
|
| 76 |
|
| 77 |
+
df_data = generate_sample_data()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
# --- 2. CLIP ML CLASSIFIER MODEL CACHING ---
|
| 80 |
@pn.cache
|
| 81 |
def load_processor_model(
|
| 82 |
processor_name: str, model_name: str
|
|
|
|
| 85 |
model = CLIPModel.from_pretrained(model_name)
|
| 86 |
return processor, model
|
| 87 |
|
|
|
|
| 88 |
async def open_image_url(image_url: str) -> Image:
|
| 89 |
async with aiohttp.ClientSession() as session:
|
| 90 |
async with session.get(image_url) as resp:
|
| 91 |
+
if resp.status != 200:
|
| 92 |
+
raise Exception(f"HTTP status {resp.status}")
|
| 93 |
return Image.open(io.BytesIO(await resp.read()))
|
| 94 |
|
|
|
|
| 95 |
def get_similarity_scores(class_items: List[str], image: Image) -> List[float]:
|
| 96 |
processor, model = load_processor_model(
|
| 97 |
"openai/clip-vit-base-patch32", "openai/clip-vit-base-patch32"
|
|
|
|
| 99 |
inputs = processor(
|
| 100 |
text=class_items,
|
| 101 |
images=[image],
|
| 102 |
+
return_tensors="pt",
|
| 103 |
)
|
| 104 |
outputs = model(**inputs)
|
| 105 |
logits_per_image = outputs.logits_per_image
|
| 106 |
class_likelihoods = logits_per_image.softmax(dim=1).detach().numpy()
|
| 107 |
return class_likelihoods[0]
|
| 108 |
|
| 109 |
+
# --- 3. WIDGET DEFINITIONS ---
|
| 110 |
|
| 111 |
+
# Global Sidebar Filters
|
| 112 |
+
date_min = df_data['Date'].min().to_pydatetime()
|
| 113 |
+
date_max = df_data['Date'].max().to_pydatetime()
|
| 114 |
+
|
| 115 |
+
date_range_slider = pn.widgets.DateRangeSlider(
|
| 116 |
+
name='Filter Date Range',
|
| 117 |
+
start=date_min,
|
| 118 |
+
end=date_max,
|
| 119 |
+
value=(date_min, date_max),
|
| 120 |
+
sizing_mode="stretch_width"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
regions_list = sorted(list(df_data['Region'].unique()))
|
| 124 |
+
region_select = pn.widgets.MultiChoice(
|
| 125 |
+
name='Filter Regions',
|
| 126 |
+
options=regions_list,
|
| 127 |
+
value=regions_list,
|
| 128 |
+
sizing_mode="stretch_width"
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
categories_list = sorted(list(df_data['Category'].unique()))
|
| 132 |
+
category_checkboxes = pn.widgets.CheckBoxGroup(
|
| 133 |
+
name='Filter Categories',
|
| 134 |
+
options=categories_list,
|
| 135 |
+
value=categories_list,
|
| 136 |
+
inline=False
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# Overview Tab Widgets
|
| 140 |
+
name_input = pn.widgets.TextInput(name="Enter your name", value="Developer", placeholder="Type name here...", sizing_mode="stretch_width")
|
| 141 |
+
color_picker = pn.widgets.ColorPicker(name="Choose card theme color", value="#20B2AA", sizing_mode="stretch_width")
|
| 142 |
+
size_slider = pn.widgets.IntSlider(name="Font size adjustment", start=12, end=28, value=16, sizing_mode="stretch_width")
|
| 143 |
+
|
| 144 |
+
# ML Tab Widgets
|
| 145 |
+
image_selector = pn.widgets.Select(
|
| 146 |
+
name="Select a sample image",
|
| 147 |
+
options={
|
| 148 |
+
"Cat": "https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?q=80&w=600&auto=format&fit=crop",
|
| 149 |
+
"Dog": "https://images.unsplash.com/photo-1543466835-00a7907e9de1?q=80&w=600&auto=format&fit=crop",
|
| 150 |
+
"Parrot": "https://images.unsplash.com/photo-1552728089-57bdde30ebd3?q=80&w=600&auto=format&fit=crop",
|
| 151 |
+
"Sports Car": "https://images.unsplash.com/photo-1503376780353-7e6692767b70?q=80&w=600&auto=format&fit=crop",
|
| 152 |
+
"Mountain Landscape": "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?q=80&w=600&auto=format&fit=crop",
|
| 153 |
+
"Custom URL (Enter below)": "custom"
|
| 154 |
+
},
|
| 155 |
+
value="https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?q=80&w=600&auto=format&fit=crop",
|
| 156 |
+
sizing_mode="stretch_width"
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
custom_url_input = pn.widgets.TextInput(
|
| 160 |
+
name="Custom Image URL",
|
| 161 |
+
placeholder="Paste any public image URL here...",
|
| 162 |
+
visible=False,
|
| 163 |
+
sizing_mode="stretch_width"
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
def update_custom_url_visibility(val):
|
| 167 |
+
custom_url_input.visible = (val == "custom")
|
| 168 |
+
|
| 169 |
+
image_selector.param.watch(lambda event: update_custom_url_visibility(event.new), 'value')
|
| 170 |
+
|
| 171 |
+
class_names_input = pn.widgets.TextInput(
|
| 172 |
+
name="Candidate Classes (comma-separated)",
|
| 173 |
+
value="cat, dog, parrot, car, mountain",
|
| 174 |
+
placeholder="e.g. cat, dog, parrot",
|
| 175 |
+
sizing_mode="stretch_width"
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
classify_btn = pn.widgets.Button(name="Run CLIP Inference", button_type="primary", sizing_mode="stretch_width")
|
| 179 |
+
|
| 180 |
+
# Playground Tab Widgets
|
| 181 |
+
latex_input = pn.widgets.TextInput(
|
| 182 |
+
name="LaTeX Equation Editor",
|
| 183 |
+
value=r"f(x) = \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}"
|
| 184 |
+
)
|
| 185 |
+
latex_pane = pn.pane.LaTeX(
|
| 186 |
+
pn.bind(lambda eq: f"$$\\text{{Output: }} {eq}$$", latex_input),
|
| 187 |
+
align="center"
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
markdown_editor = pn.widgets.TextAreaInput(
|
| 191 |
+
name="Markdown Editor",
|
| 192 |
+
value="### Markdown Live Preview!\n- **Bold text**\n- *Italics*\n- [Link to Panel](https://panel.holoviz.org)",
|
| 193 |
+
height=120
|
| 194 |
+
)
|
| 195 |
+
markdown_pane = pn.pane.Markdown(pn.bind(lambda val: val, markdown_editor))
|
| 196 |
+
|
| 197 |
+
file_input = pn.widgets.FileInput(name="Upload File (CSV/Text)", accept=".csv,.txt")
|
| 198 |
+
|
| 199 |
+
def file_details(data):
|
| 200 |
+
if data is None:
|
| 201 |
+
return "*No file uploaded yet. Upload a .csv or .txt file to view details.*"
|
| 202 |
try:
|
| 203 |
+
file_len = len(data)
|
| 204 |
+
text_preview = data[:150].decode('utf-8', errors='ignore')
|
| 205 |
+
return f"**File Size**: {file_len} bytes\n\n**First 150 characters**:\n```\n{text_preview}\n```"
|
| 206 |
+
except Exception as e:
|
| 207 |
+
return f"Failed to parse file: {str(e)}"
|
| 208 |
+
|
| 209 |
+
file_details_pane = pn.pane.Markdown(pn.bind(file_details, file_input))
|
| 210 |
+
|
| 211 |
+
video_widget = pn.widgets.Video(
|
| 212 |
+
value="https://assets.mixkit.co/videos/preview/mixkit-forest-stream-in-the-sunlight-529-large.mp4",
|
| 213 |
+
loop=True, autoplay=False, sizing_mode="stretch_width", height=200
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
# --- 4. REACTIVE FUNCTIONS & CARD GENERATORS ---
|
| 217 |
+
|
| 218 |
+
# KPI Cards generator
|
| 219 |
+
def make_kpi_card(title, value, color="#20B2AA", icon="π΅"):
|
| 220 |
+
return pn.pane.HTML(f"""
|
| 221 |
+
<div style="
|
| 222 |
+
background: rgba(128, 128, 128, 0.08);
|
| 223 |
+
border-left: 5px solid {color};
|
| 224 |
+
border-radius: 8px;
|
| 225 |
+
padding: 15px 20px;
|
| 226 |
+
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
|
| 227 |
+
display: flex;
|
| 228 |
+
align-items: center;
|
| 229 |
+
justify-content: space-between;
|
| 230 |
+
min-width: 180px;
|
| 231 |
+
flex: 1;
|
| 232 |
+
">
|
| 233 |
+
<div>
|
| 234 |
+
<span style="font-size: 13px; opacity: 0.7; text-transform: uppercase; font-weight: 600; display: block; margin-bottom: 5px;">{title}</span>
|
| 235 |
+
<span style="font-size: 22px; font-weight: bold; color: var(--neutral-foreground-rest);">{value}</span>
|
| 236 |
+
</div>
|
| 237 |
+
<span style="font-size: 28px; line-height: 1;">{icon}</span>
|
| 238 |
+
</div>
|
| 239 |
+
""", sizing_mode="stretch_width")
|
| 240 |
+
|
| 241 |
+
# Overview Greeting Card
|
| 242 |
+
def greeting_card(name, color, size):
|
| 243 |
+
style_content = f"""
|
| 244 |
+
<div style="
|
| 245 |
+
background: linear-gradient(135deg, {color}, #2c3e50);
|
| 246 |
+
padding: 30px;
|
| 247 |
+
border-radius: 12px;
|
| 248 |
+
text-align: center;
|
| 249 |
+
color: white;
|
| 250 |
+
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
|
| 251 |
+
font-size: {size}px;
|
| 252 |
+
transition: all 0.3s ease;
|
| 253 |
+
margin-top: 10px;
|
| 254 |
+
">
|
| 255 |
+
<h3 style="margin: 0; color: white;">Welcome to Panel, {name if name else "Developer"}! π</h3>
|
| 256 |
+
<p style="font-size: 14px; opacity: 0.85; margin: 12px 0 0 0;">
|
| 257 |
+
This card is updating in real time using Panel reactive bindings.
|
| 258 |
+
</p>
|
| 259 |
+
</div>
|
| 260 |
+
"""
|
| 261 |
+
return pn.pane.HTML(style_content, sizing_mode="stretch_width")
|
| 262 |
+
|
| 263 |
+
overview_interactive_card = pn.bind(greeting_card, name=name_input, color=color_picker, size=size_slider)
|
| 264 |
+
|
| 265 |
+
# Data Dashboard generator
|
| 266 |
+
def get_dashboard_layout(df_filtered):
|
| 267 |
+
if df_filtered.empty:
|
| 268 |
+
return pn.pane.Markdown("### β οΈ No data matches the selected filters. Please adjust them in the sidebar.")
|
| 269 |
+
|
| 270 |
+
total_sales = df_filtered['Sales'].sum()
|
| 271 |
+
total_profit = df_filtered['Profit'].sum()
|
| 272 |
+
margin = (total_profit / total_sales) if total_sales > 0 else 0
|
| 273 |
+
total_qty = df_filtered['Quantity'].sum()
|
| 274 |
|
| 275 |
+
kpi1 = make_kpi_card("Total Sales", f"${total_sales:,.2f}", "#20B2AA", "π°")
|
| 276 |
+
kpi2 = make_kpi_card("Total Profit", f"${total_profit:,.2f}", "#4CAF50" if total_profit >= 0 else "#F44336", "π")
|
| 277 |
+
kpi3 = make_kpi_card("Profit Margin", f"{margin:.1%}", "#FF9800", "π")
|
| 278 |
+
kpi4 = make_kpi_card("Products Sold", f"{total_qty:,}", "#9C27B0", "π¦")
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
+
kpis = pn.Row(kpi1, kpi2, kpi3, kpi4, sizing_mode="stretch_width", margin=(0, 0, 20, 0))
|
|
|
|
| 281 |
|
| 282 |
+
# 1. Line chart: Monthly trend
|
| 283 |
+
df_monthly = df_filtered.groupby(df_filtered['Date'].dt.to_period('M')).agg({'Sales': 'sum', 'Profit': 'sum'}).reset_index()
|
| 284 |
+
df_monthly['Date'] = df_monthly['Date'].dt.to_timestamp()
|
| 285 |
|
| 286 |
+
fig_line = px.line(
|
| 287 |
+
df_monthly, x='Date', y='Sales', title="Monthly Sales Trend",
|
| 288 |
+
labels={'Sales': 'Sales ($)', 'Date': 'Month'},
|
| 289 |
+
template="plotly_white"
|
| 290 |
+
)
|
| 291 |
+
fig_line.update_traces(line_color="#20B2AA", line_width=3)
|
| 292 |
+
fig_line.update_layout(
|
| 293 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 294 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 295 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 296 |
+
font=dict(color="gray")
|
| 297 |
+
)
|
| 298 |
+
chart_line = pn.pane.Plotly(fig_line, sizing_mode="stretch_width", height=350)
|
| 299 |
+
|
| 300 |
+
# 2. Bar chart: Category
|
| 301 |
+
df_cat = df_filtered.groupby(['Category', 'Sub-Category']).agg({'Sales': 'sum'}).reset_index()
|
| 302 |
+
fig_bar = px.bar(
|
| 303 |
+
df_cat, x='Sub-Category', y='Sales', color='Category',
|
| 304 |
+
title="Sales by Category & Sub-Category",
|
| 305 |
+
labels={'Sales': 'Sales ($)', 'Sub-Category': 'Sub-Category'},
|
| 306 |
+
color_discrete_sequence=["#20B2AA", "#FF9800", "#9C27B0"],
|
| 307 |
+
template="plotly_white"
|
| 308 |
+
)
|
| 309 |
+
fig_bar.update_layout(
|
| 310 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 311 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 312 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 313 |
+
font=dict(color="gray")
|
| 314 |
+
)
|
| 315 |
+
chart_bar = pn.pane.Plotly(fig_bar, sizing_mode="stretch_width", height=350)
|
| 316 |
+
|
| 317 |
+
# 3. Scatter plot
|
| 318 |
+
fig_scatter = px.scatter(
|
| 319 |
+
df_filtered, x='Sales', y='Profit', color='Category', size='Quantity',
|
| 320 |
+
hover_data=['Sub-Category', 'Date'], title="Transaction Profitability (Sales vs Profit)",
|
| 321 |
+
color_discrete_sequence=["#20B2AA", "#FF9800", "#9C27B0"],
|
| 322 |
+
opacity=0.7, template="plotly_white"
|
| 323 |
+
)
|
| 324 |
+
fig_scatter.update_layout(
|
| 325 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 326 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 327 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 328 |
+
font=dict(color="gray")
|
| 329 |
+
)
|
| 330 |
+
chart_scatter = pn.pane.Plotly(fig_scatter, sizing_mode="stretch_width", height=350)
|
| 331 |
+
|
| 332 |
+
layout = pn.Column(
|
| 333 |
+
kpis,
|
| 334 |
+
pn.Row(chart_line, chart_bar, sizing_mode="stretch_width", margin=(0, 0, 20, 0)),
|
| 335 |
+
pn.Row(chart_scatter, sizing_mode="stretch_width"),
|
| 336 |
+
sizing_mode="stretch_width"
|
| 337 |
+
)
|
| 338 |
+
return layout
|
| 339 |
+
|
| 340 |
+
def filter_and_render_dashboard(date_range, regions, categories):
|
| 341 |
+
df_filtered = df_data.copy()
|
| 342 |
+
start_dt, end_dt = date_range
|
| 343 |
+
df_filtered = df_filtered[(df_filtered['Date'] >= start_dt) & (df_filtered['Date'] <= end_dt)]
|
| 344 |
+
if regions:
|
| 345 |
+
df_filtered = df_filtered[df_filtered['Region'].isin(regions)]
|
| 346 |
+
else:
|
| 347 |
+
df_filtered = df_filtered[df_filtered['Region'].isin([])]
|
| 348 |
+
if categories:
|
| 349 |
+
df_filtered = df_filtered[df_filtered['Category'].isin(categories)]
|
| 350 |
+
else:
|
| 351 |
+
df_filtered = df_filtered[df_filtered['Category'].isin([])]
|
| 352 |
+
|
| 353 |
+
return get_dashboard_layout(df_filtered)
|
| 354 |
+
|
| 355 |
+
interactive_dashboard = pn.panel(
|
| 356 |
+
pn.bind(filter_and_render_dashboard, date_range=date_range_slider, regions=region_select, categories=category_checkboxes),
|
| 357 |
+
sizing_mode="stretch_width"
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
# ML Classification generator
|
| 361 |
+
async def classify_image(url, classes_str):
|
| 362 |
+
if not url or url == "custom":
|
| 363 |
+
yield "##### β οΈ Please provide a valid image URL."
|
| 364 |
+
return
|
| 365 |
+
|
| 366 |
+
try:
|
| 367 |
+
yield "##### β Fetching image..."
|
| 368 |
+
pil_img = await open_image_url(url)
|
| 369 |
+
img_pane = pn.pane.Image(pil_img, height=280, align="center")
|
| 370 |
+
except Exception as e:
|
| 371 |
+
yield f"##### π Failed to load image from URL: `{url}`. Error: {str(e)}"
|
| 372 |
+
return
|
| 373 |
+
|
| 374 |
+
yield "##### β Running CLIP Model (openai/clip-vit-base-patch32)..."
|
| 375 |
+
try:
|
| 376 |
+
class_items = [c.strip() for c in classes_str.split(",") if c.strip()]
|
| 377 |
+
if not class_items:
|
| 378 |
+
yield "##### β οΈ Please specify at least one class name."
|
| 379 |
+
return
|
| 380 |
+
|
| 381 |
+
scores = get_similarity_scores(class_items, pil_img)
|
| 382 |
+
|
| 383 |
+
results_col = pn.Column(
|
| 384 |
+
"##### π Classification Results",
|
| 385 |
+
img_pane,
|
| 386 |
+
sizing_mode="stretch_width"
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
for name, score in zip(class_items, scores):
|
| 390 |
+
bar = pn.indicators.Progress(
|
| 391 |
+
value=int(score * 100),
|
| 392 |
sizing_mode="stretch_width",
|
| 393 |
+
bar_color="success" if score > 0.5 else "info",
|
| 394 |
+
height=15
|
|
|
|
| 395 |
)
|
| 396 |
+
label = pn.pane.Markdown(f"**{name}**: {score:.2%}", margin=(5, 0, 0, 0))
|
| 397 |
+
results_col.append(pn.Column(label, bar, margin=(5, 0)))
|
| 398 |
+
|
| 399 |
+
yield results_col
|
| 400 |
+
except Exception as e:
|
| 401 |
+
yield f"##### π Classification failed. Error: {str(e)}"
|
| 402 |
|
| 403 |
+
def run_classification_on_click(clicks):
|
| 404 |
+
url = image_selector.value
|
| 405 |
+
if url == "custom":
|
| 406 |
+
url = custom_url_input.value
|
| 407 |
+
|
| 408 |
+
classes = class_names_input.value
|
| 409 |
+
|
| 410 |
+
if clicks == 0:
|
| 411 |
+
if url and url != "custom":
|
| 412 |
+
try:
|
| 413 |
+
img_pane = pn.pane.Image(url, height=280, align="center")
|
| 414 |
+
return pn.Column("##### Image Preview", img_pane)
|
| 415 |
+
except:
|
| 416 |
+
pass
|
| 417 |
+
return "##### π‘ Click 'Run CLIP Inference' to start classification."
|
| 418 |
+
|
| 419 |
+
return pn.panel(classify_image(url, classes))
|
| 420 |
|
| 421 |
+
classification_output_area = pn.panel(
|
| 422 |
+
pn.bind(run_classification_on_click, clicks=classify_btn),
|
| 423 |
+
sizing_mode="stretch_width"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
)
|
| 425 |
|
| 426 |
+
# Reset output when inputs change
|
| 427 |
+
def reset_clicks(event):
|
| 428 |
+
classify_btn.clicks = 0
|
|
|
|
|
|
|
| 429 |
|
| 430 |
+
image_selector.param.watch(reset_clicks, 'value')
|
| 431 |
+
custom_url_input.param.watch(reset_clicks, 'value')
|
| 432 |
+
class_names_input.param.watch(reset_clicks, 'value')
|
|
|
|
|
|
|
| 433 |
|
| 434 |
+
# --- 5. FOOTER SOCIAL LINKS ---
|
| 435 |
footer_row = pn.Row(pn.Spacer(), align="center")
|
| 436 |
for icon, url in ICON_URLS.items():
|
| 437 |
+
href_button = pn.widgets.Button(icon=icon, width=38, height=38, button_type="light")
|
| 438 |
href_button.js_on_click(code=f"window.open('{url}')")
|
| 439 |
footer_row.append(href_button)
|
| 440 |
footer_row.append(pn.Spacer())
|
| 441 |
|
| 442 |
+
# --- 6. TEMPLATE ASSEMBLING ---
|
| 443 |
+
template = pn.template.FastListTemplate(
|
| 444 |
+
title="HoloViz Panel Interactive Showcase",
|
| 445 |
+
sidebar=[
|
| 446 |
+
"## Dashboard Filters",
|
| 447 |
+
"*(These filters apply to the **Data Analytics Dashboard** tab)*",
|
| 448 |
+
date_range_slider,
|
| 449 |
+
pn.Spacer(height=10),
|
| 450 |
+
region_select,
|
| 451 |
+
pn.Spacer(height=10),
|
| 452 |
+
category_checkboxes,
|
| 453 |
+
pn.Spacer(height=25),
|
| 454 |
+
"### About HoloViz Panel",
|
| 455 |
+
"Panel is a powerful Python library that lets you build high-performance interactive web applications, dashboards, and data portals entirely in Python.",
|
| 456 |
+
"[Documentation](https://panel.holoviz.org)",
|
| 457 |
+
"[GitHub Repository](https://github.com/holoviz/panel)"
|
| 458 |
+
],
|
| 459 |
+
main=[
|
| 460 |
+
pn.Tabs(
|
| 461 |
+
("π Overview & Basics", pn.Column(
|
| 462 |
+
pn.pane.Markdown("""
|
| 463 |
+
# Welcome to the HoloViz Panel Showcase! π
|
| 464 |
+
|
| 465 |
+
This Space demonstrates how to build premium, fully interactive dashboards and web applications directly in Python using **Panel**.
|
| 466 |
+
|
| 467 |
+
### Why choose Panel?
|
| 468 |
+
- **No HTML/CSS/JS required**: Build complex frontends completely in Python.
|
| 469 |
+
- **Rich Ecosystem Integration**: Seamlessly connect Bokeh, Plotly, Altair, Matplotlib, PyTorch, and Hugging Face models.
|
| 470 |
+
- **Reactive and Callback APIs**: Simple decorators or bindings to link widgets directly to code.
|
| 471 |
+
- **Out-of-the-box templates**: Stunning themes like Fast, Material, and Bootstrap that support Dark/Light mode switching.
|
| 472 |
+
"""),
|
| 473 |
+
pn.Spacer(height=15),
|
| 474 |
+
pn.Row(
|
| 475 |
+
pn.Column(
|
| 476 |
+
"### 1. Interactive Greetings Widget",
|
| 477 |
+
"Change the inputs below and watch the card update instantly.",
|
| 478 |
+
name_input,
|
| 479 |
+
color_picker,
|
| 480 |
+
size_slider,
|
| 481 |
+
margin=(0, 15)
|
| 482 |
+
),
|
| 483 |
+
pn.Column(
|
| 484 |
+
"### Live Preview",
|
| 485 |
+
overview_interactive_card,
|
| 486 |
+
margin=(0, 15)
|
| 487 |
+
),
|
| 488 |
+
sizing_mode="stretch_width"
|
| 489 |
+
),
|
| 490 |
+
pn.Spacer(height=20),
|
| 491 |
+
pn.pane.Markdown("""
|
| 492 |
+
### Check out other tabs:
|
| 493 |
+
- **π Data Analytics Dashboard**: A full sales dashboard using Plotly Express linked dynamically to the sidebar filters.
|
| 494 |
+
- **π€ CLIP Image Classifier**: Real-time AI classification using an OpenAI CLIP model cached in memory.
|
| 495 |
+
- **π Widget Playground**: Live LaTeX editing, Markdown previewing, and file uploads.
|
| 496 |
+
""")
|
| 497 |
+
)),
|
| 498 |
+
("π Data Analytics Dashboard", pn.Column(
|
| 499 |
+
"## Real-time Superstore Analytics",
|
| 500 |
+
"Use the filters in the **left sidebar** to refine this dashboard in real-time.",
|
| 501 |
+
pn.Spacer(height=10),
|
| 502 |
+
interactive_dashboard
|
| 503 |
+
)),
|
| 504 |
+
("π€ CLIP Image Classifier", pn.Column(
|
| 505 |
+
"## AI Image Classification with CLIP",
|
| 506 |
+
"This tab runs **OpenAI CLIP (clip-vit-base-patch32)** to classify images based on natural language descriptors.",
|
| 507 |
+
pn.Spacer(height=10),
|
| 508 |
+
pn.Row(
|
| 509 |
+
pn.Column(
|
| 510 |
+
image_selector,
|
| 511 |
+
custom_url_input,
|
| 512 |
+
class_names_input,
|
| 513 |
+
pn.Spacer(height=10),
|
| 514 |
+
classify_btn,
|
| 515 |
+
width=320,
|
| 516 |
+
margin=(0, 15)
|
| 517 |
+
),
|
| 518 |
+
pn.Column(
|
| 519 |
+
classification_output_area,
|
| 520 |
+
margin=(0, 15)
|
| 521 |
+
),
|
| 522 |
+
sizing_mode="stretch_width"
|
| 523 |
+
)
|
| 524 |
+
)),
|
| 525 |
+
("π Widget Playground", pn.Column(
|
| 526 |
+
"## Panel Interactive Playground",
|
| 527 |
+
"Explore some of Panel's diverse widgets and dynamic rendering capabilities.",
|
| 528 |
+
pn.Spacer(height=15),
|
| 529 |
+
pn.Row(
|
| 530 |
+
pn.Column(
|
| 531 |
+
"### Live LaTeX Renderer",
|
| 532 |
+
latex_input,
|
| 533 |
+
latex_pane,
|
| 534 |
+
margin=(0, 15)
|
| 535 |
+
),
|
| 536 |
+
pn.Column(
|
| 537 |
+
"### Live Markdown Editor",
|
| 538 |
+
markdown_editor,
|
| 539 |
+
markdown_pane,
|
| 540 |
+
margin=(0, 15)
|
| 541 |
+
),
|
| 542 |
+
sizing_mode="stretch_width"
|
| 543 |
+
),
|
| 544 |
+
pn.Spacer(height=20),
|
| 545 |
+
pn.Row(
|
| 546 |
+
pn.Column(
|
| 547 |
+
"### File Upload Inspector",
|
| 548 |
+
file_input,
|
| 549 |
+
file_details_pane,
|
| 550 |
+
margin=(0, 15)
|
| 551 |
+
),
|
| 552 |
+
pn.Column(
|
| 553 |
+
"### Embedded Video Player",
|
| 554 |
+
video_widget,
|
| 555 |
+
margin=(0, 15)
|
| 556 |
+
),
|
| 557 |
+
sizing_mode="stretch_width"
|
| 558 |
+
)
|
| 559 |
+
))
|
| 560 |
+
),
|
| 561 |
+
pn.Spacer(height=40),
|
| 562 |
+
footer_row
|
| 563 |
+
],
|
| 564 |
+
accent_base_color="#20B2AA",
|
| 565 |
+
header_background="#20B2AA",
|
| 566 |
+
theme_toggle=True
|
| 567 |
)
|
| 568 |
|
| 569 |
+
template.servable()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
panel
|
| 2 |
-
|
| 3 |
-
|
| 4 |
numpy
|
|
|
|
| 5 |
torch
|
| 6 |
-
aiohttp
|
|
|
|
|
|
| 1 |
panel
|
| 2 |
+
pandas
|
| 3 |
+
plotly
|
| 4 |
numpy
|
| 5 |
+
transformers
|
| 6 |
torch
|
| 7 |
+
aiohttp
|
| 8 |
+
pillow
|