Fastfill-test / code /fastfill /v2 /tests /test_reference_extent.py
XXXpilar's picture
FastFill v4 base (step 6552): LoRA adapter, geometry heads, tokenizer, training code, predict.py
e6f80f3 verified
Raw History Blame Contribute Delete
8.52 kB
"""Reference room extents retain coordinates without claiming a measured room."""
from copy import deepcopy
import json
from pathlib import Path
import subprocess
import sys
import pytest
import torch
from fastfill.v2.direct_layout import bbox_diagnostics, layout_to_roomgenbench, layout_to_scene, request_to_condition
from fastfill.v2.schema import validate_condition, validate_layout
def request(dimensions=(5., 4.)):
return {"room_type": "unknown", "room_size_m": list(dimensions), "furniture_list": ["chair"]}
def prediction(condition, position=(-1., 2., -.5)):
return {"schema_version": "fastfill.v2", "objects": [
{"id": condition["objects"][0]["id"], "target_size_local_m": [1., 1., 1.],
"bottom_center_m": list(position), "yaw_rad": 0.}]}
@pytest.mark.parametrize("dimensions", [(5., 4.), (5., 4., 3.)])
def test_reference_extent_preserves_request_without_fabricating_boundary_or_floor(dimensions):
source = request(dimensions)
saved = deepcopy(source)
condition = request_to_condition(source, room_size_semantics="reference_extent")
validate_condition(condition)
assert source == saved
assert set(source) == {"room_type", "room_size_m", "furniture_list"}
assert condition["room"] == {
"frame": "right_handed_z_up", "room_type": "unknown",
"floor_polygon_xy_m": [[0., 0.], [5., 0.], [5., 4.], [0., 4.]],
"floor_z_m": 0., "floor_known": False, "boundary_known": False,
"boundary_quality": "source_reference_extent",
"height_m": dimensions[2] if len(dimensions) == 3 else None}
assert condition["constraints"] == []
assert condition["objects"] == [{"id": "obj_0000", "category": "chair", "description": "chair"}]
scene = layout_to_scene(condition, prediction(condition))
assert scene["room"] == condition["room"]
scene["room"]["floor_polygon_xy_m"][0][0] = 100.
assert condition["room"]["floor_polygon_xy_m"][0][0] == 0.
def test_default_and_explicit_rectangular_profiles_remain_exact_and_independent():
source = request((5., 4., 3.))
saved = deepcopy(source)
default = request_to_condition(source)
explicit = request_to_condition(source, room_size_semantics="rectangular")
reference = request_to_condition(source, room_size_semantics="reference_extent")
assert source == saved and default == explicit
assert default["room"]["floor_known"] is True
assert default["room"]["boundary_known"] is True
assert default["room"]["boundary_quality"] == "explicit_rectangular_request"
assert {key: value for key, value in reference["room"].items()
if key not in {"floor_known", "boundary_known", "boundary_quality"}} == {
key: value for key, value in default["room"].items()
if key not in {"floor_known", "boundary_known", "boundary_quality"}}
@pytest.mark.parametrize("semantics", [None, True, "extent", "", "REFERENCE_EXTENT"])
def test_unknown_room_size_semantics_is_rejected(semantics):
with pytest.raises(ValueError, match="room_size_semantics"):
request_to_condition(request(), room_size_semantics=semantics)
@pytest.mark.parametrize("semantics,expected", [("rectangular", "fail"), ("reference_extent", "unknown")])
def test_boundary_floor_and_ceiling_diagnostics_obey_profile(semantics, expected):
condition = request_to_condition(request((5., 4., 3.)), room_size_semantics=semantics)
# One outside object below nominal z=0; a second case above nominal ceiling.
for position in [(-1., 2., -.5), (-1., 2., 4.)]:
scene = layout_to_scene(condition, prediction(condition, position))
report = bbox_diagnostics(scene)
checks = {check["code"]: check["status"] for check in report["checks"]}
assert checks["boundary"] == expected
assert checks["floor_lower_bound"] == (expected if position[2] < 0 else (
"pass" if semantics == "rectangular" else "unknown"))
assert checks["ceiling"] == (expected if position[2] > 3 else (
"pass" if semantics == "rectangular" else "unknown"))
assert report["asset_retrieval"] == "not_attempted"
assert report["support"] == "unknown" and report["commit"] == "not_attempted"
if semantics == "reference_extent":
assert report["room_size_semantics"] == "reference_extent"
def test_reference_extent_prediction_cli_exports_raw_geometry_with_unknown_physical_checks(tmp_path):
from fastfill.v2.batch import TinyTokenizer
from fastfill.v2.model import ModelConfig, build_model
checkpoint = tmp_path / "model"
model = build_model(ModelConfig(backbone="tiny", lora_rank=0, decoder_dim=16,
decoder_heads=2, decoder_layers=1, tiny_hidden_size=16))
with torch.no_grad():
for head in (model.position_head, model.size_head, model.yaw_logits_head, model.yaw_residual_head):
head.weight.zero_()
head.bias.zero_()
model.position_head.bias.copy_(torch.tensor([-.2, .5, -.5]))
model.save_pretrained(checkpoint)
TinyTokenizer().save_pretrained(tmp_path / "tokenizer")
source = request()
request_path = tmp_path / "request.json"
request_path.write_text(json.dumps(source))
output, handoff = tmp_path / "prediction.json", tmp_path / "handoff"
command = [sys.executable, "-m", "fastfill.v2.predict", "--checkpoint", str(checkpoint),
"--request", str(request_path), "--room-size-semantics", "reference_extent",
"--output", str(output), "--export-dir", str(handoff), "--device", "cpu"]
proc = subprocess.run(command, cwd=Path(__file__).resolve().parents[3],
capture_output=True, text=True, timeout=30)
assert proc.returncode == 0, proc.stderr
condition = request_to_condition(source, room_size_semantics="reference_extent")
actual = json.loads(output.read_text())
validate_layout(actual, condition)
assert actual["objects"][0]["bottom_center_m"][0] < 0.
assert actual["objects"][0]["bottom_center_m"][2] < 0.
scene = json.loads((handoff / "scene.json").read_text())
assert scene == layout_to_scene(condition, actual)
assert scene["room"]["height_m"] is None
assert not scene["room"]["boundary_known"] and not scene["room"]["floor_known"]
diagnostics = json.loads((handoff / "diagnostics.json").read_text())
assert {check["status"] for check in diagnostics["checks"]} == {"unknown"}
assert diagnostics["room_size_semantics"] == "reference_extent"
assert diagnostics["asset_retrieval"] == "not_attempted"
assert diagnostics["commit"] == "not_attempted"
assert json.loads(request_path.read_text()) == source
assert (handoff / "layout.glb").exists() and (handoff / "preview.svg").exists()
def test_roomgenbench_handoff_keeps_reference_range_distinct_from_physical_room():
condition = request_to_condition(request((5., 4., 3.)), room_size_semantics="reference_extent")
actual = prediction(condition)
saved_condition, saved_prediction = deepcopy(condition), deepcopy(actual)
downstream = layout_to_roomgenbench(condition, actual)
assert downstream["room_size_semantics"] == "reference_extent"
assert downstream["room"]["boundary_known"] is False
assert downstream["room"]["floor_known"] is False
assert downstream["room"]["boundary_quality"] == "source_reference_extent"
assert "reference" in downstream["room_interpretation"]
assert downstream["room"]["dimensions"] == {"width": 5., "length": 4., "height": 3.}
assert downstream["objects"][0]["position"] == {"x": -1., "y": 2., "z": -.5}
assert condition == saved_condition and actual == saved_prediction
strict = request_to_condition(request((5., 4., 3.)))
assert "room_size_semantics" not in layout_to_roomgenbench(strict, prediction(strict))
def test_prediction_rejects_reference_flag_with_full_condition_before_model_loading(tmp_path, monkeypatch):
from fastfill.v2 import predict
def forbidden_load(*args, **kwargs):
raise AssertionError("inapplicable semantics flags must be rejected before model load")
monkeypatch.setattr(predict, "load_model", forbidden_load)
with pytest.raises(ValueError, match="only to --request"):
predict.main(["--checkpoint", str(tmp_path / "missing-model"),
"--condition", str(tmp_path / "missing-condition.json"),
"--room-size-semantics", "reference_extent", "--output", str(tmp_path / "prediction.json")])