jasonod888 commited on
Commit
a0b64e6
·
1 Parent(s): e81b056

Initial commit: Marigold Lighting handler + requirements

Browse files
Files changed (4) hide show
  1. .gitattributes +2 -33
  2. README.md +39 -0
  3. handler.py +169 -0
  4. requirements.txt +7 -0
.gitattributes CHANGED
@@ -1,35 +1,4 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ *.pt filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
4
  *.onnx filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Marigold Intrinsic Image Decomposition
3
+ emoji: 🏵️
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 4.44.1
8
+ app_file: app.py
9
+ pinned: true
10
+ license: apache-2.0
11
+ models:
12
+ - prs-eth/marigold-iid-appearance-v1-1
13
+ - prs-eth/marigold-iid-lighting-v1-1
14
+ ---
15
+
16
+ This is a demo of Marigold-IID, the state-of-the-art intrinsic image decomposition model for images in the wild.
17
+ We provide two models:
18
+ - Marigold-IID-Appearance which predicts albedo, metallic and roughness
19
+ - Marigold-IID-Lighting which predicts albedo, diffuse shading and non-diffuse residual
20
+
21
+ Find out more in our paper titled ["Marigold: Affordable Adaptation of Diffusion-Based Image Generators for Image Analysis"](https://arxiv.org/abs/2505.09358)
22
+
23
+ ```
24
+ @InProceedings{ke2023repurposing,
25
+ title={Repurposing Diffusion-Based Image Generators for Monocular Depth Estimation},
26
+ author={Bingxin Ke and Anton Obukhov and Shengyu Huang and Nando Metzger and Rodrigo Caye Daudt and Konrad Schindler},
27
+ booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
28
+ year={2024}
29
+ }
30
+
31
+ @misc{ke2025marigold,
32
+ title={Marigold: Affordable Adaptation of Diffusion-Based Image Generators for Image Analysis},
33
+ author={Bingxin Ke and Kevin Qu and Tianfu Wang and Nando Metzger and Shengyu Huang and Bo Li and Anton Obukhov and Konrad Schindler},
34
+ year={2025},
35
+ eprint={2505.09358},
36
+ archivePrefix={arXiv},
37
+ primaryClass={cs.CV}
38
+ }
39
+ ```
handler.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64, io, os
2
+ from typing import Any, Dict, List, Union
3
+
4
+ import torch
5
+ import numpy as np
6
+ from PIL import Image
7
+
8
+ from diffusers import MarigoldIntrinsicsPipeline
9
+ from diffusers.utils import load_image
10
+
11
+
12
+ def _decode_image(inp: Union[str, bytes]) -> Image.Image:
13
+ """
14
+ Accepts:
15
+ - https/http URL
16
+ - data: URI or bare base64 string of an image
17
+ """
18
+ if isinstance(inp, bytes):
19
+ return Image.open(io.BytesIO(inp)).convert("RGB")
20
+
21
+ if isinstance(inp, str) and (inp.startswith("http://") or inp.startswith("https://")):
22
+ return load_image(inp).convert("RGB")
23
+
24
+ # base64 (with or without data: prefix)
25
+ if isinstance(inp, str) and "," in inp and inp.strip().lower().startswith("data:"):
26
+ inp = inp.split(",", 1)[1]
27
+ data = base64.b64decode(inp)
28
+ return Image.open(io.BytesIO(data)).convert("RGB")
29
+
30
+
31
+ def _b64_png(img: Image.Image) -> str:
32
+ buf = io.BytesIO()
33
+ img.save(buf, format="PNG")
34
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
35
+
36
+
37
+ class EndpointHandler:
38
+ """
39
+ Hugging Face Inference Endpoints entrypoint.
40
+ Loads Marigold *Intrinsic Image Decomposition* (Lighting) and exposes a JSON API.
41
+
42
+ Request JSON:
43
+ {
44
+ "inputs": "<image-url OR base64>",
45
+ "parameters": {
46
+ "num_inference_steps": 4, # optional; DDIM or LCM schedules handle 1–50 (docs)
47
+ "processing_resolution": 768, # optional; sensible default comes from model
48
+ "match_input_resolution": True, # optional
49
+ "ensemble_size": 1, # >1 enables 'uncertainty'
50
+ "return_visualizations": true # if true, returns PNGs for albedo/shading/residual
51
+ }
52
+ }
53
+
54
+ Response JSON:
55
+ {
56
+ "outputs": {
57
+ "targets": ["albedo", "diffuse_shading", "residual"],
58
+ "visualizations": { "albedo": "<b64png>", ... }, # if requested
59
+ "tensors_info": { "shape": [N, 3, H, W], "dtype": "float16/float32" },
60
+ "uncertainty_available": true/false
61
+ }
62
+ }
63
+ """
64
+
65
+ def __init__(self, model_path: str = ""):
66
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
67
+
68
+ # Load the Lighting (IID) checkpoint with fp16 for speed, as in the docs.
69
+ # Model card: prs-eth/marigold-iid-lighting-v1-1
70
+ # https://huggingface.co/prs-eth/marigold-iid-lighting-v1-1
71
+ repo_id = os.environ.get("MARIGOLD_REPO_ID", "prs-eth/marigold-iid-lighting-v1-1")
72
+
73
+ self.pipe = MarigoldIntrinsicsPipeline.from_pretrained(
74
+ repo_id, variant="fp16", torch_dtype=torch.float16
75
+ ).to(self.device)
76
+
77
+ # Optional: speed tweak recommended by docs — tiny VAE
78
+ # (works across Marigold pipelines; comment out if you prefer default VAE)
79
+ try:
80
+ from diffusers import AutoencoderTiny
81
+ self.pipe.vae = AutoencoderTiny.from_pretrained(
82
+ "madebyollin/taesd", torch_dtype=torch.float16
83
+ ).to(self.device)
84
+ except Exception as _:
85
+ pass
86
+
87
+ # Disable progress bars in server context
88
+ self.pipe.set_progress_bar_config(disable=True)
89
+
90
+ def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
91
+ if not isinstance(data, dict) or "inputs" not in data:
92
+ return {"error": "Expected JSON with an 'inputs' field."}
93
+
94
+ params = data.get("parameters", {}) or {}
95
+ return_visuals = bool(params.get("return_visualizations", True))
96
+
97
+ # Map supported pipeline kwargs exactly as in diffusers:
98
+ # - num_inference_steps
99
+ # - processing_resolution
100
+ # - match_input_resolution
101
+ # - ensemble_size
102
+ # - generator (omitted here; could be added if you pass a seed)
103
+ call_kwargs = {}
104
+ for k in (
105
+ "num_inference_steps",
106
+ "processing_resolution",
107
+ "match_input_resolution",
108
+ "ensemble_size",
109
+ "output_uncertainty",
110
+ "output_latent",
111
+ ):
112
+ if k in params:
113
+ call_kwargs[k] = params[k]
114
+
115
+ try:
116
+ image = _decode_image(data["inputs"])
117
+ except Exception as e:
118
+ return {"error": f"Failed to decode 'inputs': {e}"}
119
+
120
+ # Run the pipeline. For Intrinsics, output contains:
121
+ # - prediction: (num_targets * N) x 3 x H x W in [0,1]
122
+ # - target_properties: names + visualization hints
123
+ # - uncertainty: present if ensemble_size > 2
124
+ try:
125
+ out = self.pipe(image, **call_kwargs)
126
+ except TypeError as e:
127
+ # Common pitfall: wrong arg names (e.g. 'processing_res' instead of 'processing_resolution')
128
+ return {"error": f"Pipeline call failed: {e}"}
129
+ except Exception as e:
130
+ return {"error": f"Inference failed: {e}"}
131
+
132
+ # Targets (order from target_properties)
133
+ target_props = getattr(out, "target_properties", None)
134
+ target_names: List[str] = []
135
+ if target_props and isinstance(target_props, dict):
136
+ target_names = list(target_props.get("target_names", []))
137
+
138
+ resp: Dict[str, Any] = {
139
+ "targets": target_names or ["albedo", "diffuse_shading", "residual"],
140
+ "tensors_info": None,
141
+ "uncertainty_available": getattr(out, "uncertainty", None) is not None,
142
+ }
143
+
144
+ # Shapes / dtype
145
+ pred = out.prediction # torch.Tensor or np.ndarray
146
+ if isinstance(pred, torch.Tensor):
147
+ n, c, h, w = pred.shape
148
+ resp["tensors_info"] = {"shape": [int(n), int(c), int(h), int(w)], "dtype": str(pred.dtype).replace("torch.", "")}
149
+ elif isinstance(pred, np.ndarray):
150
+ h, w, c = pred.shape[-3:]
151
+ resp["tensors_info"] = {"shape": list(pred.shape), "dtype": str(pred.dtype)}
152
+
153
+ # Optional visualizations using the official helper
154
+ if return_visuals:
155
+ try:
156
+ vis_list = self.pipe.image_processor.visualize_intrinsics(
157
+ out.prediction, target_props
158
+ ) # -> List[Dict[str, PIL.Image]]
159
+ # Usually returns a single dict with keys matching target_names
160
+ visuals = {}
161
+ if isinstance(vis_list, list) and len(vis_list) > 0 and isinstance(vis_list[0], dict):
162
+ for k, v in vis_list[0].items():
163
+ if isinstance(v, Image.Image):
164
+ visuals[k] = _b64_png(v)
165
+ resp["visualizations"] = visuals
166
+ except Exception as e:
167
+ resp["visualizations_error"] = f"Failed to visualize: {e}"
168
+
169
+ return {"outputs": resp}
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ torch>=2.1
2
+ accelerate>=0.28
3
+ diffusers>=0.35.0
4
+ transformers>=4.44
5
+ Pillow>=10.3
6
+ safetensors>=0.4.2
7
+ numpy>=1.26