Skip to content

Liver segmentectomy planning: tumor-driven cut selection - #5

Open
bouamarakamelia wants to merge 1 commit into
masterfrom
dev_kamelia_tumor_placement
Open

Liver segmentectomy planning: tumor-driven cut selection #5
bouamarakamelia wants to merge 1 commit into
masterfrom
dev_kamelia_tumor_placement

Conversation

@bouamarakamelia

Copy link
Copy Markdown

1. Description

This PR adds the ability to determine, automatically, which liver segments and vessel-skeleton nodes would be affected by resecting the vessel near a tumor replacing manual, hand-picked cut node ids with a pipeline that goes straight from "where is the tumor" to "what would this resection devascularize."

It lets you answer: given a tumor's location, which vessel branch feeds it, and if I cut there, which Couinaud segment(s) lose blood supply? --> without ever typing a node number by hand.

2. Features

a. New component logic, class diagram, and how to use it

classDiagram
  class DataEngine { <<sofa>> }
  class MeshLoader { <<sofa>> }
 
  class SkeletonGraph {
    +buildTree(entryPoint)
    +buildTreeAutoRoot()
    +assignSegmentLabels(rawLabels)
    +simulateResection(cutIds) vector~int~
    +affectedSegments(ids) vector~int~
  }
  class ClosedMeshQuery {
    <<cgalutils, shared>>
    +buildFrom(vertices, triangles)
  }
 
  class SkeletonReader~DataTypes~ {
    +graph() SkeletonGraph
  }
  class SkeletonSegmentMapper~DataTypes~ {
    +l_segmentMeshes
    +segmentNames
    +graph() SkeletonGraph
  }
  class TumorCutPointSelector~DataTypes~ {
    +l_tumorMeshes
    +cutNodeIds
    +untouchedTumorIds
  }
  class SkeletonResectionSimulator~DataTypes~ {
    +cutNodeIds
    +affectedNodeIds
    +affectedSegmentNames
    +activationDelay
  }
 
  DataEngine <|-- SkeletonReader
  DataEngine <|-- SkeletonSegmentMapper
  DataEngine <|-- TumorCutPointSelector
  DataEngine <|-- SkeletonResectionSimulator
 
  SkeletonSegmentMapper --> SkeletonReader : reads
  SkeletonSegmentMapper --> MeshLoader : segment meshes
  SkeletonSegmentMapper ..> ClosedMeshQuery : point-in-mesh
  TumorCutPointSelector --> SkeletonSegmentMapper : reads
  TumorCutPointSelector --> MeshLoader : tumor meshes
  TumorCutPointSelector ..> ClosedMeshQuery : point-in-mesh
  SkeletonResectionSimulator --> SkeletonSegmentMapper : reads
  SkeletonResectionSimulator --> TumorCutPointSelector : cutNodeIds
Loading

Four components, each a single responsibility, chained together:

  1. SkeletonReader loads the vessel centerline and roots it as a tree (auto-picking a root from the largest connected component when no explicit entry point is known, rather than guessing).
  2. SkeletonSegmentMapper assigns every skeleton node its Couinaud segment, via point-in-mesh testing against the 8 segment volumes, smoothed by majority vote per branch so boundary noise doesn't fragment a branch across segments.
  3. TumorCutPointSelector for each tumor (one mesh per tumor), finds which skeleton node(s) it physically touches, using the same point-in-mesh approach (bounding-box pre-filter, then a real geometric test never the bbox alone, since a non-convex tumor's box overstates its volume). Outputs a ready-to-use cutNodeIds list, plus which tumors touched nothing (flagging where a "nearest node in the tumor's segment" fallback would still be needed).
  4. SkeletonResectionSimulator given cut node(s) from any source (manual or TumorCutPointSelector), computes which nodes and segments actually lose blood supply, via reachability from the root rather than a naive "everything downstream" count a collateral vessel elsewhere in the tree can rescue part of what a plain subtree walk would wrongly flag as devascularized.

How to use it, end to end:

  1. Load the vessel mesh, run MeshSkeletonization, read it back with SkeletonReader.
  2. Load the 8 Couinaud segment meshes, link them into SkeletonSegmentMapper.
  3. Load each tumor as its own mesh, link them into TumorCutPointSelector along with the segment mapper.
  4. Link SkeletonResectionSimulator.cutNodeIds to @tumorSelector.cutNodeIds the resection now follows tumor placement automatically.
  5. Read back affectedNodeIds / affectedSegmentNames for the answer, or watch the live visualization (skeleton points and segment meshes color-switch from green to red/black after a configurable delay).
  6. Always check untouchedTumorIds any tumor listed there didn't touch the skeleton directly and has no cut point yet.

b. SOFA scene structure

<Node name="Mesh">
    <MeshOBJLoader name="loader" filename="..."/>
    <MeshSkeletonization name="skel" inputVertices="@loader.position" inputTriangles="@loader.triangles" outputSkeleton="..."/>
    <SkeletonReader name="reader" filename="..." inputVertices="@loader.position" outputVTK="..." outputReport="..."/>
</Node>
 
<Node name="LiverSegments">
    <MeshOBJLoader name="segII" filename="..."/> <!-- ... segIII..segVIII ... -->
    <SkeletonSegmentMapper name="segmentMapper"
        skeletonReader="@../Mesh/reader"
        segmentMeshes="@segII @segIII @segIVa @segIVb @segV @segVI @segVII @segVIII"
        segmentNames="II III IVa IVb V VI VII VIII"/>
</Node>
 
<Node name="Tumors">
    <MeshOBJLoader name="tumor1Collision" filename="..."/> <!-- one loader per tumor -->
    <TumorCutPointSelector name="tumorSelector"
        tumorMeshes="@tumor1Collision @tumor2Collision @tumor3Collision"
        tumorNames="tumor-1 tumor-2 tumor-3"
        segmentMapper="@../LiverSegments/segmentMapper"/>
</Node>
 
<SkeletonResectionSimulator name="resectionSim"
    segmentMapper="@LiverSegments/segmentMapper"
    cutNodeIds="@Tumors/tumorSelector.cutNodeIds"
    activationDelay="2.0"
    segmentVisualModels="@LiverSegments/visII/oglII @LiverSegments/visIII/oglIII ..."
    outReportFilename="..."/>

Three sibling Nodes (Mesh, LiverSegments, Tumors) feed into one resectionSim at the root each stage's output is the next stage's input, all the way from raw meshes to a final devascularization report.

3. Results

Verified against real patient data at each stage:

  • Segment mapping: point-in-mesh + branch-majority smoothing correctly assigns Couinaud segments to skeleton nodes, confirmed against patient 08's 8 segment meshes.
  • Tumor touch detection: confirmed on 3 real, separate tumor meshes (Patient 08) bounding boxes ranging 5×3×6mm to 15×15×14mm, at distinct locations across the liver.
  • Single-segment resection points, confirmed via the reachability algorithm (not naive subtree):
    Segment Cut node Nodes affected
    II 390 4 (127 by naive subtree — a real collateral edge rescues 123)
    V 135 65
    VI 88 14
    VII 824 16
    VIII 555 117
  • End-to-end pipeline, confirmed on a second patient (Patient 01) after fixing an unrelated root-selection bug that was silently corrupting every downstream stage tree connectivity, segment assignment, and resection output all now resolve correctly with no manual entry point required.
image image

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