Instructions to use XXXpilar/Fastfill-test with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use XXXpilar/Fastfill-test with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
File size: 17,305 Bytes
e6f80f3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | """Strict canonical protocol. Validation never repairs or mutates inputs."""
from __future__ import annotations
import math
from collections.abc import Mapping, Sequence
from fastfill.v2 import SCHEMA_VERSION
def _keys(value, allowed, required, label):
if not isinstance(value, Mapping):
raise ValueError(f"{label} must be an object")
if set(value) - set(allowed):
raise ValueError(f"{label}: unknown fields {sorted(set(value) - set(allowed))}")
if set(required) - set(value):
raise ValueError(f"{label}: missing fields {sorted(set(required) - set(value))}")
def finite(value, label="value", positive=False):
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
raise ValueError(f"{label} must be a finite number")
if positive and value <= 0:
raise ValueError(f"{label} must be strictly positive")
return float(value)
def vector(value, n=3, label="vector", positive=False):
if not isinstance(value, (list, tuple)) or len(value) != n:
raise ValueError(f"{label} must have length {n}")
return [finite(v, label, positive) for v in value]
def _id(value, label="id"):
if not isinstance(value, str) or not value.strip() or len(value) > 256:
raise ValueError(f"{label} must be a nonempty string of at most 256 characters")
return value
def _polygon(value, label):
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)) or len(value) < 3:
raise ValueError(f"{label} needs at least three XY points")
points = [vector(p, 2, label) for p in value]
area = abs(sum(a[0] * b[1] - b[0] * a[1]
for a, b in zip(points, points[1:] + points[:1]))) / 2
if area <= 0:
raise ValueError(f"{label} must have positive area")
# Exact shape validity is checked once at the input boundary, not repaired by buffer(0).
from shapely.geometry import Polygon
if not Polygon(points).is_valid:
raise ValueError(f"{label} is self-intersecting or otherwise invalid")
ROOM_FIELDS = {"frame", "floor_polygon_xy_m", "floor_z_m", "height_m", "boundary_quality",
"boundary_known", "floor_known", "fixed_objects", "room_type", "openings", "description"}
# exchangeable_group is supervision bookkeeping: it lives in validity (target order), never in the condition.
OBJECT_FIELDS = {"id", "category", "description", "support_parent", "support_surface_id",
"size_bounds_local_m", "fixed_size_local_m", "required_capabilities",
"retrieval_tolerance", "retrieval_tolerance_log", "attributes",
"constraint_role", "semantic_front_required"}
def migrate_legacy_row(row):
"""Move pre-C1 ``condition.objects[i].exchangeable_group`` into ``validity.exchangeable_group``
and give pre-K1 rows an all-false ``validity.size_axis_swap_allowed``.
The returned row never renders groups into the condition. Labels follow
target order (like validity.position); rows without targets keep request order.
"""
objects = row.get("condition", {}).get("objects", [])
if not isinstance(objects, list):
return row
order = row.get("target", {}).get("objects") or objects
if any(isinstance(o, Mapping) and "exchangeable_group" in o for o in objects):
validity = row.get("validity", {})
if "exchangeable_group" in validity:
raise ValueError("exchangeable_group declared in both condition objects and validity")
groups = {o.get("id"): o.get("exchangeable_group") for o in objects}
condition = {**row["condition"], "objects": [{k: v for k, v in o.items() if k != "exchangeable_group"} for o in objects]}
row = {**row, "condition": condition,
"validity": {**validity, "exchangeable_group": [groups.get(o.get("id")) for o in order]}}
validity = row.get("validity")
if isinstance(validity, Mapping) and "size_axis_swap_allowed" not in validity:
row = {**row, "validity": {**validity, "size_axis_swap_allowed": [False] * len(order)}}
return row
def _metadata(value, label, depth=0):
"""Allow JSON metadata, with explicit nulls and finite numeric values."""
if depth > 32:
raise ValueError(f"{label} metadata exceeds nesting limit")
if value is None or isinstance(value, (str, bool)):
return
if isinstance(value, (int, float)):
finite(value, label)
elif isinstance(value, Mapping):
if any(not isinstance(k, str) for k in value):
raise ValueError(f"{label} metadata keys must be strings")
for child in value.values():
_metadata(child, label, depth + 1)
elif isinstance(value, (list, tuple)):
for child in value:
_metadata(child, label, depth + 1)
else:
raise ValueError(f"{label} must contain JSON-compatible metadata")
def _capabilities(value, label, unknown_allowed=False):
if unknown_allowed and value is None:
return
if not isinstance(value, (list, tuple)) or any(not isinstance(v, str) or not v.strip() for v in value):
raise ValueError(f"{label} must contain nonempty capability names")
def _surfaces(value):
if value is None:
return
if not isinstance(value, (list, tuple)):
raise ValueError("verified support surfaces must be a list or null")
ids = []
for surface in value:
_keys(surface, {"surface_id", "local_polygon_xy_m", "local_z_m"},
{"surface_id", "local_polygon_xy_m", "local_z_m"}, "support surface")
ids.append(_id(surface["surface_id"], "surface id"))
_polygon(surface["local_polygon_xy_m"], "support surface polygon")
finite(surface["local_z_m"], "support height")
if len(ids) != len(set(ids)):
raise ValueError("support surface IDs must be unique per asset")
def _support_graph(objects):
parents = {obj["id"]: obj.get("support_parent") for obj in objects}
for obj in objects:
seen, current = set(), obj["id"]
while current in parents:
if current in seen:
raise ValueError("support graph contains a cycle")
seen = seen | {current}
current = parents[current]
def _object(obj):
_keys(obj, OBJECT_FIELDS, {"id", "category", "description"}, "request object")
_id(obj["id"])
_id(obj["category"], "category")
if not isinstance(obj["description"], str):
raise ValueError("description must be a string")
for key in ("support_parent", "support_surface_id"):
if key in obj and obj[key] is not None:
_id(obj[key], key)
if "constraint_role" in obj:
_id(obj["constraint_role"], "constraint role")
if "semantic_front_required" in obj and not isinstance(obj["semantic_front_required"], bool):
raise ValueError("semantic_front_required must be a boolean")
if "attributes" in obj:
if not isinstance(obj["attributes"], Mapping):
raise ValueError("attributes must be an object")
_metadata(obj["attributes"], "attributes")
if "fixed_size_local_m" in obj:
fixed = obj["fixed_size_local_m"]
if not isinstance(fixed, (list, tuple)) or len(fixed) != 3:
raise ValueError("fixed size must contain three numbers or nulls")
for value in fixed:
if value is not None:
finite(value, "fixed size", positive=True)
if "size_bounds_local_m" in obj:
bounds = obj["size_bounds_local_m"]
_keys(bounds, {"min", "max"}, {"min", "max"}, "size bounds")
lo, hi = vector(bounds["min"], positive=True), vector(bounds["max"], positive=True)
if any(a > b for a, b in zip(lo, hi)):
raise ValueError("size bounds min exceeds max")
if "fixed_size_local_m" in obj:
if any(v is not None and not a <= v <= b for v, a, b in zip(obj["fixed_size_local_m"], lo, hi)):
raise ValueError("fixed size conflicts with size bounds")
if "required_capabilities" in obj:
_capabilities(obj["required_capabilities"], "required_capabilities")
for key in ("retrieval_tolerance", "retrieval_tolerance_log"):
if key in obj:
value = obj[key]
values = vector(value) if isinstance(value, (list, tuple)) else [finite(value)]
if any(v < 0 for v in values):
raise ValueError("retrieval tolerance must be nonnegative")
def _constraints(constraints, refs):
if not isinstance(constraints, list):
raise ValueError("constraints must be a list")
for c in constraints:
if not isinstance(c, Mapping) or not isinstance(c.get("type"), str):
raise ValueError("constraint needs a type")
kind = _id(c["type"], "constraint type")
_metadata(c, "constraint")
if "hard" in c and not isinstance(c["hard"], bool):
raise ValueError("constraint hard flag must be a boolean")
if kind in {"faces_direction", "faces", "near", "clearance", "on", "between", "against_wall"}:
if c.get("object_id") not in refs:
raise ValueError("known constraint requires an object reference")
if kind in {"faces", "near", "clearance"}:
if c.get("target_id") not in refs or c["target_id"] == c["object_id"]:
raise ValueError("binary constraint requires a distinct object target")
if kind == "between":
targets = c.get("target_ids")
if (not isinstance(targets, list) or len(targets) != 2
or any(not isinstance(target, str) for target in targets) or len(set(targets)) != 2 or any(
target not in refs or target == c["object_id"] for target in targets)):
raise ValueError("between requires two distinct valid target objects")
if kind == "on":
target = c.get("target_id", c.get("parent_id"))
if target not in refs | {"floor", "wall"} or target == c["object_id"]:
raise ValueError("on constraint requires a distinct support parent")
if "target_id" in c and "parent_id" in c and c["target_id"] != c["parent_id"]:
raise ValueError("on constraint has conflicting parent references")
for kind_name, field in (("near", "max_distance_m"), ("clearance", "min_distance_m")):
if kind == kind_name and field not in c:
raise ValueError(f"{kind} constraint requires {field}")
for key in ("object_id", "target_id", "parent_id"):
if key in c and c[key] not in refs | {"floor", "wall"}:
raise ValueError(f"constraint references missing {key}")
if c["type"] == "faces_direction":
direction = vector(c.get("direction_xy"), 2)
if math.hypot(*direction) == 0:
raise ValueError("facing direction must be nonzero")
if c["type"] == "keepout":
_polygon(c.get("polygon_xy_m"), "keepout polygon")
for key in ("tolerance_rad", "tolerance_m", "max_distance_m", "min_distance_m"):
if key in c and finite(c[key], key) < 0:
raise ValueError(f"{key} must be nonnegative")
def validate_condition(condition):
_keys(condition, {"schema_version", "room", "objects", "constraints"},
{"schema_version", "room", "objects", "constraints"}, "condition")
if condition["schema_version"] != SCHEMA_VERSION:
raise ValueError("unsupported schema version")
room = condition["room"]
_keys(room, ROOM_FIELDS, {"frame", "floor_polygon_xy_m"}, "room")
if room["frame"] != "right_handed_z_up":
raise ValueError("room frame must be right_handed_z_up")
_polygon(room["floor_polygon_xy_m"], "floor polygon")
for key in ("floor_z_m", "height_m"):
if room.get(key) is not None:
finite(room[key], key, positive=key == "height_m")
for key in ("floor_known", "boundary_known"):
if key in room and not isinstance(room[key], bool):
raise ValueError(f"{key} must be a boolean")
if room.get("floor_known") is True and room.get("floor_z_m") is None:
raise ValueError("known floor requires a floor height")
fixed = room.get("fixed_objects", [])
if not isinstance(fixed, list):
raise ValueError("fixed objects must be a list")
fixed_ids = []
for obj in fixed:
_keys(obj, {"id", "size_local_m", "bottom_center_m", "yaw_rad", "category", "support_surfaces",
"capabilities", "required_capabilities", "semantic_front_local"},
{"id", "size_local_m", "bottom_center_m", "yaw_rad"}, "fixed object")
fixed_ids.append(_id(obj["id"]))
vector(obj["size_local_m"], positive=True)
vector(obj["bottom_center_m"])
finite(obj["yaw_rad"], "fixed yaw")
if "category" in obj:
_id(obj["category"], "fixed category")
for key in ("capabilities", "required_capabilities"):
if key in obj:
_capabilities(obj[key], key, unknown_allowed=key == "capabilities")
if "support_surfaces" in obj:
_surfaces(obj["support_surfaces"])
if obj.get("semantic_front_local") is not None:
front = vector(obj["semantic_front_local"])
if math.hypot(*front[:2]) == 0 or abs(front[2]) > 1e-6:
raise ValueError("semantic front must be a nonzero horizontal vector")
if not isinstance(condition["objects"], list):
raise ValueError("objects must be a list")
for obj in condition["objects"]:
_object(obj)
ids = [obj["id"] for obj in condition["objects"]] + fixed_ids
if len(set(ids)) != len(ids) or any(i in {"floor", "wall"} for i in ids):
raise ValueError("object IDs must be unique and not reserved")
for obj in condition["objects"]:
parent = obj.get("support_parent")
if parent is not None and (parent not in set(ids) | {"floor", "wall"} or parent == obj["id"]):
raise ValueError("invalid support parent")
_support_graph(condition["objects"])
for key in ("room_type", "description", "boundary_quality"):
if room.get(key) is not None and not isinstance(room[key], str):
raise ValueError(f"room {key} must be a string or null")
if "openings" in room:
if not isinstance(room["openings"], list):
raise ValueError("room openings must be a list")
_metadata(room["openings"], "room openings")
_constraints(condition["constraints"], set(ids))
from fastfill.v2.validation import effective_support_requests
effective_support_requests(condition)
return condition
def validate_layout(layout, condition=None):
_keys(layout, {"schema_version", "objects"}, {"schema_version", "objects"}, "layout")
if layout["schema_version"] != SCHEMA_VERSION or not isinstance(layout["objects"], list):
raise ValueError("invalid layout schema")
ids = []
for obj in layout["objects"]:
_keys(obj, {"id", "target_size_local_m", "bottom_center_m", "yaw_rad", "support_parent"},
{"id", "target_size_local_m", "bottom_center_m", "yaw_rad"}, "output object")
if "support_parent" in obj:
_id(obj["support_parent"], "output support_parent")
ids.append(_id(obj["id"]))
vector(obj["target_size_local_m"], positive=True)
vector(obj["bottom_center_m"])
yaw = finite(obj["yaw_rad"], "yaw")
if not -math.pi <= yaw < math.pi:
raise ValueError("yaw must be wrapped to [-pi, pi)")
if len(ids) != len(set(ids)):
raise ValueError("duplicate output IDs")
if condition is not None and set(ids) != {o["id"] for o in condition["objects"]}:
raise ValueError("output IDs must equal all requested IDs exactly once")
# optional predicted support: floor, wall, another output object or a fixed object; never itself, no cycle
parents = {obj["id"]: obj["support_parent"] for obj in layout["objects"] if "support_parent" in obj}
known = {"floor", "wall", *ids, *(f["id"] for f in (condition or {}).get("room", {}).get("fixed_objects", ()))}
for child, parent in parents.items():
if parent == child or (condition is not None and parent not in known):
raise ValueError(f"output support_parent of {child!r} must name the floor, a wall, another object or a fixed object")
seen = {child}
while parent in parents:
if parent in seen:
raise ValueError("output support_parent references form a cycle")
seen.add(parent)
parent = parents[parent]
return layout
def normalize_room(room, reference_height=3.0):
"""Origin/scales depend only on room input; missing height uses fixed 3m."""
points = room["floor_polygon_xy_m"]
xmin, ymin = [min(p[q] for p in points) for q in range(2)]
xmax, ymax = [max(p[q] for p in points) for q in range(2)]
height = room.get("height_m") or finite(reference_height, positive=True)
if xmax <= xmin or ymax <= ymin:
raise ValueError("room XY normalization needs positive extents")
return [float(xmin), float(ymin), float(room.get("floor_z_m") or 0.)], [float(xmax - xmin), float(ymax - ymin), float(height)]
|