Skip to content

[Skeletonization+ SOFA] Liver resection simulation: segment mapping + candidate-cut analysis - #3

Merged
epernod merged 16 commits into
masterfrom
dev_kamelia_new_comp_couinaud_segments
Aug 25, 2026
Merged

[Skeletonization+ SOFA] Liver resection simulation: segment mapping + candidate-cut analysis#3
epernod merged 16 commits into
masterfrom
dev_kamelia_new_comp_couinaud_segments

Conversation

@bouamarakamelia

Copy link
Copy Markdown

1. Description

Adds a full liver-resection simulation pipeline on top of the existing skeletonization plugin:

  • mapping each skeleton node to its Couinaud segment,
  • simulating a candidate vessel resection at one or more nodes,
  • and driving live visual feedback (skeleton points and the segment meshes themselves) that reflects the outcome, gated behind a configurable delay (5s).
    This closes the loop from "we have a vessel skeleton" to "here's what a given cut would devascularize, shown on screen."

2. Features

a. New component logic and class diagram

Class diagram

diagramme de classe

Algorithm core (SkeletonGraph, unchanged SOFA/CGAL-free design):

  • assignSegmentLabels(rawNodeLabels) : per-node segment ids, smoothed by majority vote over maximal tree branches so boundary noise doesn't fragment a branch across segments.
  • simulateResection(cutNodeIds) : BFS reachability from the root over the raw connectivity graph (m_adjacency), skipping every cut node. Returns every node no longer reachable : Affected(C) = C ∪ (V \ Perfused(C)). Deliberately distinct from a plain subtree() walk : a collateral/loop edge elsewhere in the graph can keep a node reachable even after its "obvious" path is severed.
  • affectedSegments(affectedNodeIds) : distinct segments touched by an affected-node set.

SkeletonSegmentMapper (new DataEngine): links a SkeletonReader plus a MultiLink of 8 segment MeshLoaders. For each skeleton node, tests point-in-mesh (CGAL::Side_of_triangle_mesh) against every segment mesh, falls back to nearest surface if none contain it, then hands the raw labels to assignSegmentLabels().

SkeletonResectionSimulator (new DataEngine): links a SkeletonSegmentMapper plus, optionally, the actual segment OglModels (l_segmentVisualModels). Key pieces:
- cutNodeIds / activationDelay inputs : the candidate cut, and how many seconds of sim time before the visual result actually shows.
- updateSegmentColors() : polls getContext()->getTime() every draw() call (no event listener needed) to decide perfused vs. affected color, then pushes it two ways: as a segmentColors output Data, and directly into each linked OglModel via findData("material") : reading the object's current material string, patching just the Diffuse <flag> r g b a tokens, and writing it back. This round-trip avoids hard-coding the Material struct's exact serialization format.
- draw() renders the skeleton itself as colored points (green/red/black), same time-gating.

b. SOFA scene structure

flowchart TB
  subgraph MeshNode["Node: Mesh"]
    loader["MeshOBJLoader loader"]
    skel["MeshSkeletonization skel"]
    ogl["OglModel (vein mesh)"]
    reader["SkeletonReader reader"]
  end

  subgraph SegNode["Node: LiverSegments"]
    segLoaders["MeshOBJLoader x8 segII..segVIII"]
    oglSegs["OglModel x8 oglII..oglVIII"]
    mapper["SkeletonSegmentMapper segmentMapper"]
    sim["SkeletonResectionSimulator resectionSim<br/>cutNodeIds, activationDelay"]
  end

  loader --> skel
  loader --> ogl
  loader -->|inputVertices| reader
  skel -->|writes skeleton.txt| readerr

  reader -->|skeletonReader link| mapper
  segLoaders -->|segmentMeshes link| mapper

  mapper -->|segmentMapper link| sim
  sim -->|"segmentVisualModels link:<br/>findData(material).read(...) every draw()"| oglSegs

Loading
Node: Mesh
├─ MeshOBJLoader (loader)
├─ MeshSkeletonization (skel)          --> writes skeleton.txt
├─ Node: visual → OglModel             --> renders the vein mesh
└─ SkeletonReader (reader)              --> reads skeleton.txt/.vtk, builds rooted tree

Node: LiverSegments
├─ MeshOBJLoader × 8 (segII…segVIII)
├─ Node: visII…visVIII --> OglModel × 8 (oglII…oglVIII)
├─ SkeletonSegmentMapper (segmentMapper)
│    skeletonReader = @../Mesh/reader
│    segmentMeshes  = @segII … @segVIII
└─ SkeletonResectionSimulator (resectionSim)
     segmentMapper       = @segmentMapper
     segmentVisualModels = @visII/oglII … @visVIII/oglVIII
     cutNodeIds, activationDelay

Scene-level requirements this PR depends on:
<RequiredPlugin pluginName="MeshSkeletonizationPlugin"/>, and showBehaviorModels in VisualStyle's displayFlags (otherwise draw() never fires).

3. Results

Verified against Patient 08's venous skeleton (986 nodes, root at node 574):

Scenario Cut node Nodes affected Segments hit
Cut near the trunk 971 950 / 986 6 of 8
Cut deep in one segment 135 65 V only

Confirmed single-segment resection points, one per segment:

Segment Cut node Nodes affected
II 390 4 (127 by naive subtree — collateral edge (941,942) rescues 123)
V 135 65
VI 88 14
VII 824 16
VIII 555 117

The segment II case is the concrete evidence that the reachability formula (not plain subtree) is load-bearing here, a real collateral in the data changes the answer by two orders of magnitude.

Skeleton results

image

Segment results

image

@epernod epernod added pr: new feature New feature pr: status to review To notify reviewers to review this pull-request labels Aug 25, 2026
@epernod epernod added pr: run ci pr: status to review To notify reviewers to review this pull-request and removed pr: status to review To notify reviewers to review this pull-request labels Aug 25, 2026

@epernod epernod 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.

ok works on my computer. Many things I would like to update but let's do that in next PR

@epernod epernod added pr: status ready Approved a pull-request, ready to be squashed and removed pr: status to review To notify reviewers to review this pull-request labels Aug 25, 2026
@epernod
epernod merged commit 23728a5 into master Aug 25, 2026
5 checks passed
@epernod
epernod deleted the dev_kamelia_new_comp_couinaud_segments branch August 25, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr: new feature New feature pr: run ci pr: status ready Approved a pull-request, ready to be squashed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants