|
|
import streamlit as st |
|
|
from pathlib import Path |
|
|
from io import BytesIO |
|
|
from PIL import Image, UnidentifiedImageError |
|
|
import matplotlib.pyplot as plt |
|
|
|
|
|
from imgshape.shape import get_shape |
|
|
from imgshape.analyze import analyze_type |
|
|
from imgshape.recommender import recommend_preprocessing |
|
|
from imgshape.augmentations import AugmentationRecommender |
|
|
from imgshape.report import generate_markdown_report, generate_html_report |
|
|
from imgshape.viz import plot_shape_distribution |
|
|
from imgshape.torchloader import to_torch_transform |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="imgshape v2.1.3", layout="wide") |
|
|
st.title("πΌοΈ imgshape β Smart Dataset Assistant (v2.1.3)") |
|
|
|
|
|
st.markdown( |
|
|
"Upload an image or provide a dataset folder to analyze, " |
|
|
"recommend preprocessing, generate reports, and even get PyTorch transforms." |
|
|
) |
|
|
|
|
|
|
|
|
st.sidebar.header("π Input") |
|
|
uploaded_file = st.sidebar.file_uploader( |
|
|
"Upload an image", type=["jpg", "jpeg", "png", "bmp", "tiff"] |
|
|
) |
|
|
dataset_path = st.sidebar.text_input("Dataset folder path", "assets/sample_images") |
|
|
|
|
|
tabs = st.tabs(["π Shape", "π Analyze", "π§ Recommend", "π Report", "π TorchLoader"]) |
|
|
|
|
|
|
|
|
|
|
|
def cache_uploaded_bytes(): |
|
|
""" |
|
|
Read uploaded_file once and cache raw bytes in session_state['uploaded_bytes']. |
|
|
Returns bytes or None. |
|
|
""" |
|
|
if uploaded_file is None: |
|
|
return None |
|
|
|
|
|
if "uploaded_bytes" not in st.session_state: |
|
|
try: |
|
|
st.session_state["uploaded_bytes"] = uploaded_file.read() |
|
|
except Exception as e: |
|
|
st.session_state["uploaded_bytes"] = None |
|
|
st.error(f"Error reading upload: {e}") |
|
|
return None |
|
|
return st.session_state["uploaded_bytes"] |
|
|
|
|
|
|
|
|
def load_uploaded_image_from_bytes(bytes_data): |
|
|
""" |
|
|
Build fresh BytesIO and PIL.Image from raw bytes. |
|
|
Returns (PIL.Image, BytesIO) or (None, None) on error. |
|
|
""" |
|
|
if not bytes_data: |
|
|
return None, None |
|
|
try: |
|
|
buf = BytesIO(bytes_data) |
|
|
pil_img = Image.open(BytesIO(bytes_data)).convert("RGB") |
|
|
return pil_img, buf |
|
|
except UnidentifiedImageError: |
|
|
return None, None |
|
|
except Exception as e: |
|
|
st.error(f"Unexpected error opening image: {e}") |
|
|
return None, None |
|
|
|
|
|
|
|
|
|
|
|
with tabs[0]: |
|
|
st.subheader("π Shape Detection") |
|
|
bytes_data = cache_uploaded_bytes() |
|
|
if bytes_data: |
|
|
pil_img, buf = load_uploaded_image_from_bytes(bytes_data) |
|
|
if pil_img is None: |
|
|
st.error("Uploaded file is not a valid image. Please upload a PNG/JPEG/etc.") |
|
|
else: |
|
|
st.image(pil_img, caption="Uploaded Image", use_column_width=True) |
|
|
try: |
|
|
|
|
|
shape = get_shape(pil_img) |
|
|
st.json({"shape": shape}) |
|
|
except Exception as e: |
|
|
st.error(f"Error in shape detection: {e}") |
|
|
else: |
|
|
st.info("Upload an image to see its shape.") |
|
|
|
|
|
|
|
|
|
|
|
with tabs[1]: |
|
|
st.subheader("π Image Analysis") |
|
|
bytes_data = cache_uploaded_bytes() |
|
|
if bytes_data: |
|
|
pil_img, buf = load_uploaded_image_from_bytes(bytes_data) |
|
|
if pil_img is None: |
|
|
st.error("Uploaded file is not a valid image. Please upload a PNG/JPEG/etc.") |
|
|
else: |
|
|
buf.seek(0) |
|
|
try: |
|
|
analysis = analyze_type(buf) |
|
|
st.json(analysis) |
|
|
except Exception as e: |
|
|
st.error(f"Error in analysis: {e}") |
|
|
else: |
|
|
st.info("Upload an image to analyze.") |
|
|
|
|
|
st.subheader("π Dataset Visualization") |
|
|
if st.button("Plot Shape Distribution"): |
|
|
try: |
|
|
fig = plt.figure() |
|
|
plot_shape_distribution(dataset_path, save=False) |
|
|
st.pyplot(fig) |
|
|
except Exception as e: |
|
|
st.error(f"Error plotting dataset: {e}") |
|
|
|
|
|
|
|
|
|
|
|
with tabs[2]: |
|
|
st.subheader("π§ Preprocessing + Augmentation Recommendations") |
|
|
bytes_data = cache_uploaded_bytes() |
|
|
if bytes_data: |
|
|
pil_img, buf = load_uploaded_image_from_bytes(bytes_data) |
|
|
if pil_img is None: |
|
|
st.error("Uploaded file is not a valid image. Please upload a PNG/JPEG/etc.") |
|
|
else: |
|
|
try: |
|
|
rec = recommend_preprocessing(pil_img) |
|
|
st.json({"preprocessing": rec}) |
|
|
except Exception as e: |
|
|
st.error(f"Error in preprocessing recommendation: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
ar = AugmentationRecommender(seed=42) |
|
|
buf.seek(0) |
|
|
analysis = analyze_type(buf) |
|
|
plan = ar.recommend_for_dataset( |
|
|
{"entropy_mean": analysis.get("entropy", 5.0), "image_count": 1} |
|
|
) |
|
|
st.json({ |
|
|
"augmentation_plan": { |
|
|
"order": plan.recommended_order, |
|
|
"augmentations": [a.__dict__ for a in plan.augmentations] |
|
|
} |
|
|
}) |
|
|
except Exception as e: |
|
|
st.error(f"Error in augmentation plan: {e}") |
|
|
else: |
|
|
st.info("Upload an image to get recommendations.") |
|
|
|
|
|
|
|
|
|
|
|
with tabs[3]: |
|
|
st.subheader("π Dataset Report") |
|
|
if st.button("Generate Markdown + HTML Report"): |
|
|
try: |
|
|
stats = {"image_count": 1, "source_dir": dataset_path} |
|
|
rec = {} |
|
|
bytes_data = cache_uploaded_bytes() |
|
|
if bytes_data: |
|
|
|
|
|
_, buf = load_uploaded_image_from_bytes(bytes_data) |
|
|
if buf is not None: |
|
|
buf.seek(0) |
|
|
rec = recommend_preprocessing(buf) |
|
|
|
|
|
ar = AugmentationRecommender(seed=42) |
|
|
plan = ar.recommend_for_dataset({"entropy_mean": 5.0, "image_count": 10}) |
|
|
|
|
|
md_path = Path("report.md") |
|
|
html_path = Path("report.html") |
|
|
|
|
|
generate_markdown_report( |
|
|
md_path, stats, {}, rec, |
|
|
{"augmentations": [a.__dict__ for a in plan.augmentations]} |
|
|
) |
|
|
generate_html_report(md_path, html_path) |
|
|
|
|
|
st.success("Reports generated!") |
|
|
st.download_button("β¬οΈ Download Markdown", md_path.read_text(), file_name="report.md") |
|
|
st.download_button("β¬οΈ Download HTML", html_path.read_text(), file_name="report.html") |
|
|
except Exception as e: |
|
|
st.error(f"Error generating report: {e}") |
|
|
|
|
|
|
|
|
|
|
|
with tabs[4]: |
|
|
st.subheader("π TorchLoader Export") |
|
|
bytes_data = cache_uploaded_bytes() |
|
|
if bytes_data: |
|
|
pil_img, buf = load_uploaded_image_from_bytes(bytes_data) |
|
|
if pil_img is None: |
|
|
st.error("Uploaded file is not a valid image. Please upload a PNG/JPEG/etc.") |
|
|
else: |
|
|
try: |
|
|
rec = recommend_preprocessing(pil_img) |
|
|
snippet_or_transform = to_torch_transform({}, rec) |
|
|
|
|
|
if isinstance(snippet_or_transform, str): |
|
|
st.code(snippet_or_transform, language="python") |
|
|
else: |
|
|
st.success("β
torchvision.transforms.Compose object created") |
|
|
st.write(snippet_or_transform) |
|
|
except Exception as e: |
|
|
st.error(f"Error building Torch transform: {e}") |
|
|
else: |
|
|
st.info("Upload an image to export Torch transforms.") |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown( |
|
|
""" |
|
|
<div style="text-align: center;"> |
|
|
<p><b>Connect with me</b></p> |
|
|
<a href="https://instagram.com/stifler.xd" target="_blank" style="margin: 0 10px; text-decoration: none;"> |
|
|
<img src="https://cdn-icons-png.flaticon.com/512/2111/2111463.png" width="30"/> Instagram |
|
|
</a> |
|
|
<a href="https://github.com/STiFLeR7" target="_blank" style="margin: 0 10px; text-decoration: none;"> |
|
|
<img src="https://cdn-icons-png.flaticon.com/512/733/733553.png" width="30"/> GitHub |
|
|
</a> |
|
|
<a href="https://huggingface.co/STiFLeR7" target="_blank" style="margin: 0 10px; text-decoration: none;"> |
|
|
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" width="30"/> HuggingFace |
|
|
</a> |
|
|
<a href="https://medium.com/@stiflerxd" target="_blank" style="margin: 0 10px; text-decoration: none;"> |
|
|
<img src="https://cdn-icons-png.flaticon.com/512/5968/5968906.png" width="30"/> Medium |
|
|
</a> |
|
|
<a href="https://www.kaggle.com/stiflerxd" target="_blank" style="margin: 0 10px; text-decoration: none;"> |
|
|
<img src="https://cdn-icons-png.flaticon.com/512/2111/2111290.png" width="30"/> Kaggle |
|
|
</a> |
|
|
<br><br> |
|
|
π§ <a href="mailto:hillaniljppatel@gmail.com">hillaniljppatel@gmail.com</a> | |
|
|
π <a href="https://hillpatel.tech" target="_blank">hillpatel.tech</a> |
|
|
</div> |
|
|
""", |
|
|
unsafe_allow_html=True |
|
|
) |