@@ -200,6 +200,62 @@ def solve_calibration(
200200 return best [1 ]
201201
202202
203+ def solve_calibration_with_origin (
204+ layers : GridLayers ,
205+ points : list [tuple [float , float ]],
206+ origin : tuple [float , float ],
207+ * ,
208+ resolutions : Iterable [float ],
209+ y_signs : Iterable [int ] = (1 , - 1 ),
210+ min_on_floor : float = 0.5 ,
211+ ) -> GridCalibration | None :
212+ """Fit resolution + Y orientation around a *known* grid-pixel origin.
213+
214+ Unlike :func:`solve_calibration`, the pixel origin ``(ox, oy)`` is fixed --
215+ e.g. read straight from the Q10 grid-frame header -- so this only sweeps the
216+ candidate ``resolutions`` and ``y_signs`` and keeps the placement landing the
217+ most ``points`` on floor. With the expensive 2-D offset slide gone, far fewer
218+ points are needed to confirm the fit, so it works from a short path rather
219+ than a dense clean. Returns ``None`` if no candidate lands a ``min_on_floor``
220+ fraction of points on floor (e.g. the origin or points are bogus).
221+ """
222+ if not points :
223+ return None
224+ w , h = layers .width , layers .height
225+ ox , oy = origin
226+ classify = layers .classifier
227+ # 1 = floor, 2 = wall/background (blocked), 0 = other. Index by cell.
228+ klass = bytes (
229+ 1 if (c := classify (v )) == LAYER_FLOOR else 2 if c in (LAYER_WALL , LAYER_BACKGROUND ) else 0 for v in layers .grid
230+ )
231+
232+ best : tuple [float , GridCalibration ] | None = None
233+ for resolution in resolutions :
234+ if resolution <= 0 :
235+ continue
236+ for y_sign in y_signs :
237+ on_floor = 0
238+ blocked = 0
239+ for x , y in points :
240+ px = int (x / resolution + ox )
241+ py = int (oy - y_sign * y / resolution )
242+ if not (0 <= px < w and 0 <= py < h ):
243+ blocked += 1
244+ continue
245+ k = klass [py * w + px ]
246+ if k == 1 :
247+ on_floor += 1
248+ elif k == 2 :
249+ blocked += 1
250+ score = on_floor - 1.5 * blocked
251+ if best is None or score > best [0 ]:
252+ best = (score , GridCalibration (float (resolution ), float (ox ), float (oy ), y_sign ))
253+
254+ if best is None or best [0 ] < len (points ) * min_on_floor :
255+ return None
256+ return best [1 ]
257+
258+
203259def decompose_grid (
204260 width : int ,
205261 height : int ,
0 commit comments