Saree-Design-Filling / src /streamlit_app.py
MBG0903's picture
Update src/streamlit_app.py
381ce34 verified
Raw
History Blame Contribute Delete
6.25 kB
import io
from pathlib import Path
import cv2
import numpy as np
import pandas as pd
import streamlit as st
from PIL import Image
st.set_page_config(page_title="Saree Pixel Fill Assistant", layout="wide")
SAMPLE_DIR = Path(__file__).parent / "sample_images" # images directly inside src/
def pil_to_bgr(pil_img):
arr = np.array(pil_img.convert("RGB"))
return cv2.cvtColor(arr, cv2.COLOR_RGB2BGR)
def bgr_to_pil(bgr):
return Image.fromarray(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB))
def make_line_mask(bgr, threshold=215, close_px=1):
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
line_mask = (gray < threshold).astype(np.uint8) * 255
if close_px > 0:
k = cv2.getStructuringElement(
cv2.MORPH_ELLIPSE,
(2 * close_px + 1, 2 * close_px + 1)
)
line_mask = cv2.morphologyEx(line_mask, cv2.MORPH_CLOSE, k, iterations=1)
return line_mask
def find_enclosed_regions(line_mask, min_area=20, max_area=5000):
h, w = line_mask.shape
white = (line_mask == 0).astype(np.uint8) * 255
flood = white.copy()
mask = np.zeros((h + 2, w + 2), np.uint8)
cv2.floodFill(flood, mask, (0, 0), 128)
enclosed = (flood == 255).astype(np.uint8) * 255
n, labels, stats, _ = cv2.connectedComponentsWithStats(enclosed, 8)
region_mask = np.zeros_like(enclosed)
regions = []
for i in range(1, n):
area = int(stats[i, cv2.CC_STAT_AREA])
x = int(stats[i, cv2.CC_STAT_LEFT])
y = int(stats[i, cv2.CC_STAT_TOP])
ww = int(stats[i, cv2.CC_STAT_WIDTH])
hh = int(stats[i, cv2.CC_STAT_HEIGHT])
touches_border = x == 0 or y == 0 or (x + ww) >= w or (y + hh) >= h
if not touches_border and min_area <= area <= max_area:
region_mask[labels == i] = 255
regions.append({
"id": i,
"area_px": area,
"x": x,
"y": y,
"w": ww,
"h": hh
})
return region_mask, regions
def generate_step3(bgr, threshold, close_px, min_area, max_area, fill_regions, fill_color):
line_mask = make_line_mask(bgr, threshold, close_px)
region_mask, regions = find_enclosed_regions(line_mask, min_area, max_area)
out = np.ones_like(bgr) * 255
out[line_mask > 0] = fill_color
if fill_regions:
out[region_mask > 0] = fill_color
return out, line_mask, region_mask, regions
def overlay_regions(bgr, region_mask, line_mask):
out = bgr.copy()
overlay = out.copy()
overlay[region_mask > 0] = (0, 255, 255)
overlay[line_mask > 0] = (0, 0, 255)
return cv2.addWeighted(overlay, 0.45, out, 0.55, 0)
def resize_pixel_art(bgr, scale):
h, w = bgr.shape[:2]
return cv2.resize(
bgr,
(w * scale, h * scale),
interpolation=cv2.INTER_NEAREST
)
def to_bytes_bmp(bgr):
bio = io.BytesIO()
bgr_to_pil(bgr).save(bio, format="BMP")
return bio.getvalue()
st.title("Procelevate Saree Pixel Fill Assistant")
st.caption("Prototype: Step 2 line design → Step 3 pixel separation → Step 4 resize preview")
with st.sidebar:
st.header("Controls")
threshold = st.slider("Line detection threshold", 80, 255, 215)
close_px = st.slider("Close tiny line gaps", 0, 5, 1)
min_area = st.slider("Minimum fill region area", 1, 1000, 20)
max_area = st.number_input("Maximum fill region area", min_value=100, value=5000)
fill_mode = st.selectbox(
"Fill mode",
[
"Line only",
"Fill enclosed regions",
"Fill motif body"
]
)
colour = st.color_picker("Step 3 fill/line colour", "#FF0000")
scale = st.slider("Step 4 resize scale", 2, 12, 4)
fill_rgb = tuple(int(colour.lstrip("#")[i:i + 2], 16) for i in (0, 2, 4))
fill_bgr = (fill_rgb[2], fill_rgb[1], fill_rgb[0])
sample_files = sorted([
p for p in SAMPLE_DIR.iterdir()
if p.suffix.lower() in [".bmp", ".png", ".jpg", ".jpeg"]
])
st.subheader("Choose Step 2 image")
source = st.radio(
"Image source",
["Use sample image from Space", "Upload image"],
horizontal=True
)
pil = None
if source == "Use sample image from Space":
if sample_files:
selected = st.selectbox(
"Select sample Step 2 image",
sample_files,
format_func=lambda p: p.name
)
pil = Image.open(selected).convert("RGB")
else:
st.warning("No sample images found inside src folder.")
else:
uploaded = st.file_uploader(
"Upload Step 2 image",
type=["bmp", "png", "jpg", "jpeg"]
)
if uploaded:
pil = Image.open(uploaded).convert("RGB")
if pil:
bgr = pil_to_bgr(pil)
fill_regions = fill_mode != "Line only"
step3, line_mask, region_mask, regions = generate_step3(
bgr,
threshold,
close_px,
min_area,
int(max_area),
fill_regions,
fill_bgr
)
overlay = overlay_regions(bgr, region_mask, line_mask)
resized = resize_pixel_art(step3, scale)
c1, c2, c3 = st.columns(3)
with c1:
st.subheader("Input Step 2")
st.image(pil, use_column_width=True)
with c2:
st.subheader("Detected Regions")
st.image(bgr_to_pil(overlay), use_column_width=True)
with c3:
st.subheader("Generated Step 3")
st.image(bgr_to_pil(step3), use_column_width=True)
st.subheader("Step 4 Resize Preview")
st.image(bgr_to_pil(resized), use_column_width=True)
m1, m2, m3 = st.columns(3)
m1.metric("Detected regions", len(regions))
m2.metric("Line pixels", int((line_mask > 0).sum()))
m3.metric("Filled pixels", int((region_mask > 0).sum()))
st.download_button(
"Download Step 3 BMP",
to_bytes_bmp(step3),
file_name="generated_step3.bmp"
)
st.download_button(
"Download Step 4 Preview BMP",
to_bytes_bmp(resized),
file_name="generated_step4_preview.bmp"
)
with st.expander("Region details"):
if regions:
st.dataframe(pd.DataFrame(regions).head(500), use_container_width=True)
else:
st.info("No enclosed regions detected with current settings.")