VTK 椭圆圈取ROI区域

目录

环境

1、实现效果图

 2、实现方式

2.1 框选实现

2.1.1 初始化

2.1.2 框选实现

2.2 提取椭圆 ROI 区域

2.2.1 初始化界面

2.2.2 ROI 提取


环境

QT 5.14.2

VTK 8.2.0 (vs2019编译,64位 release 版本)

构建方式:Qt5.14.2  MSVC2017 64bit Release

1、实现效果图

 2、实现方式

大致流程:

1、使用  vtkEllipseArcSource 类在原图像上画出椭圆进行框选。

2、使用  vtkImageEllipsoidSource 类制作椭圆的二值图像

3、使用  vtkImageMask 类 提取 ROI 椭圆区域

2.1 框选实现

2.1.1 初始化

自定义类 MyVTKOpenGLWidget 继承自 QVTKOpenGLWidget,初始化做了3件事:

1、创建 vtkImageViewer2  对象,用来显示图像,图像数据由外部传入。vtkImageViewer2 使用的 交互样式是自定义样式  MyInteractorStyle,主要是用来在椭圆绘制过程中屏蔽 vtk 自带的移动事件。

2、创建 vtkPointPicker 对象,用来在图像上拾取点坐标。

3、创建 vtkEllipseArcSource 对象,用来画 椭圆。

MyVTKOpenGLWidget::MyVTKOpenGLWidget(QWidget *parent) : QVTKOpenGLWidget(parent)
{

}

void MyVTKOpenGLWidget::init(vtkImageData *imageData)
{
    initImageView(imageData);
    addPicker();   // 添加 点 拾取对象
    addEllipse();  // 添加椭圆 actor
}

void MyVTKOpenGLWidget::initImageView(vtkImageData* imageData)
{
    m_imageView  = vtkSmartPointer< vtkImageViewer2 >::New();
    m_imageView->SetInputData(imageData);   // 加载数据

    // renderer 设置
    vtkRenderer* renderer = m_imageView->GetRenderer();
    renderer->ResetCamera();

    // 设置交互对象
    m_imageView->SetupInteractor(this->GetRenderWindow()->GetInteractor());

    // 设置渲染窗口
    m_imageView->SetRenderWindow(this->GetRenderWindow());

    // 设置交互样式
    m_style = vtkSmartPointer<MyInteractorStyle>::New();
    this->GetRenderWindow()->GetInteractor()->SetInteractorStyle(m_style);
}

void MyVTKOpenGLWidget::addPicker()
{
    // 设置 点 拾取对象
    m_picker = vtkSmartPointer< vtkPointPicker >::New();
    m_picker->PickFromListOn();

    vtkImageActor* imageActor = m_imageView->GetImageActor();
    m_picker->AddPickList(imageActor);
}

void MyVTKOpenGLWidget::addEllipse()
{
    m_ellipseSource = vtkSmartPointer<vtkEllipseArcSource>::New();
    vtkNew<vtkPolyDataMapper> mapper;
    mapper->SetInputData(m_ellipseSource->GetOutput());

    // 创建actor
    m_ellipseActor = vtkSmartPointer<vtkActor>::New();
    m_ellipseActor->SetMapper(mapper);
    m_ellipseActor->GetProperty()->SetColor( 1, 0.66, 0);
    m_ellipseActor->GetProperty()->SetLineWidth(2);

    m_imageView->GetRenderer()->AddActor( m_ellipseActor );
}

自定义样式 MyInteractorStyle

void MyInteractorStyle::setSelectAreaOn(bool v)
{
    m_selectAreaOn = v;
}

void MyInteractorStyle::OnMouseMove()
{
    if (m_selectAreaOn)
        return;

    vtkInteractorStyleImage::OnMouseMove();
}

2.1.2 框选实现

将拾取到的点坐标转换为图像的像素坐标

void MyVTKOpenGLWidget::calculateImageIndex(int image_coordinate[])
{
    vtkRenderer* renderer = m_imageView->GetRenderer();
    vtkImageData* image   = m_imageView->GetInput();
    vtkRenderWindowInteractor* interactor = m_imageView->GetRenderWindow()->GetInteractor();

    int* clickPos = interactor->GetEventPosition();
    m_picker->Pick(clickPos[0], clickPos[1], 0.0, renderer);

    // Get the world coordinates of the pick
    double *pos     = m_picker->GetPickPosition();
    double *spacing = image->GetSpacing();
    double *origin  = image->GetOrigin();

    image_coordinate[0] = pos[0] - origin[0]; //origin是图像的原点
    image_coordinate[1] = pos[1] - origin[1];
    image_coordinate[2] = pos[2] - origin[2];

    for (int i = 0; i < 3; i++)
    {
        int index = image_coordinate[i] / spacing[i];
        double deltX = image_coordinate[i] - index * spacing[i];
        if (deltX > spacing[i] / 2.0) index++;

        image_coordinate[i] = index;
    }
}

重载QT mousePressEvent、mouseMoveEvent、mouseReleaseEvent函数。

1、mousePressEvent: 鼠标左键按下时记录起始坐标,

2、mouseMoveEvent: 移动过程中计算当前的坐标,绘制椭圆

3、mouseReleaseEvent: 鼠标抬起表示本次绘制结束,向外发送结束绘制信号

void MyVTKOpenGLWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        m_bPressed = true;
        m_style->setSelectAreaOn(m_bPressed);
        calculateImageIndex(m_pressIndex);
    }
    QVTKOpenGLWidget::mousePressEvent(event);
}

void MyVTKOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (m_bPressed)    // 左键按下的情况下 画椭圆
    {
        calculateImageIndex(m_moveIndex);
        drawEllipse();
    }

    QVTKOpenGLWidget::mouseMoveEvent(event);
}

void MyVTKOpenGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        m_bPressed = false;
        m_style->setSelectAreaOn(m_bPressed);

        emit signalEndDrawllipse();
    }
    QVTKOpenGLWidget::mouseReleaseEvent(event);
}

 椭圆绘制:根据起始坐标和当前坐标,计算椭圆的中心坐标,X轴半径,Y轴半径,这里给了一个限制,X轴坐标相差不大的情况下,不进行绘制,否则 VTK 会报错。

void MyVTKOpenGLWidget::drawEllipse()
{
    if (qAbs(m_moveIndex[0]-m_pressIndex[0]) < 2)
        return;

    double xr = qAbs(m_moveIndex[0]-m_pressIndex[0])/2;   // 椭圆横向半径
    double yr = qAbs(m_moveIndex[1]-m_pressIndex[1])/2;   // 椭圆垂直半径

    double centerX = (m_moveIndex[0]+m_pressIndex[0])/2;  // 椭圆中心 X 轴坐标
    double centerY = (m_moveIndex[1]+m_pressIndex[1])/2;  // 椭圆中心 Y 轴坐标

    double *spacing = m_imageView->GetInput()->GetSpacing();
    m_ellipseSource->SetCenter(centerX*spacing[0], centerY*spacing[1], 1);
    m_ellipseSource->SetSegmentAngle(360);
    m_ellipseSource->SetRatio(yr/xr);
    m_ellipseSource->SetStartAngle(0);
    m_ellipseSource->SetNormal(0,  0, 1);
    m_ellipseSource->SetMajorRadiusVector(xr*spacing[0], 0, 0); // 只需设置 x 轴半径
    m_ellipseSource->Update();

    this->GetRenderWindow()->Modified();
    this->GetRenderWindow()->Render();
}

2.2 提取椭圆 ROI 区域

主窗口区域设置两个widget,

2.2.1 初始化界面


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 读取数据
    vtkSmartPointer< vtkDICOMImageReader > reader = vtkSmartPointer< vtkDICOMImageReader >::New();
    reader->SetDirectoryName("E:\\DicomDir\\Angio Sample-1409241311-000006-002");
    reader->Update();

    m_imageData = reader->GetOutput();
    ui->sourceWidget->init(m_imageData);
    initROIWidget(m_imageData);

    connect(ui->sourceWidget, SIGNAL(signalEndDrawllipse()), this, SLOT(slotDrawEllipseROI()));
}

Widget::~Widget()
{
    delete ui;
}

void Widget::initROIWidget(vtkImageData* imageData)
{
    m_imageView = vtkSmartPointer<vtkImageViewer2>::New();
    m_imageView->SetInputData(imageData);

    // 设置交互对象
    m_imageView->SetupInteractor(ui->roiWidget->GetRenderWindow()->GetInteractor());

    // 设置渲染窗口actor
    m_imageView->SetRenderWindow(ui->roiWidget->GetRenderWindow());

    // 设置交互样式
    vtkSmartPointer<vtkInteractorStyleImage> style = vtkSmartPointer<vtkInteractorStyleImage>::New();
    ui->roiWidget->GetRenderWindow()->GetInteractor()->SetInteractorStyle(style);
    m_imageView->Render();
}

2.2.2 ROI 提取

MyVTKOpenGLWidget 椭圆绘制结束会向外发送  signalEndDrawllipse 信号,主窗口接收该信号,在槽函数中提取椭圆区域

void Widget::slotDrawEllipseROI()
{
    int * pressIndex = ui->sourceWidget->getPressIndex();
    int * moveIndex  = ui->sourceWidget->getMoveIndex();

    double xr = qAbs(moveIndex[0]-pressIndex[0])/2;   // 椭圆横向半径
    double yr = qAbs(moveIndex[1]-pressIndex[1])/2;   // 椭圆垂直半径

    double centerX = (moveIndex[0]+pressIndex[0])/2;  // 椭圆中心 X 轴坐标
    double centerY = (moveIndex[1]+pressIndex[1])/2;  // 椭圆中心 Y 轴坐标

    // 制作 椭圆 二值图像
    vtkNew<vtkImageEllipsoidSource> source;
    source->SetWholeExtent(m_imageData->GetExtent());
    source->SetOutputScalarTypeToUnsignedChar();
    source->SetInValue(255);
    source->SetOutValue(0);
    source->SetCenter(centerX, centerY, 0);
    source->SetRadius(xr, yr, 1);
    source->Update();

    // 提取 ROI 区域
    vtkNew<vtkImageMask> maskFilter;
    maskFilter->SetInputData(0, m_imageData);
    maskFilter->SetInputData(1, source->GetOutput());
    maskFilter->SetMaskedOutputValue(0,0,0);
    maskFilter->Update();

    m_imageView->SetInputData(maskFilter->GetOutput());
    m_imageView->Render();
}

完整工程:VtkEllipseROI.zip-C++文档类资源-CSDN下载

VTK8.2.0编译好的库:VTK8.2.0编译好的库-其它文档类资源-CSDN下载