{"record":{"id":"fdbd74e2def47d12","repo":"odysseus-dev/odysseus","slug":"provide-at-least-one-point-box-or-object-text","errorCode":null,"errorMessage":"Provide at least one point, box, or object text","messagePattern":"Provide at least one point, box, or object text","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/gallery/gallery_routes.py","lineNumber":1858,"sourceCode":"\n        This endpoint intentionally does not inspect edit prompts. It only\n        turns explicit visual selection hints into a binary mask that the\n        editor can reuse for wand/layer-mask/inpaint workflows.\n        \"\"\"\n        require_privilege(request, \"can_generate_images\")\n        body = await request.json()\n        image = _b64_to_pil_image(body.get(\"image\") or \"\", mode=\"RGB\")\n        points = body.get(\"points\") or []\n        box = body.get(\"box\")\n        text = (body.get(\"text\") or body.get(\"query\") or \"\").strip()\n        grounded = None\n\n        if not points and not box and text:\n            grounded = _ground_text_to_box(image, text)\n            box = grounded[\"box\"]\n\n        if not points and not box:\n            raise HTTPException(400, \"Provide at least one point, box, or object text\")\n\n        backend = _load_sam_backend()\n        torch = backend[\"torch\"]\n        processor = backend[\"processor\"]\n        model = backend[\"model\"]\n        device = backend[\"device\"]\n\n        kwargs: Dict[str, Any] = {\"return_tensors\": \"pt\"}\n        input_points = []\n        if points:\n            input_labels = []\n            for p in points:\n                try:\n                    input_points.append([float(p[\"x\"]), float(p[\"y\"])])\n                    input_labels.append(int(p.get(\"label\", 1)))\n                except Exception as exc:\n                    raise HTTPException(400, \"Invalid point format\") from exc\n            kwargs[\"input_points\"] = [input_points]","sourceCodeStart":1840,"sourceCodeEnd":1876,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/gallery/gallery_routes.py#L1840-L1876","documentation":"HTTP 400 from the SAM smart-mask endpoint when the request contains no points, no box, and no text query to ground. The endpoint first tries to convert free text into a box via _ground_text_to_box; only if that also yields nothing does it reject the request. It is a pure request-shape validation error.","triggerScenarios":"POST to the SAM mask route with {\"image\": \"...\"} only; or with text that fails grounding (returns no box); or with points/box keys present but empty lists/None (body.get('points') or [] collapses falsy values to empty).","commonSituations":"Frontend sends the mask request before the user clicks a point; text query typo so grounding finds no object; box sent as empty array; JSON field name mismatch (e.g. 'prompt' instead of 'text'/'query').","solutions":["Include at least one of: points=[{\"x\":..,\"y\":..,\"label\":1}], box=[x1,y1,x2,y2], or text=\"a red cup\"","If using text, verify the text is a concrete object description that the grounding model can localize","Check the client sends the fields under the exact keys 'points', 'box', 'text' (or 'query')"],"exampleFix":"# before\nrequests.post(url, json={\"image\": b64})  # 400\n\n# after\nrequests.post(url, json={\"image\": b64, \"points\": [{\"x\": 120, \"y\": 80, \"label\": 1}]})","handlingStrategy":"validation","validationCode":"def has_mask_prompt(body: dict) -> bool:\n    return bool(\n        body.get(\"points\")\n        or body.get(\"box\")\n        or (body.get(\"text\") or body.get(\"query\") or \"\").strip()\n    )\n\nif not has_mask_prompt(body):\n    raise ValueError(\"pick a point, draw a box, or type an object name first\")","typeGuard":"function isMaskRequest(o: unknown): o is { image: string } & (\n  | { points: { x: number; y: number; label?: number }[] }\n  | { box: [number, number, number, number] }\n  | { text: string }\n) {\n  if (typeof (o as any).image !== 'string' || !(o as any).image) return false;\n  const p = (o as any).points, b = (o as any).box, t = ((o as any).text ?? (o as any).query ?? '').toString().trim();\n  return Boolean((Array.isArray(p) && p.length) || (Array.isArray(b) && b.length === 4) || t);\n}","tryCatchPattern":null,"preventionTips":["Disable the mask-submit button until the user has clicked a point, drawn a box, or typed a query","Mirror the server's falsy-collapse semantics: empty arrays and blank strings count as 'not provided'","When relying on text grounding, tell users to name concrete objects ('the red mug') since failed grounding degrades to this 400"],"tags":["http-400","validation","sam","segmentation","request-body"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}