MichaelMintIcecream commited on
Commit
26dfbe2
·
verified ·
1 Parent(s): 76b6655

diagnostic: /point perception probe (Molmo2-ER pointing)

Browse files
Files changed (1) hide show
  1. molmoact2_server.py +60 -0
molmoact2_server.py CHANGED
@@ -164,6 +164,66 @@ def health() -> dict:
164
  return _status()
165
 
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  @app.post("/act", response_model=ActResponse)
168
  def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResponse:
169
  # Constant-time compare so a bad token can't be recovered via response timing.
 
164
  return _status()
165
 
166
 
167
+ class PointRequest(BaseModel):
168
+ image: str # base64 JPEG/PNG, one camera view
169
+ query: str = "the red cup"
170
+ max_new_tokens: int = 96
171
+
172
+
173
+ class PointResponse(BaseModel):
174
+ raw: str # the VLM's verbatim generation
175
+ points: list[list[float]] # parsed [[x, y], ...] in PERCENT of image size
176
+ compute_ms: Optional[float] = None
177
+
178
+
179
+ @app.post("/point", response_model=PointResponse)
180
+ def point(req: PointRequest, authorization: Optional[str] = Header(None)) -> PointResponse:
181
+ """Perception probe (diagnostic, not on the control path): ask the Molmo2-ER
182
+ backbone — a pixel-accurate pointing model — to point at `query` in ONE
183
+ frame. Separates "does the model SEE the target in our camera domain" from
184
+ "does it act correctly": wrong/absent points on live robot frames = visual
185
+ domain gap (no calibration work can fix it); correct points + wrong motion
186
+ = the failure is downstream of perception."""
187
+ if not authorization or not secrets.compare_digest(authorization, f"Bearer {AUTH_TOKEN}"):
188
+ raise HTTPException(status_code=401, detail="bad or missing bearer token")
189
+ if _model is None:
190
+ detail = f"model load failed: {_load_error}" if _load_error else "model not loaded yet"
191
+ raise HTTPException(status_code=503, detail=detail)
192
+ img = Image.fromarray(_decode(req.image))
193
+ prompt = f"Point to {req.query}."
194
+ t0 = time.time()
195
+ try:
196
+ with _lock, torch.inference_mode():
197
+ # Preferred: the processor's chat template (Molmo2 family). Fallback:
198
+ # the classic Molmo processor.process() API. Both produce tensors the
199
+ # underlying ImageTextToText model can generate from.
200
+ try:
201
+ inputs = _processor.apply_chat_template(
202
+ [{"role": "user",
203
+ "content": [{"type": "image", "image": img},
204
+ {"type": "text", "text": prompt}]}],
205
+ add_generation_prompt=True, tokenize=True,
206
+ return_dict=True, return_tensors="pt")
207
+ except Exception:
208
+ inputs = _processor.process(images=[img], text=prompt)
209
+ inputs = {k: (v.unsqueeze(0) if hasattr(v, "dim") and v.dim() in (1, 3) else v)
210
+ for k, v in inputs.items()}
211
+ inputs = {k: (v.to(_model.device) if hasattr(v, "to") else v)
212
+ for k, v in inputs.items()}
213
+ out = _model.generate(**inputs, max_new_tokens=int(req.max_new_tokens))
214
+ n_in = inputs["input_ids"].shape[1] if "input_ids" in inputs else 0
215
+ text = _processor.tokenizer.decode(out[0][n_in:], skip_special_tokens=False)
216
+ except Exception as exc:
217
+ raise HTTPException(status_code=500, detail=f"pointing failed: {type(exc).__name__}: {exc}")
218
+ # Parse Molmo point markup: <point x="53.1" y="42.2" ...> (single) and the
219
+ # <points x1=".." y1=".." x2=".." ...> multi-point form. Percent coordinates.
220
+ import re
221
+ pts = [[float(x), float(y)] for x, y in
222
+ re.findall(r'x\d*="([0-9.]+)"\s+y\d*="([0-9.]+)"', text)]
223
+ return PointResponse(raw=text, points=pts,
224
+ compute_ms=round((time.time() - t0) * 1000.0, 1))
225
+
226
+
227
  @app.post("/act", response_model=ActResponse)
228
  def act(req: ActRequest, authorization: Optional[str] = Header(None)) -> ActResponse:
229
  # Constant-time compare so a bad token can't be recovered via response timing.