Skip to content

feat(#410): retrain mesh segmentation model — 31.5% → 94.7% on rig-truth eval#787

Merged
fernandotonon merged 2 commits into
masterfrom
feat/meshseg-v2-410
Jul 2, 2026
Merged

feat(#410): retrain mesh segmentation model — 31.5% → 94.7% on rig-truth eval#787
fernandotonon merged 2 commits into
masterfrom
feat/meshseg-v2-410

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

The shipped meshseg.onnx (v1) segmented real meshes poorly — 31.5% per-vertex accuracy against exact rig-derived ground truth, with near-random limb labels and chibi-proportioned characters collapsing to all-torso. This PR retrains it end to end; the new model scores 94.7% on the same eval and is already uploaded to the HF models repo (segment/meshseg.onnx), so existing installs pick it up on next first-use download. No C++ changes — the ONNX contract is unchanged.

Root causes of the v1 failure (all confirmed empirically)

  1. Points sampled inside primitive volumes; real mesh vertices live on surfaces
  2. No chibi proportions (v1 heads 7–18% of height; real stylised characters reach ~50% with huge ears)
  3. Uniform per-part density (real characters put 30–50% of vertices in the head/face)
  4. LEFT/RIGHT flipped vs the rig-prior convention (left limbs at +X, facing +Z)
  5. Trained at 1024 points but MeshSegmenter::predict() samples 4096
  6. Disconnected floating primitives vs connected real bodies

What v2 does (scripts/export-meshseg-onnx.py, same CLI)

  • Synthetic data: surface-sampled connected bodies in three plans — humanoid (normal/chibi/lanky, ears/muzzle, posed arms, feet +Z facing cue), quadruped (legs labelled by side, tail→torso, matching partForBoneName), biped-with-tail — with randomised per-part density and correct handedness
  • Mined CC0 rigs (Quaternius packs, provenance ledger in the corpus SOURCES.md): the loader canonicalises arbitrarily-oriented bind-pose clouds from their own labels (scored up-axis candidates, limb-axis left, 180° yaw fix for backward-facing animals) and geometrically reassigns arm/leg sides — the miner's bone-name side detection was up to ~30% wrong on some rigs; incoherent files are excluded
  • Model: PointNet++-style with two kNN aggregation blocks (~1 MB), unknown-masked class-weighted loss, phase-2 fine-tune at the app's 4096-point inference size
  • New --check-real mode audits canonicalisation of a mined corpus

Results (exact rig-derived ground truth)

eval set v1 v2
rig-truth, 3 out-of-distribution characters (kept out of training entirely) 31.5% 94.7%
held-out CC0 rigs (Male_Suit, Female_Dress, Sheep, Velociraptor) 97.0%

End-to-end via qtmesh segment (3.14.0-dev ONNX build): rigged meshes reproduce rig-truth part counts almost exactly; the same character exported to unrigged OBJ (pure model path) correctly lateralises all limbs with only shoulder/hip boundary bleed. Verified visually in the GUI.

Docs

  • docs/MESH_SEGMENTATION_STRATEGY.md — failure analysis, results, and the proposed multi-category roadmap (vegetation/vehicle/building label sets via per-category models + name-mined CC0 packs)
  • THIRD_PARTY_AI_MODELS.md / CLAUDE.md refreshed; fetch-training-rigs.sh records the stable CC0 mirror source
  • Model cards updated on both HF repos (models, dedicated)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fernandotonon, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f0021727-b397-45ae-86d0-869a7f418197

📥 Commits

Reviewing files that changed from the base of the PR and between 3ebc54b and 62bb418.

📒 Files selected for processing (6)
  • .gitignore
  • CLAUDE.md
  • THIRD_PARTY_AI_MODELS.md
  • docs/MESH_SEGMENTATION_STRATEGY.md
  • scripts/export-meshseg-onnx.py
  • scripts/fetch-training-rigs.sh
📝 Walkthrough

Walkthrough

This PR updates the mesh segmentation model documentation and rewrites the training/export pipeline in scripts/export-meshseg-onnx.py. Changes introduce surface-sampled synthetic body generation, mined-rig canonicalization for left/right label correction, a PointNet++-style architecture, and updated .gitignore/fetch-script entries.

Changes

Mesh Segmentation v2 Update

Layer / File(s) Summary
Strategy documentation for v2 model
docs/MESH_SEGMENTATION_STRATEGY.md, CLAUDE.md, THIRD_PARTY_AI_MODELS.md
New strategy doc covers v1 failure analysis, v2 data pipeline/results, category roadmap, and continual improvement; CLAUDE.md and THIRD_PARTY_AI_MODELS.md updated to describe v2 training data and PointNet++ architecture.
Synthetic surface-sampled data generation
scripts/export-meshseg-onnx.py
Docstring and generator rewritten to produce surface-sampled body-plan point clouds with augmentation/mirroring at fixed N_BASE=4096 size.
Mined rig canonicalization and validation
scripts/export-meshseg-onnx.py
Adds canonicalise() to align mined rig clouds and fix left/right labels; load_real_data() reworked with exclude/only filtering; adds check_real() diagnostics.
Training/export flow and model wiring
scripts/export-meshseg-onnx.py
main() updated to two-phase training (2048 then 4096 points) using new datasets and PointNet++-style network before ONNX export.
Repo hygiene and data source references
.gitignore, scripts/fetch-training-rigs.sh
Adds ignore rules for local mesh segmentation artifacts/venv/pycache, unignores the strategy doc, and adds a curated Quaternius CC0 source reference.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • fernandotonon/QtMeshEditor#771: Related to the same mesh segmentation model export/training pipeline consumed by the mesh part segmentation feature.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: retraining the mesh segmentation model and the improved eval result.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is detailed, on-topic, and covers summary, technical changes, results, and docs, though it omits some template sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/meshseg-v2-410

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ebc54bea2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +109 to +111
caps_dir = _unit_dirs(nc, rng)
which = rng.random(nc) < 0.5
caps = np.where(which[:, None], p0 + caps_dir * r, p1 + caps_dir * r)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sample capsule caps from exterior hemispheres

During v2 training, every limb/neck/tail that goes through capsule_surf() samples cap directions from a full sphere and attaches the whole sphere to each endpoint. Half of those cap points land on the inward side of the endpoint sphere, inside the cylinder rather than on the exterior capsule surface, so the supposedly surface-only training set still injects non-mesh-surface points for all capsule parts; restrict each cap to its outward hemisphere to match real mesh vertices.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 62bb418 — cap directions are now reflected into their outward hemispheres, so every sampled point lies exactly on the capsule surface (verified: all points at distance r from the axis segment). Note: the v2 weights currently on HF were trained before this purity fix; the caps are a small fraction of each limb's points, and the model is validated at 94.7% rig-truth accuracy, so the fix lands in the data pipeline for the next retrain rather than triggering an immediate re-release.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
scripts/export-meshseg-onnx.py (1)

472-479: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate file-collection logic between load_real_data() and check_real().

Both functions independently glob/collect files from paths:

files = []
for pth in paths:
    files += sorted(glob.glob(os.path.join(pth, "*.json"))) if os.path.isdir(pth) else [pth]

Extract a shared helper to avoid drift between the two.

♻️ Proposed refactor
+def _collect_json_files(paths):
+    files = []
+    for pth in paths:
+        files += sorted(glob.glob(os.path.join(pth, "*.json"))) if os.path.isdir(pth) else [pth]
+    return files
+
+
 def load_real_data(paths, aug, seed, exclude=(), only=()):
     rng = np.random.default_rng(seed + 777)
-    files = []
-    for pth in paths:
-        files += sorted(glob.glob(os.path.join(pth, "*.json"))) if os.path.isdir(pth) else [pth]
+    files = _collect_json_files(paths)
     files = [f for f in files if not any(e in os.path.basename(f) for e in exclude)]
 def check_real(paths):
     """--check-real: print canonicalisation sanity for every mined file."""
-    files = []
-    for pth in paths:
-        files += sorted(glob.glob(os.path.join(pth, "*.json"))) if os.path.isdir(pth) else [pth]
+    files = _collect_json_files(paths)

Also applies to: 506-511

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/export-meshseg-onnx.py` around lines 472 - 479, The file-collection
logic in load_real_data() and check_real() is duplicated and should be
centralized to prevent drift. Extract the shared path-to-json gathering loop
into a helper that both functions call, keeping the existing filtering behavior
in load_real_data() intact. Use the unique symbols load_real_data() and
check_real() to locate and replace the repeated globbing block with the shared
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/export-meshseg-onnx.py`:
- Around line 380-470: The current side sanity check in check_real() is
self-referential because canonicalise() already recomputes Lc from Pc, so a
global L/R swap can still pass as ok. Update check_real() to use an independent
reference-based left/right validation against the original labels or raw
geometry before canonicalisation, using canonicalise(), Pc, and Lc; if no such
reference exists, remove the arm/leg side assertion and keep only the geometry
consistency checks.

In `@scripts/fetch-training-rigs.sh`:
- Around line 28-32: The Quaternius mirror note in the sources list is too broad
and should be narrowed to the exact CC0 subfolders actually used. Update the
entry referenced by the comment in fetch-training-rigs.sh so it mentions only
the specific “FreeModels by Quaternius[Patreon]/Characters and Animals/” paths
used by the corpus, and avoid labeling the entire GitHub mirror as CC0. Keep the
exclusion of the “[Patreon Exclusive]” folders explicit so the recorded source
scope matches the data selection.

---

Nitpick comments:
In `@scripts/export-meshseg-onnx.py`:
- Around line 472-479: The file-collection logic in load_real_data() and
check_real() is duplicated and should be centralized to prevent drift. Extract
the shared path-to-json gathering loop into a helper that both functions call,
keeping the existing filtering behavior in load_real_data() intact. Use the
unique symbols load_real_data() and check_real() to locate and replace the
repeated globbing block with the shared helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7bc9a20-37d3-46bd-8449-b42e68126d27

📥 Commits

Reviewing files that changed from the base of the PR and between 670c866 and 3ebc54b.

📒 Files selected for processing (6)
  • .gitignore
  • CLAUDE.md
  • THIRD_PARTY_AI_MODELS.md
  • docs/MESH_SEGMENTATION_STRATEGY.md
  • scripts/export-meshseg-onnx.py
  • scripts/fetch-training-rigs.sh

Comment thread scripts/export-meshseg-onnx.py
Comment thread scripts/fetch-training-rigs.sh Outdated
…uth eval

The v1 meshseg.onnx scored 31.5% per-vertex accuracy against exact
rig-derived ground truth (near-random limbs; chibi characters collapsed
to all-torso). Root causes: volumetric instead of surface point sampling,
no chibi proportions, uniform per-part vertex density, LEFT/RIGHT flipped
vs the rig-prior convention, and a 1024-vs-4096 train/inference point-count
mismatch.

export-meshseg-onnx.py v2 (same CLI, new pipeline):
- surface-sampled connected synthetic bodies in three plans (humanoid
  incl. chibi/lanky, quadruped with side-labelled legs, biped-with-tail),
  randomised per-part density, feet/muzzle +Z facing cues, correct
  handedness (LEFT at +X)
- mined CC0 Quaternius rigs (ledger in the corpus SOURCES.md): the loader
  canonicalises arbitrarily-oriented bind-pose clouds from their own
  labels (scored up-axis candidates, left from limb axis, 180° yaw fix)
  and geometrically reassigns arm/leg sides (miner side detection was up
  to ~30% wrong on some rigs); incoherent files are excluded
- deeper PointNet++-style net (two kNN blocks, ~1 MB), unknown-masked
  class-weighted loss, phase-2 fine-tune at the app's 4096-point
  inference size
- --check-real mode to audit canonicalisation of a mined corpus

Results (exact rig-truth eval on out-of-distribution characters kept out
of training): 94.7% overall vs 31.5% baseline; held-out CC0 rigs 97.0%.
End-to-end via qtmesh segment: rigged meshes reproduce rig-truth counts;
unrigged OBJ path correctly lateralises all limbs. Model uploaded to the
HF models repo (segment/meshseg.onnx).

Docs: docs/MESH_SEGMENTATION_STRATEGY.md (failure analysis, results,
multi-category roadmap for vegetation/vehicle/building segmentation),
THIRD_PARTY_AI_MODELS.md + CLAUDE.md refreshed, fetch-training-rigs.sh
records the stable CC0 mirror source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fernandotonon fernandotonon force-pushed the feat/meshseg-v2-410 branch from 3ebc54b to 09b06eb Compare July 2, 2026 00:21
…ped mirror provenance

- capsule_surf: reflect cap directions into their outward hemispheres so
  every sampled point lies ON the capsule surface (half the cap points
  previously landed inside the cylinder — off-surface noise in the
  supposedly surface-only training set). Verified: all sampled points now
  at exactly distance r from the axis segment.
- check_real: the post-canonicalisation L/R centroid test was
  self-referential (canonicalise() rewrites sides geometrically, so it
  held by construction). The pass criterion is now geometry-only, and the
  independent miner-quality signal is the new `sidefix` fraction — how
  many limb points' MINED side labels disagreed with the geometric side
  (a whole-file swap surfaces as sidefix ≈ 100%).
- fetch-training-rigs.sh: scope the mirror provenance note to the exact
  CC0 Quaternius subfolders and require per-pack SOURCES.md rows instead
  of a blanket [CC0 content] label for the whole mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit 7c85e95 into master Jul 2, 2026
21 checks passed
@fernandotonon fernandotonon deleted the feat/meshseg-v2-410 branch July 2, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant