vtk二维重建切片一


  • 功能描述(straightened CPR):
    如图1所示,输入的是原始冠脉CTA影像,图2提供的是原始冠脉影像与提供的中心线的空间位置关系,现在需要对原始冠脉CTA影像进行拉直曲面重构,并实现重构后的实时的切面显示交互操作。
    图1
    图2

  • 解决方法:
    1)首先对输入的中心线进行重采样,通过调用vtk的vtkSplineFilter类进行重采样操作,
    然后用vmtk的vtkvmtkCenterlineAttributesFilter和vtkvmtkCenterlineGeometry类对中心线添加后续重构需要的属性“ParallelTransportNormals”和“FrenetTangent”;

    2)根据中心线上的点生成各个点在给定的冠脉影像上的切面,可以通过vtk中的vtkImageReslice类,设定好切面的大小等参数,沿着重采样后的新的中心线在每个点上生成一张切面图;

    3)将各个点的切面正确地堆叠在一起生成一个完整的重构模型,可以通过自己定义一个vtkImageData对象,大小可以从前面的切面一些参数取得,然后在每次生成一个切面的时候就遍历这个切面的所有像素值,并把值赋给我们定义的新的vtkImageData对象(注:这里也尝试过布尔运算等一些其他方法,均无法实现最后的效果);

    4)添加一种交互操作能够实时地显示整条血管各个角度的切面,这里我们用vtk首先生成了一个平面,设定好平面的朝向和位置,然后通过修改鼠标滚轮事件的调用函数来实现平面的旋转,因为我们最终重构好的模型是沿着Z轴堆叠的,所以这里我们的平面也只要沿着Z轴旋转就可以了。接下去的步骤就只要把平面旋转后相对于一开始变化的角度传出来,就可以计算出平面旋转后的法向量,这样就可以更新平面的朝向,当然与模型相交的部分也同时实现了更新。


  • 具体实现代码:

void Straight() {
    vtkSmartPointer<vtkPolyData> line;
    VtkUtil::ReadVTP(line, PATH + "/L9.vtp");

    int resolution = 600;

    double step_length = line->GetLength() / resolution;

    vtkSmartPointer<vtkSplineFilter> spline =
        vtkSmartPointer<vtkSplineFilter>::New();
    spline->SetInputData(line);
    spline->SetSubdivideToLength();
    spline->SetLength(step_length);
    spline->Update();

    line = spline->GetOutput();

    vtkSmartPointer<vtkvmtkCenterlineAttributesFilter> attributesFilter =
        vtkSmartPointer<vtkvmtkCenterlineAttributesFilter>::New();
    attributesFilter->SetInputData(line);
    attributesFilter->SetAbscissasArrayName("Abscissas");
    attributesFilter->SetParallelTransportNormalsArrayName("ParallelTransportNormals");
    attributesFilter->Update();

    vtkSmartPointer<vtkvmtkCenterlineGeometry> geometry =
        vtkSmartPointer<vtkvmtkCenterlineGeometry>::New();
    geometry->SetInputData(attributesFilter->GetOutput());
    geometry->SetLengthArrayName("Length");
    geometry->SetCurvatureArrayName("Curvature");
    geometry->SetTorsionArrayName("Torsion");
    geometry->SetTortuosityArrayName("Tortuosity");
    geometry->SetFrenetTangentArrayName("FrenetTangent");
    geometry->SetFrenetNormalArrayName("FrenetNormal");
    geometry->SetFrenetBinormalArrayName("FrenetBinormal");
    geometry->SetLineSmoothing(0);
    geometry->SetOutputSmoothedLines(0);
    geometry->SetNumberOfSmoothingIterations(100);
    geometry->SetSmoothingFactor(0.1);
    geometry->Update();

    vtkSmartPointer<vtkImageData> Image = vtkSmartPointer<vtkImageData>::New();
    Image = image_data_;


    vtkSmartPointer<vtkPolyData> centerline;
    centerline = geometry->GetOutput();

    vtkSmartPointer<vtkImageData> out_image;
    double a[3] = {0.1, 0.1, 0.1};
    int size_one = 50;
    _stepAngle = 45.0 / (size_one / 2);
    int b[2] = {size_one, size_one};
    this->CurvedMPRImages(Image, centerline, 0.0, a, b, step_length, out_image);

    vtkSmartPointer<vtkXMLImageDataReader> imageSource =
        vtkSmartPointer<vtkXMLImageDataReader>::New();
    QString cur_path = PATH + "/final.vti";
    imageSource->SetFileName(cur_path.toLocal8Bit().data());
    imageSource->Update();

    Image = imageSource->GetOutput();
    this->_image = Image;

    double bounds[6];
    Image->GetBounds(bounds);


    _planeWidth = bounds[5];
    _planeHeight = bounds[3];

    vtkSmartPointer<vtkDataSetMapper> mapper =
        vtkSmartPointer<vtkDataSetMapper>::New();
    mapper->SetInputData(Image);
    vtkSmartPointer<vtkActor> actor =
        vtkSmartPointer<vtkActor>::New();
    actor->SetMapper(mapper);
    this->_finalActor = actor;

    image_seeder_->WidgetsOff();

    this->vmtk_renderer_->GetRenderer()->AddActor(actor);
    this->vmtk_renderer_->Render();
    this->vmtk_renderer_->ResetCamera();
}
void CurvedMPRImages(vtkSmartPointer<vtkImageData> &image,
                                 vtkSmartPointer<vtkPolyData> &centerline,
                                 const double reslicingBackgroundLevel,
                                 const double outputSpacing[3],
                                 int outputSize[2],
                                 double stepLength,
                                 vtkSmartPointer<vtkImageData> &outImage) {
    vtkDataArray *frenetTangentArray = centerline->GetPointData()->GetArray("FrenetTangent");
    vtkDataArray *parallelTransportNormalsArray = centerline->GetPointData()
            ->GetArray("ParallelTransportNormals");


    vtkPoints *linePoints = centerline->GetPoints();
    int numberOfLinePoints = linePoints->GetNumberOfPoints();

    int OutputExtent[6];
    OutputExtent[0] = 0;
    OutputExtent[1] = outputSize[0] - 1;
    OutputExtent[2] = 0;
    OutputExtent[3] = outputSize[1] - 1;
    OutputExtent[4] = 0;
    OutputExtent[5] = numberOfLinePoints - 1;

    double OutputOrigin[3];
    double OutputSpacing[3];
    double firstPoint[3], secondPoint[3], zspace;
    linePoints->GetPoint(0, firstPoint);
    linePoints->GetPoint(1, secondPoint);

    zspace = sqrt(vtkMath::Distance2BetweenPoints(firstPoint, secondPoint));

    OutputSpacing[0] = outputSpacing[0];
    OutputSpacing[1] = outputSpacing[1];
    OutputSpacing[2] = zspace;

    OutputOrigin[0] = -1 * ((OutputExtent[1] - OutputExtent[0]) / 2) * OutputSpacing[0];
    OutputOrigin[1] = -1 * ((OutputExtent[3] - OutputExtent[2]) / 2) * OutputSpacing[1];
    OutputOrigin[2] = 0;

    auto reslice = vtkSmartPointer<vtkImageReslice>::New();
    reslice->SetOutputDimensionality(2);
    reslice->SetInputData(image);
    reslice->SetInterpolationModeToCubic();
    // turn off transformation of the input spacin, origin and extent, so we can define what we want
    reslice->TransformInputSamplingOff();
    // set the value of the voxels that are out of the input data
    reslice->SetBackgroundLevel(reslicingBackgroundLevel);
    // set the outputspacing
    //reslice->SetOutputSpacing(this->OutputSpacing[0],this->OutputSpacing[1],this->OutputSpacing[2]);
    reslice->SetOutputSpacing(outputSpacing);

// going to MPR
    vtkSmartPointer<vtkImageData> final_image
        = vtkSmartPointer<vtkImageData>::New();
    final_image->SetDimensions(outputSize[0], outputSize[1], numberOfLinePoints);
    final_image->AllocateScalars(VTK_DOUBLE, 1);
    final_image->SetSpacing(outputSpacing[0], outputSpacing[1], stepLength);
    int *dims = final_image->GetDimensions();

//    std::cout << "Dims: " << " x: " << dims[0] << " y: " << dims[1] << " z: " << dims[2] << std::endl;

//    std::cout << "Number of points: " << final_image->GetNumberOfPoints() << std::endl;
//    std::cout << "Number of cells: " << final_image->GetNumberOfCells() << std::endl;

    for (int slice = 0; slice < numberOfLinePoints; slice++) {
        // for each slice (or point on the line)
        double centerSlice[3];
        linePoints->GetPoint(slice, centerSlice);

        // To calculate the outputorigin & the necessarry transform
        // the vectors are retreived from the array's
        // t is the vector in the direction of the Centerline, so along the z-axis in the MPR volume
        double t[3];
        frenetTangentArray->GetTuple(slice, t);

        // p is a normal of the Centerline, directed to the 'North' direction of the inputvolume,in the MPR
        // volume this will be along the x-axis
        double p[3];
        parallelTransportNormalsArray->GetTuple(slice, p);

        double tp[3];
        // tp is the crossproduct of  t and p, and will be directed to the 'West' direction of the inputvolume,
        // in the MPR volume this will be along the y-axis
        tp[0] = (t[1] * p[2] - t[2] * p[1]);
        tp[1] = (t[2] * p[0] - t[0] * p[2]);
        tp[2] = (t[0] * p[1] - t[1] * p[0]);

        // set the axis of the first slice according to the vectors of the first point in the line
        reslice->SetResliceAxesDirectionCosines(p[0], p[1], p[2], tp[0], tp[1], tp[2], t[0], t[1], t[2]);
        // the firstPoint on the Centerline coresponds to the origin of the output axes unit vectors
        reslice->SetResliceAxesOrigin(centerSlice[0], centerSlice[1], centerSlice[2]);
        // the outputextent will be one slice
        reslice->SetOutputExtent(OutputExtent[0], OutputExtent[1], OutputExtent[2], OutputExtent[3], 0, 0);
        // the outputorigin in the input coordinate system is set to zero

        reslice->SetOutputOrigin(OutputOrigin);
        reslice->Update();

        for (int y = 0; y < dims[1]; y++) {
            for (int x = 0; x < dims[0]; x++) {
                double *pixel = static_cast<double *>(final_image->GetScalarPointer(x, y, slice));
                double value = reslice->GetOutput()->GetScalarComponentAsDouble(x, y, 0, 0);
                //std::cout << "value: " << value << std::endl;
                pixel[0] = value;
            }
        }

    }

    QString path = PATH + "/final.vti";
    vtkSmartPointer<vtkXMLImageDataWriter> writer =
        vtkSmartPointer<vtkXMLImageDataWriter>::New();
    writer->SetInputData(final_image);
    writer->SetFileName(path.toStdString().c_str());
    writer->Write();

    outImage = final_image;
}

  • 补充:
    1)这里读取的可以是DICOM影像序列,也可以是一个完整的vti文件;
    2)此处的平面交互代码较为基础就不记录了,只要根据旋转的角度更新平面对应的法向量即可。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM