Study-VTK:實現任意平面的切割
0 背景:
vtk關於三維影像(圖片)的提取有兩個類,vtkExtractVOI和vtkImageReslice。合理運用這兩個可以實現任意大小、方向的三維影像切割。
doxygen文檔:vtkExtractVOI、vtkImageReslice
1 案例:
用這兩個類實現的demo,先用vtkImageReslice過影像中心,和process座標系切割影像。讓後計算四個點到中心距離換算爲新影像上對應的座標,再用vtkExtractVOI切割。實現任意方向、大小的模型切割。這種方式一次只能且兩個軸,所以再重複一次(第二次x和y正好反向)。計算時候不能忽略間隙,否則新切割出來的會變形。
- process x軸:長軸(鼠標選的前兩個點)方向;
- process y軸:original 的Z軸方向;
- process z軸:鼠標選的後兩個點方向。
四個點選擇使用的是vtkBiDimensionalWidget
2切割類介紹
3.0 vtkExtractVOI
vtkExtractVOI是一個篩選器,用於選擇一部分輸入結構化點數據集或對輸入數據集進行子採樣。(感興趣的選定部分稱爲感興趣的體積或VOI。)此過濾器的輸出是結構化的點數據集。該過濾器處理任何拓撲尺寸(即點,線,圖像或體積)的輸入數據,並可以生成任何拓撲尺寸的輸出數據。
要使用此過濾器,請設置VOI ivar,它們是ijk最小/最大索引,用於指定數據中的矩形區域。(請注意,這些是0偏移。)您還可以指定採樣率對數據進行二次採樣。
該過濾器的典型應用是從體積中提取切片以進行圖像處理,對大體積進行二次採樣以減小數據大小,或使用感興趣的數據提取體積區域。
用法:
virtual void SetVOI (int[6])
// 提取roi在原來影像座標系上的對角座標
virtual void SetSampleRate (int, int, int)
// xyz三個軸上的採樣頻率
3.1 vtkImageReslice
沿一組新軸重新切割體積。
vtkImageReslice是圖像幾何過濾器的瑞士軍刀:它可以以相當高的效率以任意組合來置換,旋轉,翻轉,縮放,重採樣,變形和填充圖像數據。
用法:
SetOutputDimensionality (int);
// 設置輸出 結果是幾維的 1/2/3
SetResliceAxes (vtkMatrix4x4 *);
// 設置變換矩陣
SetInterpolationModeToLinear();
// 設置採樣方法
變換矩陣有很多接口輸入
重採樣方法:線性、三次線性、最鄰近
3 案例實現
1.1 切割的實現:
計算新座標系用vtkImageReslice切割
QList<QList<double>>points = original_bidimensional_->GetDisplayPosition();
QList<double> center = vti_original_widget_->GetCenter();
QList<double> origin = vti_original_widget_->GetOrigin();
QList<double> spacing = vti_original_widget_->GetSpacing();
QList<qint32> extent = vti_original_widget_->GetExtent();
double k = (points.at(0).at(1) - points.at(1).at(1)) /
(points.at(0).at(0) - points.at(1).at(0));
double jiaodu = atan(k);
double axialElements[16] = {
cos(jiaodu), 0, cos(jiaodu + 1.57), 0,
sin(jiaodu), 0, sin(jiaodu + 1.57), 0,
0, 1, 0, 0,
0, 0, 0, 1
};
vtkNew<vtkMatrix4x4> resliceAxes ;
resliceAxes->DeepCopy(axialElements);
resliceAxes->SetElement(0, 3, center.at(0));
resliceAxes->SetElement(1, 3, center.at(1));
resliceAxes->SetElement(2, 3, center.at(2));
vtkNew<vtkImageReslice> reslice;
reslice->SetInputData(vti_original_widget_->GetVtkImageData());
reslice->SetOutputDimensionality(3);
reslice->SetResliceAxes(resliceAxes);
reslice->SetInterpolationModeToLinear();
reslice->Update();
計算距離,用vtkExtractVOI提取。
int dims[3];
reslice->GetOutput()->GetDimensions(dims);
vtkNew<vtkExtractVOI> extract_voi;
extract_voi->SetInputData(reslice->GetOutput());
qint32 new_z1, new_z2, new_y1, new_y2;
{
double b = center.at(1) - center.at(0) * k;
double line_a, line_b, line_c;
line_a = k;
line_b = -1;
line_c = b;
double length1, length2;
length1 = abs(line_a * points.at(2).at(0) +
line_b * points.at(2).at(1) +
line_c)
/ sqrt(line_a * line_a + line_b * line_b);
length2 = abs(line_a * points.at(3).at(0) +
line_b * points.at(3).at(1) +
line_c)
/ sqrt(line_a * line_a + line_b * line_b);
new_z1 =
static_cast<qint32>(0.5 * (extent.at(1) - extent.at(0)) - length1 / spacing.at(0));
new_z2 =
static_cast<qint32>(0.5 * (extent.at(1) - extent.at(0)) + length2 / spacing.at(0));
}
{
double b = center.at(1) - center.at(0) * (-1 / k);
double line_a, line_b, line_c;
line_a = -1 / k;
line_b = -1;
line_c = b;
double length1, length2;
length1 = abs(line_a * points.at(1).at(0) +
line_b * points.at(1).at(1) +
line_c)
/ sqrt(line_a * line_a + line_b * line_b);
length2 = abs(line_a * points.at(0).at(0) +
line_b * points.at(0).at(1) +
line_c)
/ sqrt(line_a * line_a + line_b * line_b);
new_y1 =
static_cast<qint32>(0.5 * (extent.at(3) - extent.at(2)) -
(length1 / spacing.at(0)));
new_y2 =
static_cast<qint32>(0.5 * (extent.at(3) - extent.at(2)) +
(length2 / spacing.at(0)));
}
extract_voi->SetVOI(
new_y1 > new_y2 ? new_y2 : new_y1,
new_y1 > new_y2 ? new_y1 : new_y2,
0, dims[1],
new_z1 > new_z2 ? new_z2 : new_z1,
new_z1 > new_z2 ? new_z1 : new_z2
);
extract_voi->Update();
vti_process_widget_->SetVtkImageData(extract_voi->GetOutput());
vti_process_widget_->BuildView();
1.2 計算焦點點:
vtkBiDimensionalRepresentation2D好像只有四個點世界座標和局部座標以及兩條直線長度,沒有中心焦點,需要自己求一下。
QList<QList<double> > ImageBiDimensional::GetDisplayPosition() const {
double p1[3];
representation_->GetPoint1WorldPosition(p1);
double p2[3];
representation_->GetPoint2WorldPosition(p2);
double p3[3];
representation_->GetPoint3WorldPosition(p3);
double p4[3];
representation_->GetPoint4WorldPosition(p4);
QList<QList<double>>points;
QList<double> point1, point2, point3, point4, point5;
point1 << p1[0] << p1[1] << p1[2];
point2 << p2[0] << p2[1] << p2[2];
point3 << p3[0] << p3[1] << p3[2];
point4 << p4[0] << p4[1] << p4[2];
points << point1 << point2 << point3 << point4;
double p5[2];
double a1 = p2[1] - p1[1];
double b1 = p1[0] - p2[0];
double c1 = p1[0] * p2[1] - p2[0] * p1[1];
double a2 = p4[1] - p3[1];
double b2 = p3[0] - p4[0];
double c2 = p3[0] * p4[1] - p4[0] * p3[1];
double det = a1 * b2 - a2 * b1;
p5[0] = (c1 * b2 - c2 * b1) / det;
p5[1] = (a1 * c2 - a2 * c1) / det;
point5 << p5[0] << p5[1];
points << point5;
return points;
}
1.3 一般輸入影像都是dcm,需要保存成vti:
感覺 vtkDICOMXXXXX賊難用,所以dcm、nii影像都用itk讀寫,讓後轉換成vti。mhd可以直接用vtk讀寫。
IntensityWindowingImageFilterType::Pointer intensityFilter =
IntensityWindowingImageFilterType::New();
ReaderType::Pointer reader = ReaderType::New();
ImageIOType::Pointer dicomIO = ImageIOType::New();
reader->SetImageIO(dicomIO);
NamesGeneratorType::Pointer nameGenerator = NamesGeneratorType::New();
nameGenerator->SetUseSeriesDetails(true);
nameGenerator->SetDirectory("/home/yx/Pictures/影像/Deeplv_測試影像/75%");
using SeriesIdContainer = std::vector< std::string >;
const SeriesIdContainer &seriesUID = nameGenerator->GetSeriesUIDs();
auto seriesItr = seriesUID.begin();
auto seriesEnd = seriesUID.end();
using FileNamesContainer = std::vector< std::string >;
FileNamesContainer fileNames ;
std::string seriesIdentifier;
while (seriesItr != seriesEnd) {
seriesIdentifier = seriesItr->c_str();
fileNames = nameGenerator->GetFileNames(seriesIdentifier);
++seriesItr;
}
reader->SetFileNames(fileNames);
try {
reader->Update();
} catch (itk::ExceptionObject &ex) {
Q_UNUSED(ex)
qWarning() << "read error";
}
intensityFilter->SetInput(reader->GetOutput());
intensityFilter->SetWindowMinimum(-200);
intensityFilter->SetWindowMaximum(400);
intensityFilter->SetOutputMinimum(0);
intensityFilter->SetOutputMaximum(1);
intensityFilter->Update();
typedef itk::ImageToVTKImageFilter< ImageType> itkTovtkFilterType;
itkTovtkFilterType::Pointer itkTovtkImageFilter = itkTovtkFilterType::New();
itkTovtkImageFilter->SetInput(intensityFilter->GetOutput());
itkTovtkImageFilter->Update();
vtkSmartPointer<vtkImageData> double_image_;
if (double_image_ == nullptr) {
double_image_ = vtkSmartPointer<vtkImageData>::New();
}
double_image_->DeepCopy(itkTovtkImageFilter->GetOutput());
qint32 extent[6];
double spacing[3];
double origin[3];
double_image_->GetExtent(extent);
double_image_->GetSpacing(spacing);
double_image_->GetOrigin(origin);
qDebug() << extent[0] << extent[1] << extent[2] << extent[3] << extent[4] << extent[5];
qDebug() << spacing[0] << spacing[1] << spacing[2];
qDebug() << origin[0] << origin[1] << origin[2];
vtkNew<vtkXMLImageDataWriter> writer;
writer->SetInputData(double_image_);
writer->SetFileName("/home/yx/Desktop/original.vti");
writer->Write();
vtk學習教程
Study-VTK
本案例代碼:
https://gitee.com/yaoxin001/WorkDemo
個人博客首頁
http://118.25.63.144/