vtkResliceCursorWidget 系统设计还原

基于 VTK 9.5.0 源码 Interaction/Widgets/ 分析
涉及类:vtkResliceCursorWidget / vtkResliceCursorRepresentation / vtkResliceCursorLineRepresentation /
vtkResliceCursor / vtkResliceCursorActor / vtkResliceCursorPolyDataAlgorithm / vtkResliceCursorPicker


一、整体架构(MVC 分层 + 数据管线)

VTK Widget 框架的标准三层(Widget / Representation / Prop),叠加一条独立的重切片数据管线

PROP / 几何算法层 · 渲染

MODEL 数据模型层

VIEW + INTERACTION 视图/交互层

CONTROLLER 控制器层 · 事件分发

映射

持有 WidgetRep · 委派交互

继承实现

读/写

持有

组合

模型来源

共享

vtkResliceCursorWidget
WidgetState / ManageWindowLevel
SelectAction · RotateAction · TranslateAction
MoveAction · EndSelectAction · ResizeThicknessAction

vtkWidgetCallbackMapper
事件+修饰键 → 回调

vtkResliceCursorRepresentation 抽象
ManipulationMode / InteractionState
Reslice · ColorMap · ResliceAxes · Picker

vtkResliceCursorLineRepresentation
RotateAxis · TranslateAxis · WidgetInteraction
+ ResliceCursorActor

vtkResliceCursor
Center · XAxis/YAxis/ZAxis
Thickness · ThickMode · Image

vtkResliceCursorActor (vtkProp3D)
CenterlineActor[3] · ThickSlabActor[3]

vtkResliceCursorPolyDataAlgorithm
Cursor 模型 → 线条 PolyData

关键设计点

角色职责
控制器vtkResliceCursorWidget只做事件→动作映射,把坐标交给 Rep,不含几何运算
视图/交互vtkResliceCursorRepresentation切片图像管线 + 交互态计算(抽象)
交互实现vtkResliceCursorLineRepresentation旋转/平移的具体数学 + 十字线 actor
数据模型vtkResliceCursor中心/三轴/厚度/三平面,唯一真相源(被多视图共享)
几何生成vtkResliceCursorPolyDataAlgorithm模型 → 线条几何
十字线渲染vtkResliceCursorActor3D Prop,画十字线和厚层线
拾取vtkResliceCursorPicker屏幕坐标 ↔ 切片平面交点

二、切片图像数据管线(View 内部)

SetInputData

SetResliceAxes · ResliceAxes 4x4 矩阵

GetOutputPort

GetOutputPort

vtkResliceCursor.Image
3D 体数据

vtkImageReslice / vtkImageSlabReslice
按平面方向重采样出 2D 切片
Slab 模式做厚层投影

vtkImageMapToWindowLevelColors (ColorMap)
窗宽窗位 + LookupTable

vtkTexture → TexturePlaneActor
UseImageActor=false

vtkImageActor
UseImageActor=true

ResliceAxes 这个 4×4 矩阵是模型→管线的桥梁:UpdateReslicePlane()vtkResliceCursor
的三轴和中心填充它,再 reslice->SetResliceAxes() 驱动重采样。


三、核心过程时序图

时序 1:初始化与首帧渲染

ReslicePipelineResliceCursorLineRepWidgetAppReslicePipelineResliceCursorLineRepWidgetAppSetResliceCursor(rc) 经 CursorAlgorithm1SetEnabled(1)2CreateDefaultRepresentation new LineRep3AddObserver(RenderEvent)4Render()5BuildRepresentation()6Reslice.SetInputData(rc.GetImage())7UpdateReslicePlane()8rc.Update → ComputeAxes / BuildPolyData9用三轴/中心填 ResliceAxes, Reslice.SetResliceAxes()10ColorMap 重新着色11Actor 显示 2D 切片 + 十字线12

时序 2:鼠标拖拽旋转/平移 → 重切片 → 多视图同步(核心闭环)

以 Ctrl+左键旋转为例:

其它视图 B/CReslice→ColorMapResliceCursor 共享PickerLineRepWidget static-cbInteractor其它视图 B/CReslice→ColorMapResliceCursor 共享PickerLineRepWidget static-cbInteractor按下 Ctrl+左键拖拽 MouseMove 反复释放左键UserCtrl+LeftButtonPress1RotateAction(self)2ComputeInteractionState(X,Y)3Pick(X,Y,0,ren)4GetCenter / GetCenterlineAxisPolyData5PickedCenter/Axis1/Axis26InteractionState=OnAxis1/27SetManipulationMode(RotateBothAxes)8StartWidgetInteraction(pos) 记录 StartCenter9WidgetState=Active, GrabFocus, Highlight(1)10InvokeEvent(StartInteraction), Render()11MouseMove12MoveAction(self)13WidgetInteraction(eventPos)14RotateAxis(e,axis) 计算角度+旋转轴15SetXAxis/SetYAxis(...)16Modified MTime++17InvokeEvent(InteractionEvent)18Render→BuildRepresentation()19UpdateReslicePlane 新三轴→ResliceAxes20SetResliceAxes / ColorMap 重算21新 2D 切片22InvokeAnEvent()23InvokeEvent(ResliceAxesChangedEvent)24通知观察者25各自 Render 同步刷新26LeftButtonRelease27EndSelectAction(self)28EndWidgetInteraction, Highlight(0), ActivateText(0)29SetManipulationMode(None)30ReleaseFocus, InvokeEvent(EndInteraction), Render()31User
多视图同步的核心机制

关键在于:三个视图(轴/冠/矢)共享同一个 vtkResliceCursor 实例。

  1. 任一视图改动 → rc->SetXAxis()/SetCenter()rcMTime 更新
  2. 该 Widget 通过 rc->InvokeEvent(ResliceAxesChangedEvent) 主动广播
  3. 其它 Widget 在 rc 上注册了观察者,回调里调用各自 Render()
  4. 各视图 BuildRepresentation() 发现 rc->GetMTime() > BuildTime,重新 UpdateReslicePlane()
    拉取新模型 → 重切片

InvokeEvent ResliceAxesChangedEvent

观察者回调

观察者回调

View-Axial · Widget A

vtkResliceCursor
共享单例/模型

View-Coronal · Widget B

View-Sagittal · Widget C

A.Render()

B.Render()

C.Render()

时序 3:厚度调整(右键) / 窗宽窗位(空白区左键)

窗宽窗位 · 空白区左键

LeftButtonPress Outside → SelectAction

InteractionState==Outside 且 ManageWindowLevel

StartWindowLevel → SetManipulationMode WindowLevelling

MouseMove → WidgetInteraction → WindowLevel x,y
ColorMap 改窗宽窗位 · 不触发 UpdateReslicePlane

InvokeEvent WindowLevelEvent

厚度调整 · 右键

RightButtonPress → ResizeThicknessAction

ComputeInteractionState
命中且 ThickMode 开

SetManipulationMode ResizeThickness

MouseMove → WidgetInteraction
竖直位移算 sf → rc.SetThickness t乘sf

InvokeEvent ResliceThicknessChangedEvent

注意 BuildRepresentation() 里有判断:WindowLevelling 模式跳过 UpdateReslicePlane()——
窗位只改颜色映射,不需要重切片,这是性能优化。


四、设计模式总结

模式体现
MVC / 三层Widget(控制) — Representation(视图) — ResliceCursor(模型)
状态机WidgetState(Start/Active) + ManipulationMode(None/PanAndRotate/RotateBothAxes/TranslateSingleAxis/ResizeThickness/WindowLevelling)
命令/回调映射vtkWidgetCallbackMapper 把"事件+修饰键"映射到 static 回调函数
观察者InvokeEvent(ResliceAxesChangedEvent),多视图通过监听共享模型实现同步
模板方法基类 vtkResliceCursorRepresentation 定义交互接口,子类 LineRepresentation 实现
管线(Pipeline)Reslice → ColorMap → Texture/Actor 的 VTK 数据流
策略UseImageActor 切换两种渲染策略;ThickModevtkImageReslice / vtkImageSlabReslice 间切换

设计精髓:Widget 只管"翻译"鼠标事件,所有几何运算下沉到 Representation,而 vtkResliceCursor
作为被多视图共享的单一数据模型,配合 VTK 的事件观察者机制,实现了三正交视图的实时联动重切片。


五、事件→动作映射表(构造函数中注册)

触发条件WidgetEvent回调动作
左键按下(无修饰)SelectSelectAction命中→PanAndRotate;空白→窗宽窗位
Ctrl+左键RotateRotateActionRotateBothAxes
Alt+左键TranslateTranslateActionTranslateSingleAxis
右键按下ResizeResizeThicknessAction调整厚度(需 ThickMode)
左/右键释放EndSelect/EndResizeEndSelectAction结束交互,复位
鼠标移动MoveMoveAction悬停判定光标 / 拖拽时更新
按键 “o”ResetResetResliceCursorAction复位 cursor

六、类继承 UML 图

6.1 继承层次(泛化关系 ▷)

vtkProp

vtkObject

vtkProp3D

vtkWidgetRepresentation

vtkResliceCursorActor · 十字线 3D Prop

vtkAlgorithm

vtkPolyDataAlgorithm

vtkResliceCursorPolyDataAlgorithm · Cursor 模型→线条

vtkResliceCursor · 数据模型

vtkResliceCursorRepresentation · 抽象

vtkResliceCursorLineRepresentation

vtkResliceCursorThickLineRepresentation

vtkResliceCursorWidget · 控制器

vtkAbstractWidget

vtkResliceCursorPicker · 射线拾取

vtkPicker

箭头方向为「子类 → 父类」(泛化)。

6.2 关联/聚合关系(类图,简化)

WidgetRep 聚合

组合

组合

Picker

共享

共享

注入

模型来源

vtkResliceCursorWidget

-int WidgetState

-int ModifierActive

-vtkTypeBool ManageWindowLevel

+SetRepresentation(r)

#SelectAction()

#RotateAction()

#TranslateAction()

#MoveAction()

#EndSelectAction()

#ResizeThicknessAction()

#InvokeAnEvent()

«abstract»

vtkResliceCursorRepresentation

#int ManipulationMode

#int InteractionState

#vtkImageReslice Reslice

#vtkImageMapToWindowLevelColors ColorMap

#vtkMatrix4x4 ResliceAxes

#vtkResliceCursorPicker Picker

+ComputeInteractionState()

+WidgetInteraction()

+UpdateReslicePlane()

+GetResliceCursor() : vtkResliceCursor

vtkResliceCursorLineRepresentation

+vtkResliceCursorActor ResliceCursorActor

+ComputeInteractionState()

+WidgetInteraction()

-RotateAxis()

-TranslateAxis()

+GetResliceCursorActor()

vtkResliceCursorActor

#vtkResliceCursorPolyDataAlgorithm CursorAlgorithm

#vtkActor CursorCenterlineActor

#vtkActor CursorThickSlabActor

+RenderOpaqueGeometry()

+GetCenterlineActor(axis)

+SetUserMatrix()

vtkResliceCursor

-double Center

-double XAxis_YAxis_ZAxis

-double Thickness

-vtkTypeBool ThickMode

-vtkImageData Image

+Update()

+Reset()

+GetPlane(n)

+GetCenterlineAxisPolyData(axis)

vtkResliceCursorPolyDataAlgorithm

+GetAxis1() : int

+GetAxis2() : int

+GetReslicePlaneNormal() : int

+GetResliceCursor() : vtkResliceCursor

vtkResliceCursorPicker

#vtkResliceCursorPolyDataAlgorithm ResliceCursorAlgorithm

#int PickedAxis1

#int PickedAxis2

#int PickedCenter

#vtkMatrix4x4 TransformMatrix

+Pick(x,y,z,ren) : int

+Pick(displayPos,world,ren)

#IntersectPolyDataWithLine()

#IntersectPointWithLine()

关系图例:o--> 聚合(生命周期独立) · *--> 组合(内部 new/Delete 强拥有) · <|-- 泛化(继承) · ..> 共享/依赖

6.3 关系性质说明

关系类型说明
Widget → Representation聚合WidgetRep 由外部 SetRepresentation 传入,或 CreateDefaultRepresentation 默认创建
LineRep ◆ ResliceCursorActor组合子类内部 new/Delete,强拥有
ResliceCursorActor ◆ CursorAlgorithm组合actor 内部持有几何算法
Rep / Actor / Picker → ResliceCursor关联(共享)三者都引用同一个 vtkResliceCursor,是多视图联动的关键
Picker → CursorAlgorithm关联拾取前 SetResliceCursorAlgorithm() 注入

七、vtkResliceCursorPicker 拾取细节

vtkResliceCursorPicker 继承自 vtkPicker,是射线投射拾取器(ray-cast),专门判断鼠标点中了
十字线的哪一部分:轴 1 / 轴 2 / 中心。它由 ComputeInteractionState() 在每次按下或悬停时调用。

7.1 调用入口

// vtkResliceCursorLineRepresentation::ComputeInteractionState()
this->Picker->SetResliceCursorAlgorithm(this->ResliceCursorActor->GetCursorAlgorithm());
int picked = this->Picker->Pick(X, Y, 0, this->Renderer);

bool pickedAxis1  = this->Picker->GetPickedAxis1();
bool pickedAxis2  = this->Picker->GetPickedAxis2();
bool pickedCenter = this->Picker->GetPickedCenter();

if (picked) this->Picker->GetPickPosition(this->StartPickPosition);

// 优先级:Center > Axis1 > Axis2
if      (pickedCenter) InteractionState = OnCenter;
else if (pickedAxis1)  InteractionState = OnAxis1;
else if (pickedAxis2)  InteractionState = OnAxis2;
else                   InteractionState = Outside;

拾取优先级:中心 > 轴1 > 轴2。命中中心可平移整个 cursor,命中单轴则旋转/平移该轴。

7.2 Pick() 算法五步(vtkResliceCursorPicker::Pick)

① 构造拾取射线
   屏幕 (X,Y) ──DisplayToWorld──▶ 世界坐标 PickPosition
   以相机位置→该点连线为射线,裁剪到前/后裁剪面 → 线段 [p1World, p2World]
   (区分透视投影 / 平行投影两种端点算法)

② 计算世界坐标容差 tol
   取视口对角两点的世界距离 × this->Tolerance
   → 把屏幕像素容差换算成世界空间的"粗细半径"

③ 取出待测几何(来自共享的 ResliceCursor)
   axis1 = CursorAlgorithm->GetAxis1();   axis2 = GetAxis2();
   center = rc->GetCenter();
   line1  = rc->GetCenterlineAxisPolyData(axis1);   // 轴1 线条
   line2  = rc->GetCenterlineAxisPolyData(axis2);   // 轴2 线条

④ 三次相交测试
   PickedCenter = IntersectPointWithLine(p1,p2, center, tol);   // 点 vs 射线
   PickedAxis1  = IntersectPolyDataWithLine(p1,p2, line1, tol); // 线段 vs 射线
   PickedAxis2  = IntersectPolyDataWithLine(p1,p2, line2, tol);

⑤ 若命中,回算精确拾取点
   TransformPlane();                              // 应用 TransformMatrix
   Plane->IntersectWithLine(p1,p2, t, pickPosT);  // 与切片平面求交
   InverseTransformPoint(pickPosT, PickPosition); // 逆变换回真实世界坐标

返回 PickedAxis1 + PickedAxis2 + PickedCenter (>0 即命中)

7.3 两个相交测试核心

IntersectPointWithLine(点—射线,判中心命中):
把中心点投影到射线上,参数 t∈[0,1] 且三个分量偏差都 < tol → 命中。

t = dot(ray, X - p1) / dot(ray, ray);          // 投影参数
if (t in [0,1] && |X - (p1 + t*ray)| < tol)    // 逐分量在容差内
    return 1;

IntersectPolyDataWithLine(线段—射线,判轴命中):
遍历 polydata 每个 cell,用 cell->IntersectWithLine(p1,p2,tol,...) 求交,命中即返回。
若设了 TransformMatrix,会先把 cell 顶点变换后再测。

7.4 TransformMatrix 的作用

当切片平面被旋转/有用户矩阵(SetUserMatrix)时,十字线几何在世界空间是"斜放"的。
Picker 用 TransformMatrix 把:

  • 测试阶段:几何点变换到与射线一致的空间求交;
  • 回算阶段:用 InverseTransformPoint 把交点逆变换回真实世界坐标,
    保证 StartPickPosition 在后续 WidgetInteraction 的平移/旋转计算中正确。

7.5 重载 Pick(displayPos, world, ren) — 平移用

void Pick(double displayPos[2], double world[3], vtkRenderer* ren);

这是另一个重载:给一个屏幕坐标,直接返回它在当前切片平面上的世界交点。
WidgetInteraction 中平移(OnCenter / TranslateAxis)就靠它把鼠标位置映射到平面上:

// 平移中心
this->Picker->Pick(e, intersectionPos, this->Renderer);
newCenter[i] = StartCenterPosition[i] + intersectionPos[i] - StartPickPosition[i];
rc->SetCenter(newCenter);

7.6 拾取时序小结

PickedCenter

PickedAxis1

PickedAxis2

MouseMove / Press

ComputeInteractionState X,Y

Picker.SetResliceCursorAlgorithm(alg)

Picker.Pick X,Y,0,ren

① 屏幕→世界 构造射线 p1,p2
透视/平行分别处理

② 视口对角换算世界容差 tol

③ 取共享 rc: center / line axis1 / line axis2

④ 三次相交测试

IntersectPointWithLine → PickedCenter

IntersectPolyDataWithLine → PickedAxis1

IntersectPolyDataWithLine → PickedAxis2

命中?

⑤ TransformPlane + IntersectWithLine
InverseTransformPoint 回算 PickPosition

InteractionState = Outside

优先级判定

OnCenter

OnAxis1

OnAxis2

Widget: SetManipulationMode + SetCursor 光标形状


八、补充图(Mermaid 汇总)

第一/二/三/六/七节的图已全部内嵌为 Mermaid。此节作为汇总附录,提供含 VTK 基类的完整类图与 Widget 状态机

8.1 完整类继承 / 关联图(含 VTK 基类)

WidgetRep 聚合

组合

组合

Picker

共享引用

共享引用

注入

模型来源

vtkObject

vtkProp

vtkProp3D

vtkAbstractWidget

vtkWidgetRepresentation

vtkAlgorithm

vtkPolyDataAlgorithm

vtkPicker

vtkResliceCursorWidget

-int WidgetState

-int ModifierActive

-vtkTypeBool ManageWindowLevel

+SetRepresentation(r)

+CreateDefaultRepresentation()

+ResetResliceCursor()

#SelectAction()

#RotateAction()

#TranslateAction()

#MoveAction()

#EndSelectAction()

#ResizeThicknessAction()

#InvokeAnEvent()

«abstract»

vtkResliceCursorRepresentation

#int ManipulationMode

#int InteractionState

#vtkImageReslice Reslice

#vtkImageMapToWindowLevelColors ColorMap

#vtkMatrix4x4 ResliceAxes

#vtkResliceCursorPicker Picker

+ComputeInteractionState()

+WidgetInteraction()

+BuildRepresentation()

+UpdateReslicePlane()

+GetResliceCursor() : vtkResliceCursor

vtkResliceCursorLineRepresentation

+vtkResliceCursorActor ResliceCursorActor

+ComputeInteractionState()

+WidgetInteraction()

-RotateAxis()

-TranslateAxis()

+GetResliceCursorActor()

vtkResliceCursorThickLineRepresentation

vtkResliceCursor

-double Center

-double XAxis_YAxis_ZAxis

-double Thickness

-vtkTypeBool ThickMode

-vtkImageData Image

+Update()

+Reset()

+GetPlane(n) : vtkPlane

+GetCenterlineAxisPolyData(axis)

vtkResliceCursorPolyDataAlgorithm

+GetAxis1() : int

+GetAxis2() : int

+GetPlaneAxis1() : int

+GetPlaneAxis2() : int

+GetReslicePlaneNormal() : int

+GetResliceCursor() : vtkResliceCursor

vtkResliceCursorActor

#vtkResliceCursorPolyDataAlgorithm CursorAlgorithm

#vtkActor CursorCenterlineActor

#vtkActor CursorThickSlabActor

+RenderOpaqueGeometry()

+GetCenterlineActor(axis)

+SetUserMatrix(m)

vtkResliceCursorPicker

#vtkResliceCursorPolyDataAlgorithm ResliceCursorAlgorithm

#int PickedAxis1

#int PickedAxis2

#int PickedCenter

#vtkMatrix4x4 TransformMatrix

+Pick(x,y,z,ren) : int

+Pick(displayPos,world,ren)

#IntersectPolyDataWithLine()

#IntersectPointWithLine()

交互时序图见 §三、拾取流程图见 §7.6,均已内嵌为 Mermaid,此处不再重复。

8.2 状态机(Mermaid stateDiagram)

Press 命中/StartWindowLevel

Release(EndSelectAction)

Start

Active

左键命中轴/中心

Ctrl+左键

Alt+左键

右键(ThickMode)

左键空白区+ManageWindowLevel

None

PanAndRotate

RotateBothAxes

TranslateSingleAxis

ResizeThickness

WindowLevelling

注:本文档统一采用 Mermaid 渲染,已移除 PlantUML 块。在 VS Code 中安装扩展
Markdown Preview Mermaid Support(bierner.markdown-mermaid)后,用 Ctrl+Shift+V
打开 Markdown 预览即可本地渲染上述全部图表,无需任何服务器。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐