HaysonC/starshot

0

stars

3

commits

Python

primary language

May 4, 2026

updated

README

parameterization · 3D-FRONT (N, 128) parameterization

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

Install

python -m venv env && source env/bin/activate
pip install -e .                 # installs parameterization + its dependencies

How to run

# 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

Library use

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))

Outputs

  • 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.

Package layout (src/parameterization/)

ModulePublic surface
dataPlacedObject, Room, Scene, load_scenes, parse_object_glb
paramsD, SLOT_*, room_tensor, decode_room, class_embed, PREDICATES, THRESH, OBB helpers
evaluatePredicateEngine, AblationSpec, EnergyRecoverer, RecoveryResult, six evaluator classes
diagramsmeta-stat / relationship-graph plotting functions
vizdraw_room, draw_room_3d
themedark neon matplotlib/seaborn helpers (apply(), glow_bars, render_table, …)
streamConcatStream, PART_URLS

CLI runners (scripts/, outside the package):

FilePurpose
stream.pyStream-extract GLB rooms from huanngzh/3D-Front.
evaluate.pySubcommands: validate, stress, diverse, mesh, compare, predicates.
diagrams.pyRender the meta-statistic + relationship-graph PNGs.
run_all.pyEnd-to-end orchestrator.
build_verification_csv.pyGenerate VERIFICATION.csv.

Original data format

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:

  • Rotation is baked into the mesh geometry, not stored on the node transform — so you can't read yaw from the matrix. We recover it via PCA on the XZ point cloud.
  • The scene is per-room normalized into roughly a unit cube. Absolute metres aren't meaningful; every threshold in params.THRESH is tuned to that scale.

Forward transform — GLB → (N, 128)

Stage 1: GLB → PlacedObject (data.py)

FieldHow we get it
class_superregex on filename prefix → PREFIX_TO_SUPERCAT (7 buckets: Bed, Sofa, Chair, Table, Cabinet/Shelf, Lighting, Decor)
jid8-hex stem after the prefix
posAABB 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.
yawangle of the principal eigenvector of the XZ covariance
scale[1, 1, 1] (mesh is already at world scale)
rot_quatderived from yaw (kept for downstream tools that want a quaternion)

Stage 2: 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]).

Inverse transform — (N, 128) → DecodedObject

Implemented 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 fieldHow we recover it
class_supernearest 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.
posdirect slice v[64:67]
yawatan2(v[67], v[68])
scaledirect 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 exact mesh — same; the natural next step is asset retrieval + place.
  • Affordance and relationship slots are derived features, not state. They exist to condition a diffusion model, not to be inverted.

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.

Contributors

HaysonC

3 commits

HaysonC/starshot

0

stars

3

commits

Python

primary language

May 4, 2026

updated

README

parameterization · 3D-FRONT (N, 128) parameterization

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

Install

python -m venv env && source env/bin/activate
pip install -e .                 # installs parameterization + its dependencies

How to run

# 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

Library use

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))

Outputs

  • 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.

Package layout (src/parameterization/)

ModulePublic surface
dataPlacedObject, Room, Scene, load_scenes, parse_object_glb
paramsD, SLOT_*, room_tensor, decode_room, class_embed, PREDICATES, THRESH, OBB helpers
evaluatePredicateEngine, AblationSpec, EnergyRecoverer, RecoveryResult, six evaluator classes
diagramsmeta-stat / relationship-graph plotting functions
vizdraw_room, draw_room_3d
themedark neon matplotlib/seaborn helpers (apply(), glow_bars, render_table, …)
streamConcatStream, PART_URLS

CLI runners (scripts/, outside the package):

FilePurpose
stream.pyStream-extract GLB rooms from huanngzh/3D-Front.
evaluate.pySubcommands: validate, stress, diverse, mesh, compare, predicates.
diagrams.pyRender the meta-statistic + relationship-graph PNGs.
run_all.pyEnd-to-end orchestrator.
build_verification_csv.pyGenerate VERIFICATION.csv.

Original data format

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:

  • Rotation is baked into the mesh geometry, not stored on the node transform — so you can't read yaw from the matrix. We recover it via PCA on the XZ point cloud.
  • The scene is per-room normalized into roughly a unit cube. Absolute metres aren't meaningful; every threshold in params.THRESH is tuned to that scale.

Forward transform — GLB → (N, 128)

Stage 1: GLB → PlacedObject (data.py)

FieldHow we get it
class_superregex on filename prefix → PREFIX_TO_SUPERCAT (7 buckets: Bed, Sofa, Chair, Table, Cabinet/Shelf, Lighting, Decor)
jid8-hex stem after the prefix
posAABB 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.
yawangle of the principal eigenvector of the XZ covariance
scale[1, 1, 1] (mesh is already at world scale)
rot_quatderived from yaw (kept for downstream tools that want a quaternion)

Stage 2: 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]).

Inverse transform — (N, 128) → DecodedObject

Implemented 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 fieldHow we recover it
class_supernearest 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.
posdirect slice v[64:67]
yawatan2(v[67], v[68])
scaledirect 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 exact mesh — same; the natural next step is asset retrieval + place.
  • Affordance and relationship slots are derived features, not state. They exist to condition a diffusion model, not to be inverted.

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.

Contributors

HaysonC

3 commits

Languages

Python

91.5%

JavaScript

6.8%

HTML

1.8%