Skip to content

Toblerone#3397

Draft
Lestropie wants to merge 2 commits into
devfrom
toblerone
Draft

Toblerone#3397
Lestropie wants to merge 2 commits into
devfrom
toblerone

Conversation

@Lestropie

Copy link
Copy Markdown
Member

Given I recently attacked the long-standing mesh2voxel bug (#3391), #2732 came to mind. I had some recollection that it wasn't just the computation of partial volume for intersected voxels that was different, there was also some difference in how it dealt with labelling voxels inside vs. outside that surface, which potentially would have obviated that mesh2voxel issue. So I thought I'd take the chance to throw an agent at it, given mesh2voxel is often a performance bottleneck, particularly in 5ttgen hsvs.

~ 7x performance improvement, <= 0.025 PVE difference to my brute force approach (with no real guarantee that mine is in fact more accurate).

  • Reconsider -subvoxel option:
    In addition to defining the default value as a constexpr and printing it into the command help page rather than duplicating, it might be preferable to change how that default is determined, given the software may operate on non-human data. Perhaps it should instead be a voxel size fraction?

  • @Lestropie Full manual code review.

Lestropie added 2 commits June 8, 2026 21:28
Implements the geometric "Toblerone" voxelisation method (Kirk et al.,
IEEE TMI 2020) as a selectable alternative to the existing brute-force
partial volume estimator, exposed through a new -algorithm option on
mesh2voxel and made the default. The shared first stage (surface-voxel
intersection) is retained, after which Toblerone classifies whole voxels
by a deterministic ray-casting parity fill and estimates edge-voxel
fractions by adaptive sub-voxel subdivision, using exact convex-polytope
clipping for clean planar cuts and finer reduced-ray resampling
elsewhere. This replaces the brute-force method's dense fixed point
lattice and its heuristic interior/exterior classification, improving
both speed and robustness while leaving the original algorithm available
and unchanged.

Session prompts:
1. > File "toblerone.txt" provides the content of a scientific journal article that describes a method for computing, from a closed surface mesh in 3D, partial volume fractions for a 3D voxel grid. MRtrix3 already has functionality for this task, implemented in cpp/core/surface/algo/mesh2image.* and interfaced in command cpp/cmd/mesh2voxel.cpp. However this implementation is both slow (as it involves generating and testing a dense 3D point lattice for each intersected voxel) and bug-prone (relies on heuristic classification of non-intersected voxels as inside vs. outside surface). Attempt to draft a plan for implementation of the "Toblerone" algorithm alongside the existing brute-force algorithm. Comment on whether the failure to adequately propagate mathematical symbols into this text file, or the absence of demonstrative figure images, is prohibitive. Additional web searching is permitted.

Generated-by: Claude Opus 4.8 <noreply@anthropic.com>
- Add manuscript describing method to mesh2voxel references.
- Update test data and tests.
@Lestropie Lestropie self-assigned this Jun 8, 2026

@github-actions github-actions 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.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 25 out of 96. Check the log or trigger a new build to see more.

Comment thread cpp/cmd/mesh2voxel.cpp
+ Argument ("template", "the template image").type_image_in()
+ Argument ("output", "the output image").type_image_out();

OPTIONS

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: no header providing "MR::App::OPTIONS" is directly included [misc-include-cleaner]

  OPTIONS
  ^

Comment thread cpp/cmd/mesh2voxel.cpp

// Select the partial volume estimation algorithm
Surface::Algo::Mesh2ImageOptions options;
auto opt = get_options("algorithm");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: no header providing "MR::App::get_options" is directly included [misc-include-cleaner]

  auto opt = get_options("algorithm");
             ^

Comment thread cpp/cmd/mesh2voxel.cpp
options.method = MR::Enum::from_name<Surface::Algo::Mesh2ImageMethod>(opt[0][0]);
opt = get_options("subvoxel");
if (!opt.empty())
options.subvoxel_mm = static_cast<default_type>(opt[0][0]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: no header providing "MR::default_type" is directly included [misc-include-cleaner]

cpp/cmd/mesh2voxel.cpp:26:

+ #include "types.h"


void mesh2image(const Mesh &mesh_realspace, Image<float> &image) {
// For every edge voxel, stores those polygons that may intersect the voxel
using Vox2Poly = std::map<Vox, std::vector<size_t>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: no header providing "std::vector" is directly included [misc-include-cleaner]

cpp/core/surface/algo/mesh2image.cpp:24:

+ #include <vector>

namespace {

// Encoding of the per-voxel interior/exterior classification used by the Toblerone algorithm
enum vox_class_t : uint8_t { VC_OUTSIDE = 0, VC_INSIDE = 1, VC_ON_MESH = 2 };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: no header providing "uint8_t" is directly included [misc-include-cleaner]

cpp/core/surface/algo/mesh2image.cpp:20:

- #include <map>
+ #include <cstdint>
+ #include <map>

}
for (size_t axis = 0; axis != 3; ++axis) {
if (std::min({v0[axis], v1[axis], v2[axis]}) > box_half[axis] ||
std::max({v0[axis], v1[axis], v2[axis]}) < -box_half[axis])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: narrowing conversion from 'size_t' (aka 'unsigned long') to signed type 'Index' (aka 'long') is implementation-defined [bugprone-narrowing-conversions]

        std::max({v0[axis], v1[axis], v2[axis]}) < -box_half[axis])
                               ^

}
for (size_t axis = 0; axis != 3; ++axis) {
if (std::min({v0[axis], v1[axis], v2[axis]}) > box_half[axis] ||
std::max({v0[axis], v1[axis], v2[axis]}) < -box_half[axis])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: narrowing conversion from 'size_t' (aka 'unsigned long') to signed type 'Index' (aka 'long') is implementation-defined [bugprone-narrowing-conversions]

        std::max({v0[axis], v1[axis], v2[axis]}) < -box_half[axis])
                     ^

return false;
}
if (separating(e0.cross(e1)))
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: redundant boolean literal in conditional return statement [readability-simplify-boolean-expr]

cpp/core/surface/algo/mesh2image.cpp:187:

-   if (separating(e0.cross(e1)))
-     return false;
-   return true;
+   return !separating(e0.cross(e1));


// Whether any polygon of the local patch overlaps the axis-aligned box (centre, half-extents)
bool patch_overlaps_box(const Vertex &centre, const Vertex &half, const std::vector<size_t> &polys, const Mesh &mesh) {
for (const size_t poly_index : polys) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: replace loop by 'std::any_of()' [readability-use-anyofallof]

  for (const size_t poly_index : polys) {
  ^

// Volume of the part of tetrahedron `v` interior to the plane through q with outward normal n,
// i.e. the side where (x - q).n <= 0, by enumeration of the marching-tetrahedra cases.
default_type tetra_clip_interior_volume(const std::array<Vertex, 4> &v, const Vertex &q, const Vertex &n) {
std::array<default_type, 4> f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

warning: uninitialized record type: 'f' [cppcoreguidelines-pro-type-member-init]

Suggested change
std::array<default_type, 4> f;
std::array<default_type, 4> f{};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant