keeendaaa commited on
Commit
6d9cf18
·
1 Parent(s): bd1714d

zaebalo blyat

Browse files
__pycache__/local_service.cpython-314.pyc ADDED
Binary file (7.05 kB). View file
 
app.py CHANGED
@@ -307,7 +307,49 @@ def format_wd14_tags(tags: Dict[str, float], threshold: float = 0.35) -> str:
307
  def generate_3d(scribble_image_dict, prompt, scribble_confidence, text_confidence, seed):
308
  print("Generating 3D model...")
309
  input_prompt = prompt # Keep track of original prompt for return on early exit
310
- if scribble_image_dict is None or scribble_image_dict.get("composite") is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  print("No scribble image provided.")
312
  return None, input_prompt # Return None for model, original prompt
313
 
@@ -317,7 +359,7 @@ def generate_3d(scribble_image_dict, prompt, scribble_confidence, text_confidenc
317
  print("Prompt is empty, attempting WD14 tagging...")
318
  try:
319
  # Get the user drawing (black on white) for tagging
320
- user_drawing_img = Image.fromarray(scribble_image_dict["composite"]).convert("RGB")
321
  tag_results = wd14_tagger.interrogate(user_drawing_img)
322
  if tag_results:
323
  ratings, tags = tag_results
@@ -346,7 +388,7 @@ def generate_3d(scribble_image_dict, prompt, scribble_confidence, text_confidenc
346
  # --- Image Preprocessing for TripoSG ---
347
  # Get the composite image again (safer in case dict is modified)
348
  # The composite might be RGBA if a layer was involved, ensure RGB for processing
349
- image_for_triposg = Image.fromarray(scribble_image_dict["composite"]).convert("RGB")
350
  # Preprocess the image: invert colors (black on white -> white on black)
351
  image_np = np.array(image_for_triposg)
352
  processed_image_np = 255 - image_np
 
307
  def generate_3d(scribble_image_dict, prompt, scribble_confidence, text_confidence, seed):
308
  print("Generating 3D model...")
309
  input_prompt = prompt # Keep track of original prompt for return on early exit
310
+ if scribble_image_dict is None:
311
+ print("No scribble image provided.")
312
+ return None, input_prompt # Return None for model, original prompt
313
+
314
+ # ImageEditor can return composite=None for API calls; rebuild from background/layers if needed.
315
+ def _image_from_value(value):
316
+ if value is None:
317
+ return None
318
+ if isinstance(value, Image.Image):
319
+ return value
320
+ if isinstance(value, np.ndarray):
321
+ return Image.fromarray(value)
322
+ if isinstance(value, str):
323
+ try:
324
+ return Image.open(value)
325
+ except Exception:
326
+ return None
327
+ return None
328
+
329
+ def _build_composite(image_dict):
330
+ composite_img = _image_from_value(image_dict.get("composite"))
331
+ if composite_img is not None:
332
+ return composite_img
333
+
334
+ background = _image_from_value(image_dict.get("background"))
335
+ layers = image_dict.get("layers") or []
336
+ layer_images = [_image_from_value(layer) for layer in layers]
337
+ layer_images = [img for img in layer_images if img is not None]
338
+
339
+ if background is None and not layer_images:
340
+ return None
341
+
342
+ if background is None:
343
+ base = Image.new("RGBA", layer_images[0].size, (255, 255, 255, 255))
344
+ else:
345
+ base = background.convert("RGBA")
346
+
347
+ for layer_img in layer_images:
348
+ base = Image.alpha_composite(base, layer_img.convert("RGBA"))
349
+ return base.convert("RGB")
350
+
351
+ composite_image = _build_composite(scribble_image_dict)
352
+ if composite_image is None:
353
  print("No scribble image provided.")
354
  return None, input_prompt # Return None for model, original prompt
355
 
 
359
  print("Prompt is empty, attempting WD14 tagging...")
360
  try:
361
  # Get the user drawing (black on white) for tagging
362
+ user_drawing_img = composite_image.convert("RGB")
363
  tag_results = wd14_tagger.interrogate(user_drawing_img)
364
  if tag_results:
365
  ratings, tags = tag_results
 
388
  # --- Image Preprocessing for TripoSG ---
389
  # Get the composite image again (safer in case dict is modified)
390
  # The composite might be RGBA if a layer was involved, ensure RGB for processing
391
+ image_for_triposg = composite_image.convert("RGB")
392
  # Preprocess the image: invert colors (black on white -> white on black)
393
  image_np = np.array(image_for_triposg)
394
  processed_image_np = 255 - image_np
local_service.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import tempfile
5
+ import uuid
6
+ from typing import Dict
7
+
8
+ from fastapi import FastAPI, File, Form, UploadFile
9
+ from fastapi.responses import HTMLResponse, FileResponse, PlainTextResponse, RedirectResponse
10
+ from fastapi.staticfiles import StaticFiles
11
+ from gradio_client import Client, handle_file
12
+ from PIL import Image
13
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
14
+
15
+ APP_TITLE = "Text Sobirai - Local Viewer"
16
+ HF_SPACE = "kendaaa/text_sobirai"
17
+
18
+ app = FastAPI(title=APP_TITLE)
19
+ app.mount("/static", StaticFiles(directory="static"), name="static")
20
+
21
+ jinja_env = Environment(
22
+ loader=FileSystemLoader("templates"),
23
+ autoescape=select_autoescape(["html", "xml"]),
24
+ )
25
+
26
+ hf_token = os.environ.get("HF_TOKEN")
27
+ client = Client(HF_SPACE, hf_token=hf_token) if hf_token else Client(HF_SPACE)
28
+
29
+ model_store: Dict[str, str] = {}
30
+
31
+
32
+ def render_template(name: str, **context: object) -> HTMLResponse:
33
+ template = jinja_env.get_template(name)
34
+ return HTMLResponse(template.render(**context))
35
+
36
+
37
+ @app.get("/", response_class=HTMLResponse)
38
+ def index() -> HTMLResponse:
39
+ return render_template("index.html", title=APP_TITLE)
40
+
41
+
42
+ @app.post("/generate", response_class=HTMLResponse)
43
+ def generate(
44
+ prompt: str = Form(...),
45
+ scribble: UploadFile | None = File(None),
46
+ scribble_confidence: float = Form(0.4),
47
+ text_confidence: float = Form(1.0),
48
+ seed: int = Form(0),
49
+ ) -> HTMLResponse:
50
+ if scribble is None or not scribble.filename:
51
+ background = Image.new("RGB", (256, 256), "white")
52
+ layer = Image.new("RGBA", background.size, (0, 0, 0, 0))
53
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as scribble_tmp:
54
+ scribble_path = scribble_tmp.name
55
+ layer.save(scribble_path)
56
+ else:
57
+ suffix = os.path.splitext(scribble.filename or "scribble.png")[1] or ".png"
58
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as scribble_tmp:
59
+ scribble_path = scribble_tmp.name
60
+ scribble_tmp.write(scribble.file.read())
61
+ try:
62
+ with Image.open(scribble_path) as img:
63
+ background = Image.new("RGB", img.size, "white")
64
+ except Exception:
65
+ background = Image.new("RGB", (256, 256), "white")
66
+
67
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as bg_tmp:
68
+ background_path = bg_tmp.name
69
+ background.save(background_path)
70
+
71
+ result = client.predict(
72
+ scribble_image_dict={
73
+ "background": handle_file(background_path),
74
+ "layers": [handle_file(scribble_path)],
75
+ "composite": None,
76
+ "id": None,
77
+ },
78
+ prompt=prompt,
79
+ scribble_confidence=float(scribble_confidence),
80
+ text_confidence=float(text_confidence),
81
+ seed=int(seed),
82
+ api_name="/generate_3d",
83
+ )
84
+
85
+ model_path, used_prompt = result
86
+ if not model_path:
87
+ return render_template(
88
+ "result.html",
89
+ title=APP_TITLE,
90
+ error="Model was not generated. Check the Space logs.",
91
+ prompt=used_prompt or prompt,
92
+ model_url=None,
93
+ )
94
+
95
+ model_id = str(uuid.uuid4())
96
+ model_store[model_id] = model_path
97
+ model_url = f"/models/{model_id}"
98
+ return render_template(
99
+ "result.html",
100
+ title=APP_TITLE,
101
+ error=None,
102
+ prompt=used_prompt or prompt,
103
+ model_url=model_url,
104
+ )
105
+
106
+
107
+ @app.get("/models/{model_id}")
108
+ def get_model(model_id: str):
109
+ path = model_store.get(model_id)
110
+ if not path:
111
+ return PlainTextResponse("model not found", status_code=404)
112
+ return FileResponse(path, media_type="model/gltf-binary")
113
+
114
+
115
+ @app.get("/generate")
116
+ def generate_get() -> RedirectResponse:
117
+ return RedirectResponse(url="/")
118
+
119
+
120
+ if __name__ == "__main__":
121
+ import uvicorn
122
+
123
+ uvicorn.run("local_service:app", host="0.0.0.0", port=8000, reload=True)
requirements.local.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ jinja2
4
+ python-multipart
5
+ gradio_client
6
+ pillow
static/styles.css ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg: #f7f4f0;
3
+ --panel: #ffffff;
4
+ --text: #1d1b1a;
5
+ --accent: #2b6f65;
6
+ --border: #e5e0d8;
7
+ }
8
+
9
+ * {
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ body {
14
+ margin: 0;
15
+ font-family: "Iowan Old Style", "Garamond", "Times New Roman", serif;
16
+ color: var(--text);
17
+ background: linear-gradient(135deg, #f7f4f0, #efe9e1);
18
+ }
19
+
20
+ .page {
21
+ max-width: 980px;
22
+ margin: 0 auto;
23
+ padding: 40px 20px 60px;
24
+ }
25
+
26
+ .hero {
27
+ text-align: center;
28
+ margin-bottom: 32px;
29
+ }
30
+
31
+ .hero h1 {
32
+ font-size: 2.4rem;
33
+ margin: 0 0 8px;
34
+ }
35
+
36
+ .panel {
37
+ background: var(--panel);
38
+ border: 1px solid var(--border);
39
+ border-radius: 16px;
40
+ padding: 24px;
41
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.06);
42
+ }
43
+
44
+ .form {
45
+ display: grid;
46
+ gap: 16px;
47
+ }
48
+
49
+ .field {
50
+ display: grid;
51
+ gap: 6px;
52
+ font-size: 0.95rem;
53
+ }
54
+
55
+ .row {
56
+ display: grid;
57
+ gap: 16px;
58
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
59
+ }
60
+
61
+ input[type="text"],
62
+ input[type="number"],
63
+ input[type="file"] {
64
+ padding: 10px 12px;
65
+ border-radius: 10px;
66
+ border: 1px solid var(--border);
67
+ font-size: 1rem;
68
+ }
69
+
70
+ button {
71
+ background: var(--accent);
72
+ color: white;
73
+ border: none;
74
+ padding: 12px 18px;
75
+ border-radius: 12px;
76
+ font-size: 1rem;
77
+ cursor: pointer;
78
+ }
79
+
80
+ button:hover {
81
+ opacity: 0.92;
82
+ }
83
+
84
+ .viewer {
85
+ width: 100%;
86
+ height: 520px;
87
+ background: #fafafa;
88
+ border-radius: 12px;
89
+ }
90
+
91
+ .error {
92
+ color: #9a2c2c;
93
+ font-weight: 600;
94
+ }
95
+
96
+ @media (max-width: 640px) {
97
+ .viewer {
98
+ height: 360px;
99
+ }
100
+ }
templates/index.html ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{ title }}</title>
7
+ <link rel="stylesheet" href="/static/styles.css" />
8
+ </head>
9
+ <body>
10
+ <main class="page">
11
+ <header class="hero">
12
+ <h1>{{ title }}</h1>
13
+ <p>Локальный просмотр 3D модели из Space на Hugging Face.</p>
14
+ </header>
15
+
16
+ <section class="panel">
17
+ <form class="form" action="/generate" method="post" enctype="multipart/form-data">
18
+ <label class="field">
19
+ <span>Prompt</span>
20
+ <input type="text" name="prompt" placeholder="Например: a small chair" required />
21
+ </label>
22
+ <label class="field">
23
+ <span>Scribble (PNG, опционально)</span>
24
+ <input type="file" name="scribble" accept="image/*" />
25
+ </label>
26
+ <div class="row">
27
+ <label class="field">
28
+ <span>Scribble confidence</span>
29
+ <input type="number" name="scribble_confidence" step="0.05" min="0" max="1" value="0.4" />
30
+ </label>
31
+ <label class="field">
32
+ <span>Prompt confidence</span>
33
+ <input type="number" name="text_confidence" step="0.05" min="0" max="1" value="1.0" />
34
+ </label>
35
+ <label class="field">
36
+ <span>Seed</span>
37
+ <input type="number" name="seed" value="0" />
38
+ </label>
39
+ </div>
40
+ <button type="submit">Сгенерировать</button>
41
+ </form>
42
+ </section>
43
+ </main>
44
+ </body>
45
+ </html>
templates/result.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{ title }}</title>
7
+ <link rel="stylesheet" href="/static/styles.css" />
8
+ <script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
9
+ </head>
10
+ <body>
11
+ <main class="page">
12
+ <header class="hero">
13
+ <h1>{{ title }}</h1>
14
+ <p>Prompt: <strong>{{ prompt }}</strong></p>
15
+ <p><a href="/">Сгенерировать еще</a></p>
16
+ </header>
17
+
18
+ <section class="panel">
19
+ {% if error %}
20
+ <p class="error">{{ error }}</p>
21
+ {% else %}
22
+ <model-viewer
23
+ class="viewer"
24
+ src="{{ model_url }}"
25
+ auto-rotate
26
+ camera-controls
27
+ shadow-intensity="0.8"
28
+ exposure="1.0"
29
+ ></model-viewer>
30
+ {% endif %}
31
+ </section>
32
+ </main>
33
+ </body>
34
+ </html>