evoneuralai commited on
Commit
74f0b48
·
verified ·
1 Parent(s): 945df47

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ .venv/
3
+ venv/
4
+ env/
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ .eggs/
9
+ dist/
10
+ build/
11
+
12
+ # Outputs and weights
13
+ outputs/
14
+ weights/
15
+ *.obj
16
+ *.glb
17
+ *.png
18
+ !scripts/**/*.png
19
+ TripoSR/
20
+ *.ckpt
21
+ *.safetensors
22
+
23
+ # IDE
24
+ .idea/
25
+ .vscode/
26
+ *.swp
27
+ *.swo
28
+
29
+ # Streamlit
30
+ .streamlit/
31
+
32
+ # Logs
33
+ *.log
34
+ performance_log.txt
.huggingfaceignore ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Exclude from Hugging Face upload (hf upload)
2
+ # Only code and config are uploaded; weights/outputs stay local.
3
+
4
+ # Python
5
+ .venv/
6
+ venv/
7
+ env/
8
+ __pycache__/
9
+ *.py[cod]
10
+ *.egg-info/
11
+ .eggs/
12
+ dist/
13
+ build/
14
+
15
+ # Outputs and model weights (large; users download separately)
16
+ outputs/
17
+ weights/
18
+ *.obj
19
+ *.glb
20
+ *.png
21
+ TripoSR/
22
+ *.ckpt
23
+ *.safetensors
24
+ *.bin
25
+
26
+ # IDE and editor
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+ *.swo
31
+
32
+ # Streamlit
33
+ .streamlit/
34
+
35
+ # Logs
36
+ *.log
37
+ performance_log.txt
38
+
39
+ # Git (keep .gitignore in repo)
40
+ .git/
README.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # selfhostedmodels
2
+
3
+ # Evoneural MVP – Local Mesh & Skybox
4
+
5
+ Localhost MVP for **text → 3D mesh** and **text → 360° skybox** using local models (no hosted APIs).
6
+
7
+ - **Mesh**: Text → image (Stable Diffusion) → 3D mesh (TripoSR). Output: `.obj` or `.glb`.
8
+ - **Skybox**: Text → 2:1 equirectangular image (Stable Diffusion). Optional seamless edge check.
9
+
10
+ **Default model:** `runwayml/stable-diffusion-v1-5` (no Hugging Face login required; first run downloads ~4GB).
11
+
12
+ ## Prerequisites
13
+
14
+ - **Python 3.10** (recommended) — [python.org](https://www.python.org/downloads/)
15
+ - **NVIDIA GPU** with CUDA (recommended; CPU is slower)
16
+ - **Git** (for cloning TripoSR; mesh only)
17
+
18
+ **No Conda?** Use **venv** (built into Python) — steps below.
19
+
20
+ ## 1. Environment
21
+
22
+ ### Option A: venv + pip (no Conda)
23
+
24
+ From PowerShell (project folder is `evoneural`):
25
+
26
+ ```powershell
27
+ cd D:\project\evoneural
28
+ python -m venv .venv
29
+ .venv\Scripts\Activate.ps1
30
+ pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
31
+ pip install -r requirements.txt
32
+ ```
33
+
34
+ - **CPU only:** use `pip install torch torchvision` (no `--index-url`).
35
+ - **CUDA 12.x:** use `cu121` instead of `cu118`.
36
+
37
+ ### Option B: Conda
38
+
39
+ ```powershell
40
+ cd D:\project\evoneural
41
+ conda env create -f environment.yml
42
+ conda activate evoneural-mvp
43
+ ```
44
+
45
+ If you use CPU-only or a different CUDA version, edit `environment.yml` (e.g. remove `pytorch-cuda=11.8` or set `pytorch-cuda=12.1`).
46
+
47
+ ## 2. TripoSR (for mesh)
48
+
49
+ Mesh generation needs the TripoSR repo and its dependencies.
50
+
51
+ ```powershell
52
+ cd D:\project\evoneural
53
+ git clone https://github.com/VAST-AI-Research/TripoSR.git TripoSR
54
+ pip install -r TripoSR/requirements.txt
55
+ ```
56
+
57
+ On Windows, if `torchmcubes` fails, see [TripoSR README](https://github.com/VAST-AI-Research/TripoSR#troubleshooting) (CUDA version match, then reinstall torchmcubes).
58
+
59
+ ## 2b. Stable Diffusion model (Hugging Face)
60
+
61
+ If you see **"Cannot load model ... model is not cached locally and an error occurred while trying to fetch metadata"**, the app cannot reach Hugging Face. Use one of these:
62
+
63
+ **Option 1 – Log in (uses cached token)**
64
+ From a terminal with internet:
65
+
66
+ ```powershell
67
+ huggingface-cli login
68
+ ```
69
+
70
+ Paste a token from [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) (read access is enough). Then run the app again.
71
+
72
+ **Option 2 – Set token in env**
73
+ Create a token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens), then:
74
+
75
+ ```powershell
76
+ $env:HF_TOKEN = "hf_xxxxxxxx"
77
+ streamlit run app.py
78
+ ```
79
+
80
+ **Option 3 – Download model once, then use offline**
81
+ On a machine that can reach Hugging Face:
82
+
83
+ ```powershell
84
+ cd D:\project\evoneural
85
+ .venv\Scripts\Activate.ps1
86
+ python -m scripts.download_sd_model
87
+ ```
88
+
89
+ Then set the path and run the app (no Hugging Face needed):
90
+
91
+ ```powershell
92
+ $env:SD_MODEL_PATH = "D:\project\evoneural\weights\stable-diffusion-2-1-base"
93
+ streamlit run app.py
94
+ ```
95
+
96
+ ## How it works
97
+
98
+ 1. **Skybox tab:** You enter a text prompt → the app loads Stable Diffusion (from cache or Hugging Face) → generates a 2:1 image → saves to `outputs/` and shows a download button. Optional “seamless” check compares left/right edges.
99
+ 2. **Mesh tab:** You enter a prompt (or upload an image) → the app generates an image with SD (if needed) → runs TripoSR on that image → outputs a `.obj` or `.glb` to `outputs/` (requires TripoSR repo cloned in `./TripoSR`).
100
+ 3. **Model loading:** The app first tries a local folder (`SD_MODEL_PATH` or `weights/stable-diffusion-2-1-base` if complete). If none, it loads `runwayml/stable-diffusion-v1-5` from the Hub (first run downloads the model; later runs use the cache). No token needed unless your network restricts Hugging Face.
101
+
102
+ ## 3. Run the app
103
+
104
+ From the project root (with venv activated):
105
+
106
+ ```powershell
107
+ cd D:\project\evoneural
108
+ .venv\Scripts\Activate.ps1
109
+ streamlit run app.py
110
+ ```
111
+
112
+ Open **http://localhost:8501**.
113
+
114
+ - **Text → 3D Mesh**: Enter a prompt (or upload an image). First run downloads SD 2.1 and TripoSR weights.
115
+ - **Text → Skybox**: Enter a prompt; image is 2:1 (e.g. 1024×512). Use “Run seamless edge check” to compare left/right edges.
116
+
117
+ Outputs are under `outputs/`. Use the download buttons to save mesh (`.glb`/`.obj`) and skybox (`.png`).
118
+
119
+ ## 4. Performance
120
+
121
+ - **Skybox**: ~6–8 GB VRAM (SD 2.1, 1024×512, FP16). Use 2048×1024 only if you have enough VRAM.
122
+ - **Mesh**: ~6 GB for TripoSR + ~6 GB for SD (text-to-image). Total peak can be ~10–12 GB if both run in same process.
123
+
124
+ If you run out of VRAM:
125
+
126
+ - Use 1024×512 for skybox.
127
+ - Close other GPU apps.
128
+ - Consider quantization (e.g. 8-bit) or CPU offload in diffusers (see [Optimization](#optimization)).
129
+
130
+ ## 5. Optimization (if VRAM is exceeded)
131
+
132
+ - **Quantization**: Use `load_in_8bit=True` or `load_in_4bit=True` with `bitsandbytes` where supported in diffusers.
133
+ - **Model CPU offload**: In diffusers, `pipe.enable_sequential_cpu_offload()` or `pipe.enable_model_cpu_offload()` to move parts to CPU and reduce peak VRAM (slower).
134
+ - **Smaller resolution**: 512×512 for text-to-image; 1024×512 for skybox.
135
+
136
+ ## Project layout
137
+
138
+ ```
139
+ evoneural/
140
+ ├── README.md
141
+ ├── app.py # Streamlit UI
142
+ ├── requirements.txt
143
+ ├── environment.yml
144
+ ├── scripts/
145
+ │ ├── skybox_generator.py
146
+ │ ├── mesh_generator.py
147
+ │ ├── text_to_image.py
148
+ │ └── check_seamless.py
149
+ ├── outputs/ # Generated meshes and skybox images
150
+ └── TripoSR/ # Clone here (see step 2)
151
+ ```
152
+
153
+ ## License
154
+
155
+ See TripoSR and Stable Diffusion model licenses (MIT / Stability). This MVP is for local use and evaluation.
app.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Evoneural MVP - Local 3D Mesh + Skybox Generation
3
+ Run: streamlit run app.py
4
+ Open: http://localhost:8501
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # Ensure project root is on path
12
+ ROOT = Path(__file__).resolve().parent
13
+ if str(ROOT) not in sys.path:
14
+ sys.path.insert(0, str(ROOT))
15
+
16
+ import streamlit as st
17
+
18
+ OUTPUTS = ROOT / "outputs"
19
+ OUTPUTS.mkdir(exist_ok=True)
20
+
21
+
22
+ def main() -> None:
23
+ st.set_page_config(
24
+ page_title="Evoneural MVP - Mesh & Skybox",
25
+ page_icon="🎮",
26
+ layout="wide",
27
+ )
28
+ st.title("Evoneural MVP – Local Mesh & Skybox")
29
+ st.caption("Text → 3D mesh (TripoSR) and Text → 360° skybox (Stable Diffusion). Runs on localhost.")
30
+
31
+ # Sidebar: model setup (token + download)
32
+ with st.sidebar:
33
+ st.subheader("Stable Diffusion model")
34
+ from scripts.skybox_generator import _default_local_weights_dir
35
+ local_model = _default_local_weights_dir()
36
+ if local_model:
37
+ st.success(f"Local model: found")
38
+ st.caption(os.path.basename(local_model))
39
+ else:
40
+ st.warning("No local model. Download below or need internet on first generate.")
41
+ hf_token = st.text_input(
42
+ "Hugging Face token (optional, if behind firewall)",
43
+ type="password",
44
+ key="hf_token",
45
+ placeholder="hf_...",
46
+ help="Get a token at huggingface.co/settings/tokens",
47
+ )
48
+ if hf_token:
49
+ os.environ["HF_TOKEN"] = hf_token
50
+ if st.button("Download model (~4GB to ./weights/sd-v1-5)", key="btn_download"):
51
+ with st.spinner("Downloading model... (may take several minutes)"):
52
+ try:
53
+ from scripts.download_sd_model import download_sd_model
54
+ path = download_sd_model(token=hf_token or os.environ.get("HF_TOKEN"))
55
+ st.success(f"Model saved. Try generating a skybox.")
56
+ st.rerun()
57
+ except Exception as e:
58
+ st.error(str(e))
59
+ st.caption("Set a Hugging Face token above if your network blocks Hugging Face.")
60
+
61
+ tab_mesh, tab_skybox = st.tabs(["🟦 Text → 3D Mesh", "🌅 Text → Skybox"])
62
+
63
+ with tab_mesh:
64
+ st.subheader("Generate 3D mesh from text")
65
+ st.markdown(
66
+ "Uses **Stable Diffusion** for text→image, then **TripoSR** for image→mesh. "
67
+ "TripoSR repo must be cloned into `./TripoSR` (see README)."
68
+ )
69
+ prompt_mesh = st.text_input(
70
+ "Prompt (e.g. for mesh)",
71
+ value="A highly detailed, sci-fi mechanical drone with glowing blue accents.",
72
+ key="mesh_prompt",
73
+ )
74
+ col1, col2 = st.columns(2)
75
+ with col1:
76
+ mesh_format = st.selectbox("Mesh format", ["glb", "obj"], key="mesh_fmt")
77
+ seed_mesh = st.number_input("Seed (optional)", value=42, min_value=0, key="mesh_seed")
78
+ with col2:
79
+ use_image = st.checkbox("Use uploaded image instead of text", value=False, key="use_img")
80
+ uploaded = st.file_uploader("Upload image for mesh", type=["png", "jpg"], key="mesh_upload") if use_image else None
81
+
82
+ if st.button("Generate mesh", key="btn_mesh"):
83
+ if not prompt_mesh.strip() and not use_image:
84
+ st.warning("Enter a prompt or upload an image.")
85
+ else:
86
+ with st.spinner("Running pipeline..."):
87
+ try:
88
+ from scripts.mesh_generator import (
89
+ generate_mesh_from_image,
90
+ generate_mesh_from_text,
91
+ find_triposr_root,
92
+ )
93
+ triposr_root = find_triposr_root(str(ROOT))
94
+ if not triposr_root:
95
+ st.error(
96
+ "TripoSR not found. Clone it: "
97
+ "`git clone https://github.com/VAST-AI-Research/TripoSR.git TripoSR`"
98
+ )
99
+ elif use_image and uploaded:
100
+ path = os.path.join(OUTPUTS, "uploaded_mesh_input.png")
101
+ with open(path, "wb") as f:
102
+ f.write(uploaded.getvalue())
103
+ mesh_path, elapsed, msg = generate_mesh_from_image(
104
+ path,
105
+ output_dir=str(OUTPUTS / "mesh_run"),
106
+ mesh_format=mesh_format,
107
+ )
108
+ if mesh_path:
109
+ st.success(f"Done in {elapsed:.1f}s. {msg}")
110
+ with open(mesh_path, "rb") as f:
111
+ st.download_button("Download mesh", f, file_name=os.path.basename(mesh_path), key="dl_mesh_upload")
112
+ else:
113
+ st.error(msg)
114
+ else:
115
+ mesh_path, elapsed, msg = generate_mesh_from_text(
116
+ prompt_mesh,
117
+ output_dir=str(OUTPUTS),
118
+ mesh_format=mesh_format,
119
+ seed=seed_mesh,
120
+ )
121
+ if mesh_path:
122
+ st.success(f"Done in {elapsed:.1f}s. {msg}")
123
+ with open(mesh_path, "rb") as f:
124
+ st.download_button("Download mesh", f, file_name=os.path.basename(mesh_path), key="dl_mesh")
125
+ else:
126
+ st.error(msg)
127
+ except Exception as e:
128
+ st.exception(e)
129
+
130
+ with tab_skybox:
131
+ st.subheader("Generate 2:1 equirectangular skybox")
132
+ st.markdown(
133
+ "Uses **Stable Diffusion 2.1** at 2:1 aspect (e.g. 1024×512). "
134
+ "Optional seamless check compares left/right edges."
135
+ )
136
+ prompt_sky = st.text_input(
137
+ "Prompt (e.g. for skybox)",
138
+ value="Cyberpunk city skyline at dusk, neon reflections, cinematic lighting.",
139
+ key="sky_prompt",
140
+ )
141
+ col1, col2 = st.columns(2)
142
+ with col1:
143
+ width = st.selectbox("Width", [1024, 2048], key="sky_w")
144
+ height = width // 2
145
+ seed_sky = st.number_input("Seed (optional)", value=42, min_value=0, key="sky_seed")
146
+ with col2:
147
+ check_seamless = st.checkbox("Run seamless edge check", value=True, key="seamless")
148
+
149
+ if st.button("Generate skybox", key="btn_sky"):
150
+ if not prompt_sky.strip():
151
+ st.warning("Enter a prompt.")
152
+ else:
153
+ with st.spinner("Generating skybox..."):
154
+ try:
155
+ from scripts.skybox_generator import generate_skybox
156
+ from scripts.check_seamless import check_seamless as run_seamless
157
+
158
+ out_path, elapsed, vram_mb = generate_skybox(
159
+ prompt_sky,
160
+ output_dir=str(OUTPUTS),
161
+ width=width,
162
+ height=height,
163
+ seed=seed_sky,
164
+ )
165
+ st.success(f"Done in {elapsed:.1f}s. Peak VRAM: {vram_mb:.0f} MB")
166
+ st.image(out_path, use_container_width=True)
167
+ with open(out_path, "rb") as f:
168
+ st.download_button("Download skybox", f, file_name=os.path.basename(out_path), key="dl_sky")
169
+
170
+ if check_seamless:
171
+ result = run_seamless(out_path)
172
+ st.info(result["message"])
173
+ except Exception as e:
174
+ st.exception(e)
175
+
176
+ st.divider()
177
+ st.caption("Evoneural AI – Local ML Deployment MVP. Models run locally (no API).")
178
+
179
+
180
+ if __name__ == "__main__":
181
+ main()
environment.yml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Conda environment for Evoneural MVP
2
+ # Create: conda env create -f environment.yml
3
+ # Activate: conda activate evoneural-mvp
4
+
5
+ name: evoneural-mvp
6
+ channels:
7
+ - pytorch
8
+ - nvidia
9
+ - conda-forge
10
+ - defaults
11
+ dependencies:
12
+ - python=3.10
13
+ - pip
14
+ - pytorch
15
+ - torchvision
16
+ - pytorch-cuda=11.8 # or 12.1; comment out if CPU-only
17
+ - pip:
18
+ - streamlit>=1.28.0
19
+ - diffusers>=0.25.0
20
+ - transformers>=4.35.0
21
+ - accelerate>=0.25.0
22
+ - safetensors>=0.4.0
23
+ - huggingface-hub>=0.20.0
24
+ - rembg>=2.0.50
25
+ - Pillow>=10.0.0
requirements.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Evoneural MVP - Local 3D Mesh + Skybox Generation
2
+ # Python 3.10 recommended. Install PyTorch with CUDA first: https://pytorch.org
3
+
4
+ # Core
5
+ torch>=2.0.0
6
+ torchvision>=0.15.0
7
+ Pillow>=10.0.0
8
+
9
+ # Streamlit UI
10
+ streamlit>=1.28.0
11
+
12
+ # Skybox: Stable Diffusion (diffusers)
13
+ diffusers>=0.25.0
14
+ transformers>=4.35.0
15
+ accelerate>=0.25.0
16
+ safetensors>=0.4.0
17
+
18
+ # Optional: reduce VRAM for skybox
19
+ # xformers # uncomment if you have CUDA and want lower VRAM
20
+
21
+ # Mesh: TripoSR dependencies (also need TripoSR repo cloned - see README)
22
+ huggingface-hub>=0.20.0
23
+ rembg>=2.0.50
24
+ numpy>=1.24.0
25
+
26
+ # Text-to-image for mesh (same as skybox stack)
27
+ # (already above)
28
+
29
+ # TripoSR-specific (install after cloning TripoSR, or use our subprocess runner)
30
+ # omegaconf==2.3.0
31
+ # einops==0.7.0
32
+ # trimesh>=4.0.0
33
+ # xatlas==0.0.9
scripts/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Evoneural MVP scripts
scripts/check_seamless.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Seamless check for 2:1 equirectangular skybox: compare left vs right edge.
3
+ Returns MSE and a simple pass/fail (low MSE = more seamless).
4
+ """
5
+
6
+ import numpy as np
7
+ from PIL import Image
8
+
9
+
10
+ def check_seamless(image_path: str, column_width: int = 5) -> dict:
11
+ """
12
+ Load image, compare left and right edge columns. Equirectangular wraps,
13
+ so left and right should match for a seamless skybox.
14
+ Returns dict with mse, passed (bool), and message.
15
+ """
16
+ img = np.array(Image.open(image_path).convert("RGB"))
17
+ h, w = img.shape[:2]
18
+
19
+ if w < 2 * column_width:
20
+ return {
21
+ "mse": float("inf"),
22
+ "passed": False,
23
+ "message": f"Image width {w} too small for column width {column_width}",
24
+ }
25
+
26
+ left = img[:, :column_width].astype(np.float32)
27
+ right = img[:, -column_width:].astype(np.float32)
28
+ mse = float(np.mean((left - right) ** 2))
29
+
30
+ # Heuristic: MSE < 100 often looks reasonably seamless
31
+ passed = mse < 100
32
+ message = (
33
+ f"Left/right edge MSE = {mse:.2f}. "
34
+ + ("Seamless (edges match)." if passed else "Edges differ (consider 360° model).")
35
+ )
36
+ return {"mse": mse, "passed": passed, "message": message}
scripts/download_sd_model.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Download Stable Diffusion v1.5 to ./weights/sd-v1-5 for offline use.
3
+ Run once (with internet). App auto-uses this folder if present.
4
+
5
+ python -m scripts.download_sd_model
6
+
7
+ Set HF_TOKEN=your_token if behind firewall. Can also use "Download model" in the app sidebar.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ ROOT = Path(__file__).resolve().parent.parent
15
+ MODEL_ID = "runwayml/stable-diffusion-v1-5"
16
+ DEFAULT_LOCAL_DIR = ROOT / "weights" / "sd-v1-5"
17
+
18
+
19
+ def download_sd_model(local_dir: str | Path | None = None, token: str | None = None) -> str:
20
+ """Download runwayml/stable-diffusion-v1-5 to local_dir. Returns path on success, raises on failure."""
21
+ from huggingface_hub import snapshot_download
22
+ out_dir = Path(local_dir or DEFAULT_LOCAL_DIR)
23
+ out_dir.mkdir(parents=True, exist_ok=True)
24
+ tok = token or os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
25
+ snapshot_download(
26
+ repo_id=MODEL_ID,
27
+ local_dir=str(out_dir),
28
+ token=tok,
29
+ )
30
+ return str(out_dir.resolve())
31
+
32
+
33
+ def main() -> None:
34
+ out_dir = os.environ.get("SD_MODEL_PATH", str(DEFAULT_LOCAL_DIR))
35
+ try:
36
+ path = download_sd_model(local_dir=out_dir)
37
+ print(f"Done. App will use: {path}")
38
+ print("Run: streamlit run app.py")
39
+ except Exception as e:
40
+ print(f"Download failed: {e}", file=sys.stderr)
41
+ print("Set HF_TOKEN=your_token if behind firewall (huggingface.co/settings/tokens)", file=sys.stderr)
42
+ raise SystemExit(1) from e
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
scripts/mesh_generator.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Mesh generator: image → 3D mesh via TripoSR (local).
3
+ Expects TripoSR repo cloned at project_root/TripoSR. Uses subprocess to run run.py.
4
+ For text→mesh: first generate image with text_to_image(), then call this.
5
+ """
6
+
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+
13
+
14
+ def find_triposr_root(project_root: str | None = None) -> str | None:
15
+ """Locate TripoSR repo: ./TripoSR or ../TripoSR from script dir."""
16
+ if project_root is None:
17
+ project_root = str(Path(__file__).resolve().parent.parent)
18
+ candidates = [
19
+ os.path.join(project_root, "TripoSR"),
20
+ os.path.join(project_root, "..", "TripoSR"),
21
+ ]
22
+ for p in candidates:
23
+ run_py = os.path.join(p, "run.py")
24
+ if os.path.isfile(run_py):
25
+ return p
26
+ return None
27
+
28
+
29
+ def generate_mesh_from_image(
30
+ image_path: str,
31
+ output_dir: str = "outputs",
32
+ mesh_format: str = "glb",
33
+ triposr_root: str | None = None,
34
+ device: str = "cuda:0",
35
+ ) -> tuple[str | None, float, str]:
36
+ """
37
+ Run TripoSR on an image. Returns (path_to_mesh, inference_time_sec, message).
38
+ If TripoSR is not found, returns (None, 0, error_message).
39
+ """
40
+ project_root = str(Path(__file__).resolve().parent.parent)
41
+ triposr_root = triposr_root or find_triposr_root(project_root)
42
+ if not triposr_root:
43
+ return (
44
+ None,
45
+ 0.0,
46
+ "TripoSR not found. Clone it: git clone https://github.com/VAST-AI-Research/TripoSR.git",
47
+ )
48
+
49
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
50
+ # TripoSR writes to output_dir/0/mesh.obj (or mesh.glb)
51
+ run_py = os.path.join(triposr_root, "run.py")
52
+ cmd = [
53
+ sys.executable,
54
+ run_py,
55
+ image_path,
56
+ "--output-dir",
57
+ output_dir,
58
+ "--model-save-format",
59
+ mesh_format,
60
+ "--device",
61
+ device,
62
+ ]
63
+
64
+ t0 = time.perf_counter()
65
+ try:
66
+ result = subprocess.run(
67
+ cmd,
68
+ cwd=triposr_root,
69
+ capture_output=True,
70
+ text=True,
71
+ timeout=120,
72
+ )
73
+ t1 = time.perf_counter()
74
+ if result.returncode != 0:
75
+ return (
76
+ None,
77
+ t1 - t0,
78
+ f"TripoSR failed: {result.stderr or result.stdout or 'unknown'}",
79
+ )
80
+ # Output is output_dir/0/mesh.glb or mesh.obj
81
+ mesh_path = os.path.join(output_dir, "0", f"mesh.{mesh_format}")
82
+ if not os.path.isfile(mesh_path):
83
+ return (None, t1 - t0, f"TripoSR did not produce {mesh_path}")
84
+ return (os.path.abspath(mesh_path), t1 - t0, "OK")
85
+ except subprocess.TimeoutExpired:
86
+ t1 = time.perf_counter()
87
+ return (None, t1 - t0, "TripoSR timed out (120s)")
88
+ except Exception as e:
89
+ t1 = time.perf_counter()
90
+ return (None, t1 - t0, str(e))
91
+
92
+
93
+ def generate_mesh_from_text(
94
+ prompt: str,
95
+ output_dir: str = "outputs",
96
+ mesh_format: str = "glb",
97
+ seed: int | None = None,
98
+ ) -> tuple[str | None, float, str]:
99
+ """
100
+ Text → image (SD) → mesh (TripoSR). Returns (path_to_mesh, total_time_sec, message).
101
+ """
102
+ # Import here so script can run from any cwd
103
+ _root = str(Path(__file__).resolve().parent.parent)
104
+ if _root not in sys.path:
105
+ sys.path.insert(0, _root)
106
+ from scripts.text_to_image import text_to_image
107
+
108
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
109
+ t0 = time.perf_counter()
110
+ try:
111
+ image_path, _ = text_to_image(prompt, output_dir=output_dir, seed=seed)
112
+ except Exception as e:
113
+ return (None, 0.0, f"Text-to-image failed: {e}")
114
+
115
+ mesh_path, mesh_time, msg = generate_mesh_from_image(
116
+ image_path,
117
+ output_dir=os.path.join(output_dir, "mesh_run"),
118
+ mesh_format=mesh_format,
119
+ )
120
+ total_time = time.perf_counter() - t0
121
+ if mesh_path:
122
+ return (mesh_path, total_time, msg)
123
+ return (None, total_time, msg)
scripts/skybox_generator.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Skybox generator: text → 2:1 equirectangular image (Stable Diffusion, local).
3
+ Uses FP16 to reduce VRAM. Output 1024x512 or 2048x1024.
4
+ """
5
+
6
+ import os
7
+ import time
8
+ from pathlib import Path
9
+
10
+ import torch
11
+
12
+ # Default: v1.5 works without license acceptance. Use SD_MODEL_ID to prefer SD 2.1.
13
+ DEFAULT_MODEL_ID = "runwayml/stable-diffusion-v1-5"
14
+ FALLBACK_MODEL_ID = "runwayml/stable-diffusion-v1-5" # Same; alternate if primary fails
15
+
16
+
17
+ def get_device() -> str:
18
+ return "cuda" if torch.cuda.is_available() else "cpu"
19
+
20
+
21
+ def _is_complete_sd_dir(path: Path) -> bool:
22
+ """True if path looks like a complete Stable Diffusion pipeline (has unet weights)."""
23
+ if not path.is_dir():
24
+ return False
25
+ unet = path / "unet"
26
+ if not unet.is_dir():
27
+ return False
28
+ return any(
29
+ (unet / f).exists()
30
+ for f in ("diffusion_pytorch_model.safetensors", "diffusion_pytorch_model.bin")
31
+ )
32
+
33
+
34
+ def _default_local_weights_dir() -> str | None:
35
+ """First complete SD folder under weights/ (sd-v1-5 or stable-diffusion-2-1-base)."""
36
+ try:
37
+ root = Path(__file__).resolve().parent.parent
38
+ for name in ("sd-v1-5", "stable-diffusion-2-1-base"):
39
+ local = root / "weights" / name
40
+ if _is_complete_sd_dir(local):
41
+ return str(local)
42
+ return None
43
+ except Exception:
44
+ return None
45
+
46
+
47
+ def _resolve_model_path_and_token():
48
+ """Use local path if set or default weights/ folder exists, else Hub id. Token from HF_TOKEN or huggingface-cli login."""
49
+ local = os.environ.get("SD_MODEL_PATH", "").strip()
50
+ if local and os.path.isdir(local):
51
+ return local, None
52
+ default_local = _default_local_weights_dir()
53
+ if default_local:
54
+ return default_local, None
55
+ model_id = os.environ.get("SD_MODEL_ID", DEFAULT_MODEL_ID)
56
+ token = os.environ.get("HF_TOKEN") or True # True = use cached login
57
+ return model_id, token
58
+
59
+
60
+ def generate_skybox(
61
+ prompt: str,
62
+ output_dir: str = "outputs",
63
+ width: int = 1024,
64
+ height: int = 512,
65
+ seed: int | None = None,
66
+ model_id: str | None = None,
67
+ ) -> tuple[str, float, float]:
68
+ """
69
+ Generate a 2:1 equirectangular skybox image from a text prompt.
70
+ Returns (path_to_image, inference_time_sec, peak_vram_mb).
71
+ """
72
+ from diffusers import StableDiffusionPipeline
73
+
74
+ device = get_device()
75
+ dtype = torch.float16 if device == "cuda" else torch.float32
76
+
77
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
78
+
79
+ pretrained, token = _resolve_model_path_and_token()
80
+ load_id = model_id or pretrained
81
+ local_only = os.path.isdir(load_id)
82
+ pipe = None
83
+ last_error = None
84
+
85
+ def _load(pid: str, local: bool) -> bool:
86
+ nonlocal pipe, last_error
87
+ try:
88
+ pipe = StableDiffusionPipeline.from_pretrained(
89
+ pid,
90
+ torch_dtype=dtype,
91
+ safety_checker=None,
92
+ token=None if local else (token or True),
93
+ local_files_only=local,
94
+ )
95
+ return True
96
+ except Exception as err:
97
+ last_error = err
98
+ return False
99
+
100
+ if _load(load_id, local_only):
101
+ pass
102
+ elif not local_only and _load(FALLBACK_MODEL_ID, False):
103
+ pass
104
+ if pipe is None:
105
+ raise RuntimeError(
106
+ "Could not load Stable Diffusion. Need internet to download the model (first run).\n"
107
+ " - Set HF_TOKEN=your_token if behind firewall (huggingface.co/settings/tokens)\n"
108
+ " - Or download once: huggingface-cli download runwayml/stable-diffusion-v1-5 --local-dir ./weights/sd-v1-5"
109
+ ) from last_error
110
+
111
+ pipe = pipe.to(device)
112
+
113
+ # Optional: enable xformers for lower VRAM (uncomment if installed)
114
+ # if device == "cuda":
115
+ # pipe.enable_xformers_memory_efficient_attention()
116
+
117
+ if device == "cuda":
118
+ torch.cuda.reset_peak_memory_stats()
119
+ torch.cuda.synchronize()
120
+
121
+ generator = None
122
+ if seed is not None:
123
+ generator = torch.Generator(device=device).manual_seed(seed)
124
+
125
+ t0 = time.perf_counter()
126
+ image = pipe(
127
+ prompt=prompt,
128
+ width=width,
129
+ height=height,
130
+ num_inference_steps=50,
131
+ generator=generator,
132
+ ).images[0]
133
+
134
+ if device == "cuda":
135
+ torch.cuda.synchronize()
136
+ t1 = time.perf_counter()
137
+ inference_time = t1 - t0
138
+ peak_vram_mb = (
139
+ torch.cuda.max_memory_allocated() / 1024 / 1024
140
+ if device == "cuda"
141
+ else 0.0
142
+ )
143
+
144
+ # Save with safe filename
145
+ safe_name = "".join(c if c.isalnum() or c in " -_" else "_" for c in prompt)[:60]
146
+ out_path = os.path.join(output_dir, f"skybox_{safe_name.strip()}.png")
147
+ image.save(out_path)
148
+
149
+ return out_path, inference_time, peak_vram_mb
scripts/text_to_image.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Text-to-image for mesh pipeline: generate a single image from prompt (SD 2.1, local).
3
+ Uses same SD_MODEL_PATH / HF_TOKEN as skybox_generator.
4
+ """
5
+
6
+ import os
7
+ import time
8
+ from pathlib import Path
9
+
10
+ import torch
11
+
12
+ from scripts.skybox_generator import _resolve_model_path_and_token, FALLBACK_MODEL_ID
13
+
14
+
15
+ def get_device() -> str:
16
+ return "cuda" if torch.cuda.is_available() else "cpu"
17
+
18
+
19
+ def text_to_image(
20
+ prompt: str,
21
+ output_dir: str = "outputs",
22
+ size: int = 512,
23
+ seed: int | None = None,
24
+ model_id: str | None = None,
25
+ ) -> tuple[str, float]:
26
+ """Generate one image from text. Returns (path_to_image, inference_time_sec)."""
27
+ from diffusers import StableDiffusionPipeline
28
+
29
+ device = get_device()
30
+ dtype = torch.float16 if device == "cuda" else torch.float32
31
+
32
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
33
+
34
+ pretrained, token = _resolve_model_path_and_token()
35
+ load_id = model_id or pretrained
36
+ local_only = os.path.isdir(load_id)
37
+ pipe = None
38
+ try:
39
+ pipe = StableDiffusionPipeline.from_pretrained(
40
+ load_id,
41
+ torch_dtype=dtype,
42
+ safety_checker=None,
43
+ token=None if local_only else (token or True),
44
+ local_files_only=local_only,
45
+ )
46
+ except Exception:
47
+ if not local_only:
48
+ try:
49
+ pipe = StableDiffusionPipeline.from_pretrained(
50
+ FALLBACK_MODEL_ID,
51
+ torch_dtype=dtype,
52
+ safety_checker=None,
53
+ token=token or True,
54
+ )
55
+ except Exception:
56
+ pass
57
+ if pipe is None:
58
+ raise RuntimeError(
59
+ "Could not load Stable Diffusion. Need internet (first run). Set HF_TOKEN if behind firewall."
60
+ )
61
+ pipe = pipe.to(device)
62
+
63
+ generator = None
64
+ if seed is not None:
65
+ generator = torch.Generator(device=device).manual_seed(seed)
66
+
67
+ t0 = time.perf_counter()
68
+ image = pipe(
69
+ prompt=prompt,
70
+ width=size,
71
+ height=size,
72
+ num_inference_steps=50,
73
+ generator=generator,
74
+ ).images[0]
75
+ t1 = time.perf_counter()
76
+
77
+ safe_name = "".join(c if c.isalnum() or c in " -_" else "_" for c in prompt)[:50]
78
+ out_path = os.path.join(output_dir, f"mesh_input_{safe_name.strip()}.png")
79
+ image.save(out_path)
80
+ return out_path, t1 - t0
scripts/upload_to_hf.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Upload evoneural code and config to Hugging Face (no weights/outputs).
3
+ Run from project root: python -m scripts.upload_to_hf
4
+
5
+ Requires: pip install huggingface_hub, and HF token (huggingface-cli login or HF_TOKEN).
6
+ """
7
+ from pathlib import Path
8
+
9
+ REPO_ID = "evoneural/evoneuralIn3D"
10
+ ROOT = Path(__file__).resolve().parent.parent
11
+
12
+ # Same exclusions as .huggingfaceignore so only code/config are uploaded
13
+ IGNORE_PATTERNS = [
14
+ ".venv/*",
15
+ "venv/*",
16
+ "env/*",
17
+ "__pycache__/*",
18
+ "*.pyc",
19
+ "outputs/*",
20
+ "weights/*",
21
+ "*.obj",
22
+ "*.glb",
23
+ "*.png",
24
+ "TripoSR/*",
25
+ "*.ckpt",
26
+ "*.safetensors",
27
+ "*.bin",
28
+ ".idea/*",
29
+ ".vscode/*",
30
+ ".streamlit/*",
31
+ "*.log",
32
+ "performance_log.txt",
33
+ ".git/*",
34
+ ]
35
+
36
+
37
+ def main() -> None:
38
+ from huggingface_hub import HfApi
39
+
40
+ api = HfApi()
41
+ # Create repo if it doesn't exist (needs write token)
42
+ api.create_repo(repo_id=REPO_ID, repo_type="model", exist_ok=True)
43
+ print(f"Uploading to {REPO_ID} (code and config only)...")
44
+ api.upload_folder(
45
+ folder_path=str(ROOT),
46
+ repo_id=REPO_ID,
47
+ repo_type="model",
48
+ ignore_patterns=IGNORE_PATTERNS,
49
+ )
50
+ print(f"Done. See https://huggingface.co/{REPO_ID}")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()