mac9087 commited on
Commit
c105678
·
verified ·
1 Parent(s): 49424fc

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -0
app.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from flask import Flask, request, jsonify, send_file
4
+ from werkzeug.utils import secure_filename
5
+ from PIL import Image
6
+ import io
7
+ import zipfile
8
+ from diffusers import ShapEImg2ImgPipeline
9
+ from diffusers.utils import export_to_obj
10
+
11
+ app = Flask(__name__)
12
+
13
+ # Configure upload folder
14
+ UPLOAD_FOLDER = 'uploads'
15
+ RESULTS_FOLDER = 'results'
16
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
17
+
18
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
19
+ os.makedirs(RESULTS_FOLDER, exist_ok=True)
20
+
21
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
22
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max
23
+
24
+ # Initialize the model (will download on first run)
25
+ device = "cuda" if torch.cuda.is_available() else "cpu"
26
+ pipe = ShapEImg2ImgPipeline.from_pretrained("openai/shap-e-img2img", torch_dtype=torch.float16)
27
+ pipe = pipe.to(device)
28
+
29
+ def allowed_file(filename):
30
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
31
+
32
+ @app.route('/health', methods=['GET'])
33
+ def health_check():
34
+ return jsonify({"status": "healthy", "model": "Shap-E Image to 3D"}), 200
35
+
36
+ @app.route('/convert', methods=['POST'])
37
+ def convert_image_to_3d():
38
+ # Check if image is in the request
39
+ if 'image' not in request.files:
40
+ return jsonify({"error": "No image provided"}), 400
41
+
42
+ file = request.files['image']
43
+ if file.filename == '':
44
+ return jsonify({"error": "No image selected"}), 400
45
+
46
+ if not allowed_file(file.filename):
47
+ return jsonify({"error": f"File type not allowed. Supported types: {', '.join(ALLOWED_EXTENSIONS)}"}), 400
48
+
49
+ # Get optional parameters
50
+ guidance_scale = float(request.form.get('guidance_scale', 3.0))
51
+ num_inference_steps = int(request.form.get('num_inference_steps', 64))
52
+ output_format = request.form.get('output_format', 'obj').lower()
53
+
54
+ # Validate output format
55
+ if output_format not in ['obj', 'glb']:
56
+ return jsonify({"error": "Unsupported output format. Use 'obj' or 'glb'"}), 400
57
+
58
+ try:
59
+ # Process image
60
+ filename = secure_filename(file.filename)
61
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
62
+ file.save(filepath)
63
+
64
+ # Open image
65
+ image = Image.open(filepath).convert("RGB")
66
+
67
+ # Generate 3D model
68
+ images = pipe(
69
+ image,
70
+ guidance_scale=guidance_scale,
71
+ num_inference_steps=num_inference_steps,
72
+ output_type="mesh",
73
+ ).images
74
+
75
+ # Create unique output directory
76
+ import uuid
77
+ output_id = str(uuid.uuid4())
78
+ output_dir = os.path.join(RESULTS_FOLDER, output_id)
79
+ os.makedirs(output_dir, exist_ok=True)
80
+
81
+ # Export to requested format
82
+ if output_format == 'obj':
83
+ obj_path = os.path.join(output_dir, "model.obj")
84
+ export_to_obj(images[0], obj_path)
85
+
86
+ # Create a zip file with OBJ and MTL
87
+ zip_path = os.path.join(output_dir, "model.zip")
88
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
89
+ zipf.write(obj_path, arcname="model.obj")
90
+ mtl_path = os.path.join(output_dir, "model.mtl")
91
+ if os.path.exists(mtl_path):
92
+ zipf.write(mtl_path, arcname="model.mtl")
93
+
94
+ return send_file(zip_path, as_attachment=True, download_name="model.zip")
95
+
96
+ elif output_format == 'glb':
97
+ # For GLB format, we need to convert the mesh
98
+ from trimesh import Trimesh
99
+ mesh = images[0]
100
+ vertices = mesh.verts
101
+ faces = mesh.faces
102
+
103
+ # Create a trimesh object
104
+ trimesh_obj = Trimesh(vertices=vertices, faces=faces)
105
+
106
+ # Export as GLB
107
+ glb_path = os.path.join(output_dir, "model.glb")
108
+ trimesh_obj.export(glb_path)
109
+
110
+ return send_file(glb_path, as_attachment=True, download_name="model.glb")
111
+
112
+ except Exception as e:
113
+ return jsonify({"error": str(e)}), 500
114
+
115
+ @app.route('/', methods=['GET'])
116
+ def index():
117
+ return """
118
+ <html>
119
+ <head>
120
+ <title>Image to 3D Model Converter</title>
121
+ <style>
122
+ body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
123
+ h1 { color: #333; }
124
+ form { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
125
+ label { display: block; margin: 10px 0 5px; }
126
+ input, select { margin-bottom: 10px; padding: 8px; width: 100%; }
127
+ button { background: #4CAF50; color: white; border: none; padding: 10px 15px; cursor: pointer; }
128
+ .api-info { background: #f5f5f5; padding: 15px; border-radius: 5px; }
129
+ pre { background: #eee; padding: 10px; overflow-x: auto; }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <h1>Image to 3D Model Converter</h1>
134
+
135
+ <form action="/convert" method="post" enctype="multipart/form-data">
136
+ <label for="image">Upload Image:</label>
137
+ <input type="file" id="image" name="image" accept=".png,.jpg,.jpeg" required>
138
+
139
+ <label for="guidance_scale">Guidance Scale (1.0-5.0):</label>
140
+ <input type="number" id="guidance_scale" name="guidance_scale" min="1.0" max="5.0" step="0.1" value="3.0">
141
+
142
+ <label for="num_inference_steps">Inference Steps (32-128):</label>
143
+ <input type="number" id="num_inference_steps" name="num_inference_steps" min="32" max="128" value="64">
144
+
145
+ <label for="output_format">Output Format:</label>
146
+ <select id="output_format" name="output_format">
147
+ <option value="obj">OBJ (for Unity)</option>
148
+ <option value="glb">GLB (for Three.js/Unreal)</option>
149
+ </select>
150
+
151
+ <button type="submit">Convert to 3D</button>
152
+ </form>
153
+
154
+ <div class="api-info">
155
+ <h2>API Documentation</h2>
156
+ <p>Endpoint: <code>/convert</code> (POST)</p>
157
+ <p>Parameters:</p>
158
+ <ul>
159
+ <li><code>image</code>: Image file (required)</li>
160
+ <li><code>guidance_scale</code>: Float between 1.0-5.0 (default: 3.0)</li>
161
+ <li><code>num_inference_steps</code>: Integer between 32-128 (default: 64)</li>
162
+ <li><code>output_format</code>: "obj" or "glb" (default: "obj")</li>
163
+ </ul>
164
+ <p>Example curl request:</p>
165
+ <pre>curl -X POST -F "image=@your_image.jpg" -F "output_format=obj" http://localhost:5000/convert -o model.zip</pre>
166
+ </div>
167
+ </body>
168
+ </html>
169
+ """
170
+
171
+ if __name__ == '__main__':
172
+ app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))