Spaces:
Running
A newer version of the Gradio SDK is available:
6.1.0
title: Image to STL Converter API
emoji: π
colorFrom: indigo
colorTo: yellow
sdk: gradio
sdk_version: 4.44.1
app_file: app.py
pinned: false
license: apache-2.0
short_description: Convert 2D images into 3D STL heightmaps via REST API.
Image to STL Converter API
This Space exposes an API-only backend for converting 2D images into 3D
binary STL heightmaps using the
depth-anything/Depth-Anything-V2-Small-hf
model. It is designed to be called from external applications such as a
Next.js frontend.
Under the hood, the pipeline is:
Image β Depth Map β 3D Mesh β STL file
The Gradio interface is primarily used to expose a REST API endpoint. A minimal web UI is still available for quick manual testing.
Model
- Model:
depth-anything/Depth-Anything-V2-Small-hf - Task:
depth-estimation - Framework:
transformers,torch
The model is loaded globally when the Space starts, so repeated requests do not reload the model.
API Endpoint
URL
- Method:
POST - Path:
/api/convert - Content-Type:
multipart/form-data
On Hugging Face Spaces, the full URL will look like:
curl -X POST https://YOUR-SPACE.hf.space/api/convert \
-F "[email protected]" \
-F "height_scale=20" \
-F "base_thickness=2" \
-o output.stl
Replace YOUR-SPACE with the actual Space subdomain (for example,
lobster2154-image-to-stl).
Request Parameters
All parameters are sent as multipart/form-data fields.
image(required)- Type: file
- Description: Input image to be converted to a 3D relief.
height_scale(optional)- Type: number (float)
- Default:
20 - Range:
5β50 - Description: Scales the normalized depth values to control the maximum height of the relief.
base_thickness(optional)- Type: number (float)
- Default:
2 - Range:
1β10 - Description: Thickness of the solid base added under the relief.
Response
Status 200 (success)
- Body: Binary STL file representing a watertight 3D mesh.
- Headers:
Content-Type: application/sla(ormodel/stl, depending on proxy)Content-Disposition: attachment; filename="output.stl"
Status 4xx / 5xx (error)
Body: JSON error object produced by Gradio, for example:
{"error": "Conversion failed: <detailed message>"}
Typical error causes include:
- Missing image file
- Non-numeric or non-positive
height_scale/base_thickness - Model initialization issues
Processing Details
Depth estimation
- The input image is passed into the depth-estimation pipeline.
- The pipeline returns
predicted_depth, which is converted to a NumPy array.
Normalization
Depth values are normalized to
[0, 1]using:normalized = (depth - depth.min()) / (depth.max() - depth.min())
Downsampling
- For performance, the depth map is downsampled to a maximum of 150 Γ 150 pixels using LANCZOS interpolation if the original is larger.
Mesh generation
- A top surface is created from the heightmap.
- A flat bottom surface is added (
z = 0). - Four side walls are generated (left, right, front, back) to produce a watertight mesh.
STL export
- The mesh is converted to a
numpy-stlmesh and saved as a binary STL file, which is then streamed back to the client as a file download.
- The mesh is converted to a
Processing is designed to complete within ~30 seconds for typical image sizes thanks to the downsampling step.
Example Usage (cURL)
curl -X POST https://YOUR-SPACE.hf.space/api/convert \
-F "[email protected]" \
-F "height_scale=20" \
-F "base_thickness=2" \
-o output.stl
[email protected]: The image to convert.height_scale: Optional; controls relief height.base_thickness: Optional; controls the base thickness.output.stl: The STL file to be written locally.
Next.js Integration Example
Below is an example of calling this Space from a Next.js app using fetch.
Client-side call (e.g. inside a React component)
// Example client-side call from a React component
async function uploadImageAndDownloadStl(imageFile: File) {
const formData = new FormData();
formData.append("image", imageFile);
formData.append("height_scale", "20");
formData.append("base_thickness", "2");
const response = await fetch("https://YOUR-SPACE.hf.space/api/convert", {
method: "POST",
body: formData,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Conversion failed: ${errorText}`);
}
const stlBlob = await response.blob();
const url = URL.createObjectURL(stlBlob);
// Trigger download in the browser
const a = document.createElement("a");
a.href = url;
a.download = "output.stl";
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
Example Next.js API Route (App Router)
// app/api/convert-image/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const formData = await req.formData();
const image = formData.get("image") as File | null;
if (!image) {
return NextResponse.json({ error: "Image file is required" }, { status: 400 });
}
const relayFormData = new FormData();
relayFormData.append("image", image);
relayFormData.append("height_scale", (formData.get("height_scale") ?? "20") as string);
relayFormData.append(
"base_thickness",
(formData.get("base_thickness") ?? "2") as string,
);
const hfResponse = await fetch("https://YOUR-SPACE.hf.space/api/convert", {
method: "POST",
body: relayFormData,
});
if (!hfResponse.ok) {
const errorText = await hfResponse.text();
return NextResponse.json(
{ error: "Conversion failed", details: errorText },
{ status: hfResponse.status },
);
}
const stlArrayBuffer = await hfResponse.arrayBuffer();
return new NextResponse(stlArrayBuffer, {
status: 200,
headers: {
"Content-Type": "application/sla",
"Content-Disposition": "attachment; filename=output.stl",
},
});
}
In your frontend, you can then call /api/convert-image from React components
without exposing the Hugging Face Space URL directly.
Error Handling
The backend performs several validations and provides descriptive error messages:
- Missing image β 400-like error with message
"No image was provided. Please upload an image file." - Invalid parameters β error if
height_scaleorbase_thicknessare non-numeric or non-positive. - Model not loaded β error if the depth model failed to initialize.
- Internal errors β any unexpected exception is logged and returned as a
generic
"Conversion failed: ..."message.
These errors are surfaced via Gradio's API as JSON responses with non-200 HTTP status codes, making them easy to handle from your Next.js frontend.