python vtk读取dicom序列+鼠标键盘交互

发布时间 2023-10-29 18:52:16作者: yaksa777

目标:vtk + pyqt实现四视图。

之前不了解vtk,也不了解鼠标键盘交互。网上搜索了资料,发现博客里大都是C++的例子。

困扰几天,今天终于做出来一部分,分享一下。

参考官方教程:

第一步:python vtk 读取 dicom 文件

#!/usr/bin/env python3

# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkIOImage import vtkDICOMImageReader
from vtkmodules.vtkInteractionImage import vtkImageViewer2
from vtkmodules.vtkRenderingCore import vtkRenderWindowInteractor


def main():
    # 创建一个vtkNamedColors对象,用于为图像查看器窗口设置背景颜色
    colors = vtkNamedColors()
    
    # Read the DICOM file
    input_filename = "digest_article/brain_020.dcm"
    reader = vtkDICOMImageReader()  # 创建一个vtkDICOMImageReader对象用于读取DICOM文件
    reader.SetFileName(input_filename)  # 将输入文件名指定给vtkDICOMImageReader实例
    reader.Update()  # 读取DICOM文件

    # Visualize
    image_viewer = vtkImageViewer2()  # 创建一个vtkImageViewer2对象用于可视化
    image_viewer.SetInputConnection(reader.GetOutputPort())

    render_window_interactor = vtkRenderWindowInteractor()  # 创建一个vtkRenderWindowInteractor对象图像查看器进行交互
    image_viewer.SetupInteractor(render_window_interactor)  # 设置vtkImageViewer2和vtkRenderWindowInteractor之间的交互

    image_viewer.Render()
    image_viewer.GetRenderer().SetBackground(colors.GetColor3d("SlateGray"))
    image_viewer.GetRenderWindow().SetWindowName("ReadDICOM")
    image_viewer.GetRenderer().ResetCamera()
    image_viewer.Render()

    render_window_interactor.Start()


if __name__ == "__main__":
    main()
  • image_viewer.Render() 当对图像查看器进行了一些交互操作或更改了其属性(如窗口大小、背景颜色等),可以调用此方法立即执行并显示出来。

  • image_viewer.GetRenderer().ResetCamera() 用于重新设置图像查看器的渲染器(renderer)的相机(camera)。相机在渲染器中定义了图像的视角和位置。调用 ResetCamera() 方法将自动根据渲染器中呈现的的范围和位置调整相机,使整个图像在渲染窗口中完全可见。

image

第二步:自定义交互器

VTK: vtkInteractorStyleImage Class Reference,不太懂具体细节

#!/usr/bin/env python3

# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkIOImage import vtkDICOMImageReader
from vtkmodules.vtkInteractionImage import vtkImageViewer2
from vtkmodules.vtkRenderingCore import vtkRenderWindowInteractor
# 添加
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage
import vtk


# -------------------- 添加 start --------------------
class MyVtkInteractorStyleImage(vtkInteractorStyleImage):
    def __init__(self, parent=None):
        super().__init__()
        # 为vtk交互事件添加一个观察者(Observer),触发"左键点击"事件,调用 self.LeftButtonPressEvent
        self.AddObserver(vtk.vtkCommand.LeftButtonPressEvent, self.LeftButtonPressEvent)

    def LeftButtonPressEvent(self, obj, event):
        print("LeftButtonPressEvent")

# -------------------- 添加  end  --------------------

def main():
    colors = vtkNamedColors()
    
    # Read the DICOM file
    input_filename = "digest_article/brain_020.dcm"
    reader = vtkDICOMImageReader()
    reader.SetFileName(input_filename)
    reader.Update()

    # Visualize
    image_viewer = vtkImageViewer2()
    image_viewer.SetInputConnection(reader.GetOutputPort())

    render_window_interactor = vtkRenderWindowInteractor()
    image_viewer.SetupInteractor(render_window_interactor)

    # -------------------- 添加 start --------------------
    # 创建一个MyVtkInteractorStyleImage()对象来自定义vtk的交互方式
    my_interactor_style = MyVtkInteractorStyleImage()
    # 将render_window_interactor的交互方式(Interactor Style)设置为MyVtkInteractorStyleImage()
    render_window_interactor.SetInteractorStyle(my_interactor_style)
    render_window_interactor.Render()
    # -------------------- 添加  end  --------------------

    image_viewer.Render()
    image_viewer.GetRenderer().SetBackground(colors.GetColor3d("SlateGray"))
    image_viewer.GetRenderWindow().SetWindowName("ReadDICOM")
    image_viewer.GetRenderer().ResetCamera()
    image_viewer.Render()

    render_window_interactor.Start()


if __name__ == "__main__":
    main()

左键点击图片,cmd输出"LeftButtonPressEvent"。

第三步:自定义切片交互器

事件1:键盘上下键

事件2:鼠标滚轮前滚

事件3:鼠标滚轮后滚

交互中需要设置Slice值, 可以通过vtkImageViewer2类实现。但在MyVtkInteractorStyleImage类中需要使用main函数中的image_viewer。

方法是在MyVtkInteractorStyleImage类中定义一个 set_image_viewer 函数,并在main函数中添加一句 ”my_interactor_style.set_image_viewer(image_viewer)“。

class MyVtkInteractorStyleImage(vtkInteractorStyleImage):
    def __init__(self, parent=None):
        super().__init__()

        self.AddObserver(vtk.vtkCommand.KeyPressEvent, self.key_press_event)
        self.AddObserver(vtk.vtkCommand.MouseWheelForwardEvent, self.mouse_wheel_forward_event)
        self.AddObserver(vtk.vtkCommand.MouseWheelBackwardEvent, self.mouse_wheel_backward_event)

        self.image_viewer = None
        self.status_mapper = None
        self.slice = 0
        self.min_slice = 0
        self.max_slice = 0

    def set_image_viewer(self, image_viewer):
        self.image_viewer = image_viewer
        self.min_slice = image_viewer.GetSliceMin()
        self.max_slice = image_viewer.GetSliceMax()
        self.slice = self.min_slice
        print(f'Slicer: Min = {self.min_slice}, Max= {self.max_slice}')

    def move_slice_forward(self):
        if self.slice < self.max_slice:
            self.slice += 1
            print(f'MoveSliceForward::Slice = {self.slice}')
            self.image_viewer.SetSlice(self.slice)
            self.image_viewer.Render()

    def move_slice_backward(self):
        if self.slice > self.min_slice:
            self.slice -= 1
            print(f'MoveSliceBackward::Slice = {self.slice}')
            self.image_viewer.SetSlice(self.slice)
            self.image_viewer.Render()

    def key_press_event(self, obj, event):
        # print("key_press_event")
        key = self.GetInteractor().GetKeySym()
        if key == 'Up':
            self.move_slice_forward()
        elif key == 'Down':
            self.move_slice_backward()

    def mouse_wheel_forward_event(self, obj, event):
        # print("mouse_wheel_forward_event")
        self.move_slice_forward()

    def mouse_wheel_backward_event(self, obj, event):
        # print("mouse_wheel_backward_event")
        self.move_slice_backward()

完整代码:

#!/usr/bin/env python3

# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingContextOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkIOImage import vtkDICOMImageReader
from vtkmodules.vtkInteractionImage import vtkImageViewer2
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage
from vtkmodules.vtkRenderingCore import (
    vtkRenderWindowInteractor,
)
import vtk


# -------------------- 修改 start --------------------
class MyVtkInteractorStyleImage(vtkInteractorStyleImage):
    def __init__(self, parent=None):
        super().__init__()

        self.AddObserver(vtk.vtkCommand.KeyPressEvent, self.key_press_event)
        self.AddObserver(vtk.vtkCommand.MouseWheelForwardEvent, self.mouse_wheel_forward_event)
        self.AddObserver(vtk.vtkCommand.MouseWheelBackwardEvent, self.mouse_wheel_backward_event)

        self.image_viewer = None
        self.status_mapper = None
        self.slice = 0
        self.min_slice = 0
        self.max_slice = 0

    def set_image_viewer(self, image_viewer):
        self.image_viewer = image_viewer
        self.min_slice = image_viewer.GetSliceMin()
        self.max_slice = image_viewer.GetSliceMax()
        self.slice = self.min_slice
        print(f'Slicer: Min = {self.min_slice}, Max= {self.max_slice}')

    def move_slice_forward(self):
        if self.slice < self.max_slice:
            self.slice += 1
            print(f'MoveSliceForward::Slice = {self.slice}')
            self.image_viewer.SetSlice(self.slice)
            self.image_viewer.Render()

    def move_slice_backward(self):
        if self.slice > self.min_slice:
            self.slice -= 1
            print(f'MoveSliceBackward::Slice = {self.slice}')
            self.image_viewer.SetSlice(self.slice)
            self.image_viewer.Render()

    def key_press_event(self, obj, event):
        # print("key_press_event")
        key = self.GetInteractor().GetKeySym()
        if key == 'Up':
            self.move_slice_forward()
        elif key == 'Down':
            self.move_slice_backward()

    def mouse_wheel_forward_event(self, obj, event):
        # print("mouse_wheel_forward_event")
        self.move_slice_forward()

    def mouse_wheel_backward_event(self, obj, event):
        # print("mouse_wheel_backward_event")
        self.move_slice_backward()
# -------------------- 修改  end  --------------------


def main():
    colors = vtkNamedColors()

    # -------------------- 修改 start --------------------
    # 读取dicom序列
    input_folder = "digest_article"
    reader = vtkDICOMImageReader()
    reader.SetDirectoryName(input_folder)
    reader.Update()
    # -------------------- 修改  end  --------------------

    # Visualilze
    image_viewer = vtkImageViewer2()
    image_viewer.SetInputConnection(reader.GetOutputPort())

    render_window_interactor = vtkRenderWindowInteractor()
    image_viewer.SetupInteractor(render_window_interactor)

    my_interactor_style = MyVtkInteractorStyleImage()
    # -------------------- 添加 start --------------------
    my_interactor_style.set_image_viewer(image_viewer)
    # -------------------- 添加  end  --------------------
    render_window_interactor.SetInteractorStyle(my_interactor_style)
    render_window_interactor.Render()

    image_viewer.Render()
    image_viewer.GetRenderer().ResetCamera()
    image_viewer.GetRenderer().SetBackground(colors.GetColor3d('SlateGray'))
    image_viewer.GetRenderWindow().SetSize(800, 800)
    image_viewer.GetRenderWindow().SetWindowName('ReadDICOMSeries')
    image_viewer.Render()

    render_window_interactor.Start()


if __name__ == '__main__':
    main()