A small, self-contained library + CLI for turning real 3D-FRONT rooms into a diffusion-friendly per-object tensor and stress-testing the encoding.
src/parameterization/ # the package (library, no CLI)
scripts/ # CLI runners (outside the package, import from parameterization)
pyproject.toml # editable-install + console scripts
python -m venv env && source env/bin/activate
pip install -e . # installs parameterization + its dependencies
# 1. fetch a slice of real rooms (~1 GB, a few minutes)
python scripts/stream.py --rooms 50 --out data/hf_scene
# 2. one shot — runs validate → stress → diverse → predicates → mesh → compare → diagrams
python scripts/run_all.py --root data/hf_scene
# └── --skip-mesh skips the slow FCL collision step
Each evaluator individually:
python scripts/evaluate.py validate data/hf_scene --out output/validate
python scripts/evaluate.py stress data/hf_scene --out output/stress
python scripts/evaluate.py diverse data/hf_scene --out output/eval
python scripts/evaluate.py predicates data/hf_scene --out output/predicates --k-recover 8
python scripts/evaluate.py mesh data/hf_scene --out output/eval
python scripts/evaluate.py compare data/hf_scene \
--mesh-json output/eval/mesh_collisions.json --out output/diagrams
python scripts/diagrams.py data/hf_scene --out output/diagrams
Console scripts (after pip install -e .):
parameterization-evaluate validate data/hf_scene
parameterization-diagrams data/hf_scene
parameterization-stream --rooms 50
import parameterization as s
# 1. load real rooms from streamed GLBs
scenes = s.load_scenes(Path("data/hf_scene"))
room = scenes[0].rooms[0]
# 2. build the (N, 128) per-object tensor
M, labels = s.room_tensor(room) # M: (N, 128) float32
# 3. round-trip pos/yaw/scale/class
decoded = s.decode_room(M, s.SUPER_CATEGORIES)
# 4. ablation + energy-min recovery
study = s.PredicateAblationStudy(
root=Path("data/hf_scene"),
out=Path("output/predicates"),
k_recover=8,
)
study.run()
# 5. lower-level: drive the recovery directly
engine = s.PredicateEngine()
recoverer = s.EnergyRecoverer(engine, mean_size={...})
result = recoverer.recover(room, s.AblationSpec("drop_pos"),
rng=np.random.default_rng(0))
print(result.pos_err.mean(), len(result.objs))
output/validate/ — 2×2 GT-vs-decoded panels (top-down + 3D) for the largest rooms.output/stress/ — 5 styled tables: per-slot noise sweeps, transferability, busiest rooms.output/eval/ — metrics.json, mesh_collisions.json, 4 summary tables.output/predicates/ — Jaccard + recovery-error tables, firing-ratio plot, subset sweep, resolved relationship graph.output/diagrams/ — meta-stat figures, PCA, predicate graphs, collision comparisons.src/parameterization/)| Module | Public surface |
|---|---|
data | PlacedObject, Room, Scene, load_scenes, parse_object_glb |
params | D, SLOT_*, room_tensor, decode_room, class_embed, PREDICATES, THRESH, OBB helpers |
evaluate | PredicateEngine, AblationSpec, EnergyRecoverer, RecoveryResult, six evaluator classes |
diagrams | meta-stat / relationship-graph plotting functions |
viz | draw_room, draw_room_3d |
theme | dark neon matplotlib/seaborn helpers (apply(), glow_bars, render_table, …) |
stream | ConcatStream, PART_URLS |
CLI runners (scripts/, outside the package):
| File | Purpose |
|---|---|
stream.py | Stream-extract GLB rooms from huanngzh/3D-Front. |
evaluate.py | Subcommands: validate, stress, diverse, mesh, compare, predicates. |
diagrams.py | Render the meta-statistic + relationship-graph PNGs. |
run_all.py | End-to-end orchestrator. |
build_verification_csv.py | Generate VERIFICATION.csv. |
The data lives at huggingface.co/datasets/huanngzh/3D-Front — a processed
mirror of Alibaba's 3D-FRONT, derived from the MIDI-3D pipeline. It's packed as
one tar.gz split into 13 byte-range parts (partaa..partam, ≈43 GB total).
Internal layout:
3D-FRONT-SCENE/
<house_uuid>/
<room_id>/ e.g. LivingRoom-12345
Bed_<jid>_0.glb ← one GLB per placed instance
Sofa_<jid>_1.glb
Table_<jid>_2.glb
...
wall.glb / floor.glb / ceil.glb (architecture, skipped)
<room>_full.glb (whole-room merge, skipped)
Each per-object GLB is a placed instance already in world coordinates — its
world AABB IS the placed bbox. The filename prefix (Bed_, Sofa_, …) gives
the 3D-FUTURE super-category; the 8-hex chunk is the asset id (jid).
Two subtleties:
params.THRESH is tuned to
that scale.PlacedObject (data.py)| Field | How we get it |
|---|---|
class_super | regex on filename prefix → PREFIX_TO_SUPERCAT (7 buckets: Bed, Sofa, Chair, Table, Cabinet/Shelf, Lighting, Decor) |
jid | 8-hex stem after the prefix |
pos | AABB centroid for X/Z, AABB min for Y (objects sit on their base) |
size | (w, h, d) — h from AABB-Y; (w, d) from a 2D OBB fit to the XZ vertex cloud. Raw AABB would over-estimate w/d for any rotated object. |
yaw | angle of the principal eigenvector of the XZ covariance |
scale | [1, 1, 1] (mesh is already at world scale) |
rot_quat | derived from yaw (kept for downstream tools that want a quaternion) |
PlacedObject → 128-d row (params.py)[ 0: 64) class_embed ── frozen lookup: SHA-256(class_super) → seeded RNG → unit-norm vec
[ 64: 67) pos ── (x, y, z) copied from PlacedObject.pos
[ 67: 70) rot ── (sin yaw, cos yaw, 0) smooth 2π wrap
[ 70: 73) scale ── per-axis multiplier
[ 73: 89) affordance ── 16 hand-coded flags per super-cat
[ 89:121) relationship ── 8 predicates × 4 features:
[4k+0] fraction of other objects satisfying p
[4k+1] mean local-frame dx to those objects
[4k+2] mean local-frame dz to those objects
[4k+3] mean Euclidean distance to those objects
[121:128) reserved ── slack for later fields
Affordance flag order: [sittable, sleepable, surface_top, storage, light_source, leaning, holdable, has_back, mobile, wall_mount, decorative, electronic, fluid, soft, hard, large_footprint].
The 8 predicates (mechanical, threshold-based — see params.THRESH):
on_top_of, supports, parallel_to, perpendicular_to, adjacent_to,
faces, left_of, right_of. Footprint contact uses 2D OBB intersection via
SAT (separating-axis theorem), not axis-aligned overlap, so yawed objects
don't get false-positive adjacent_to tags from their AABB envelope.
A whole room is np.stack of those rows. room_tensor(room) returns
((N, 128) float32, [class names]).
DecodedObjectImplemented in params.decode_object. Most slots are direct reads — that's
why round-trip on noise-free data is trivially perfect, and why the
interesting test is how each slot degrades under Gaussian noise (covered by
evaluate.py stress).
| Output field | How we recover it |
|---|---|
class_super | nearest neighbour of v[0:64] against the frozen embedding table (dot product, since entries are unit-norm). 7 well-separated anchors stay correct up to σ ≈ 0.5. |
pos | direct slice v[64:67] |
yaw | atan2(v[67], v[68]) |
scale | direct slice v[70:73] |
Not decoded back, by design:
size (w, h, d) — not parameterized yet; expected to come from a 3D-FUTURE
asset retrieval step keyed by class (or by jid).The round-trip we can do losslessly is (class, pos, yaw, scale). For
visual validation, evaluate.py validate borrows GT size/scale back at
render time so it can draw a bbox.
3 commits
Python
91.5%
JavaScript
6.8%
HTML
1.8%
A small, self-contained library + CLI for turning real 3D-FRONT rooms into a diffusion-friendly per-object tensor and stress-testing the encoding.
src/parameterization/ # the package (library, no CLI)
scripts/ # CLI runners (outside the package, import from parameterization)
pyproject.toml # editable-install + console scripts
python -m venv env && source env/bin/activate
pip install -e . # installs parameterization + its dependencies
# 1. fetch a slice of real rooms (~1 GB, a few minutes)
python scripts/stream.py --rooms 50 --out data/hf_scene
# 2. one shot — runs validate → stress → diverse → predicates → mesh → compare → diagrams
python scripts/run_all.py --root data/hf_scene
# └── --skip-mesh skips the slow FCL collision step
Each evaluator individually:
python scripts/evaluate.py validate data/hf_scene --out output/validate
python scripts/evaluate.py stress data/hf_scene --out output/stress
python scripts/evaluate.py diverse data/hf_scene --out output/eval
python scripts/evaluate.py predicates data/hf_scene --out output/predicates --k-recover 8
python scripts/evaluate.py mesh data/hf_scene --out output/eval
python scripts/evaluate.py compare data/hf_scene \
--mesh-json output/eval/mesh_collisions.json --out output/diagrams
python scripts/diagrams.py data/hf_scene --out output/diagrams
Console scripts (after pip install -e .):
parameterization-evaluate validate data/hf_scene
parameterization-diagrams data/hf_scene
parameterization-stream --rooms 50
import parameterization as s
# 1. load real rooms from streamed GLBs
scenes = s.load_scenes(Path("data/hf_scene"))
room = scenes[0].rooms[0]
# 2. build the (N, 128) per-object tensor
M, labels = s.room_tensor(room) # M: (N, 128) float32
# 3. round-trip pos/yaw/scale/class
decoded = s.decode_room(M, s.SUPER_CATEGORIES)
# 4. ablation + energy-min recovery
study = s.PredicateAblationStudy(
root=Path("data/hf_scene"),
out=Path("output/predicates"),
k_recover=8,
)
study.run()
# 5. lower-level: drive the recovery directly
engine = s.PredicateEngine()
recoverer = s.EnergyRecoverer(engine, mean_size={...})
result = recoverer.recover(room, s.AblationSpec("drop_pos"),
rng=np.random.default_rng(0))
print(result.pos_err.mean(), len(result.objs))
output/validate/ — 2×2 GT-vs-decoded panels (top-down + 3D) for the largest rooms.output/stress/ — 5 styled tables: per-slot noise sweeps, transferability, busiest rooms.output/eval/ — metrics.json, mesh_collisions.json, 4 summary tables.output/predicates/ — Jaccard + recovery-error tables, firing-ratio plot, subset sweep, resolved relationship graph.output/diagrams/ — meta-stat figures, PCA, predicate graphs, collision comparisons.src/parameterization/)| Module | Public surface |
|---|---|
data | PlacedObject, Room, Scene, load_scenes, parse_object_glb |
params | D, SLOT_*, room_tensor, decode_room, class_embed, PREDICATES, THRESH, OBB helpers |
evaluate | PredicateEngine, AblationSpec, EnergyRecoverer, RecoveryResult, six evaluator classes |
diagrams | meta-stat / relationship-graph plotting functions |
viz | draw_room, draw_room_3d |
theme | dark neon matplotlib/seaborn helpers (apply(), glow_bars, render_table, …) |
stream | ConcatStream, PART_URLS |
CLI runners (scripts/, outside the package):
| File | Purpose |
|---|---|
stream.py | Stream-extract GLB rooms from huanngzh/3D-Front. |
evaluate.py | Subcommands: validate, stress, diverse, mesh, compare, predicates. |
diagrams.py | Render the meta-statistic + relationship-graph PNGs. |
run_all.py | End-to-end orchestrator. |
build_verification_csv.py | Generate VERIFICATION.csv. |
The data lives at huggingface.co/datasets/huanngzh/3D-Front — a processed
mirror of Alibaba's 3D-FRONT, derived from the MIDI-3D pipeline. It's packed as
one tar.gz split into 13 byte-range parts (partaa..partam, ≈43 GB total).
Internal layout:
3D-FRONT-SCENE/
<house_uuid>/
<room_id>/ e.g. LivingRoom-12345
Bed_<jid>_0.glb ← one GLB per placed instance
Sofa_<jid>_1.glb
Table_<jid>_2.glb
...
wall.glb / floor.glb / ceil.glb (architecture, skipped)
<room>_full.glb (whole-room merge, skipped)
Each per-object GLB is a placed instance already in world coordinates — its
world AABB IS the placed bbox. The filename prefix (Bed_, Sofa_, …) gives
the 3D-FUTURE super-category; the 8-hex chunk is the asset id (jid).
Two subtleties:
params.THRESH is tuned to
that scale.PlacedObject (data.py)| Field | How we get it |
|---|---|
class_super | regex on filename prefix → PREFIX_TO_SUPERCAT (7 buckets: Bed, Sofa, Chair, Table, Cabinet/Shelf, Lighting, Decor) |
jid | 8-hex stem after the prefix |
pos | AABB centroid for X/Z, AABB min for Y (objects sit on their base) |
size | (w, h, d) — h from AABB-Y; (w, d) from a 2D OBB fit to the XZ vertex cloud. Raw AABB would over-estimate w/d for any rotated object. |
yaw | angle of the principal eigenvector of the XZ covariance |
scale | [1, 1, 1] (mesh is already at world scale) |
rot_quat | derived from yaw (kept for downstream tools that want a quaternion) |
PlacedObject → 128-d row (params.py)[ 0: 64) class_embed ── frozen lookup: SHA-256(class_super) → seeded RNG → unit-norm vec
[ 64: 67) pos ── (x, y, z) copied from PlacedObject.pos
[ 67: 70) rot ── (sin yaw, cos yaw, 0) smooth 2π wrap
[ 70: 73) scale ── per-axis multiplier
[ 73: 89) affordance ── 16 hand-coded flags per super-cat
[ 89:121) relationship ── 8 predicates × 4 features:
[4k+0] fraction of other objects satisfying p
[4k+1] mean local-frame dx to those objects
[4k+2] mean local-frame dz to those objects
[4k+3] mean Euclidean distance to those objects
[121:128) reserved ── slack for later fields
Affordance flag order: [sittable, sleepable, surface_top, storage, light_source, leaning, holdable, has_back, mobile, wall_mount, decorative, electronic, fluid, soft, hard, large_footprint].
The 8 predicates (mechanical, threshold-based — see params.THRESH):
on_top_of, supports, parallel_to, perpendicular_to, adjacent_to,
faces, left_of, right_of. Footprint contact uses 2D OBB intersection via
SAT (separating-axis theorem), not axis-aligned overlap, so yawed objects
don't get false-positive adjacent_to tags from their AABB envelope.
A whole room is np.stack of those rows. room_tensor(room) returns
((N, 128) float32, [class names]).
DecodedObjectImplemented in params.decode_object. Most slots are direct reads — that's
why round-trip on noise-free data is trivially perfect, and why the
interesting test is how each slot degrades under Gaussian noise (covered by
evaluate.py stress).
| Output field | How we recover it |
|---|---|
class_super | nearest neighbour of v[0:64] against the frozen embedding table (dot product, since entries are unit-norm). 7 well-separated anchors stay correct up to σ ≈ 0.5. |
pos | direct slice v[64:67] |
yaw | atan2(v[67], v[68]) |
scale | direct slice v[70:73] |
Not decoded back, by design:
size (w, h, d) — not parameterized yet; expected to come from a 3D-FUTURE
asset retrieval step keyed by class (or by jid).The round-trip we can do losslessly is (class, pos, yaw, scale). For
visual validation, evaluate.py validate borrows GT size/scale back at
render time so it can draw a bbox.
3 commits
Python
91.5%
JavaScript
6.8%
HTML
1.8%