前言
最近在做一個醫學圖像處理的項目,項目的內容就是在拉直的血管上面找到鈣化的部分,並且計算出鈣化積分。
效果圖如下所示:
第一章 醫學背景知識介紹
1.1 冠脈動脈鈣化以及鈣化積分定義
冠狀動脈鈣化(CAC)的定性檢測根據病變大小超過某一面積或體積閾值、密度超過規定的CT值閾值即可區分冠狀動脈斑塊內的病變是否為鈣化。冠狀動脈鈣化積分(CACS)中傳統的積分法AS定義面積閾值為1 mm2、CT值閾值為130 Hu。Detrano等使用8.16mm3的體積作為鈣化灶大小的閾值。質量積分[10]定義面積閾值為連續的3個像素、CT值閾值為130 Hu。Nelson等[11]所使用的面積閾值為4個緊鄰的像素,CT值閾值為130 Hu。另有研究者認為[12]不應使用某一固定的CT值作為病灶的密度閾值,因為同一密度的物體在不同CT機上的CT值可能有差異,故推薦使用一個固定的含鈣濃度值(100 mg/cc)作為閾值。然而Glodny等[13]卻嘗試在CCTA圖像中將冠狀動脈上CT值大於600 Hu的像素分離出來作為鈣化灶來研究,這種方法雖有偏頗,會明顯低估鈣化斑塊負荷,但其結果卻和常規CACS有高度線性相關性,甚至認為該方法可用於替代常規CACS以避免CACS掃描導致的電離輻射,這一觀點尚待進一步的考證。
Agatston等[2]首次使用了CACS對冠狀動脈鈣化斑塊進行了定量分析,稱之為Agatston 積分(Agatston Score, AS),
AS= Σ (CT值 × 面積 × 權重系數)。
CT值越高,權重系數越大,具體如下:130~199 Hu為1,200~299 HU為2,300~399 Hu為3,等於或大於400 Hu為4。
1998年Callister等[14]報道了CAC的容積積分(Volume Score,VS)的定量分析。VS等於所有鈣化斑塊的容積之和。VS積分法所計算的是鈣化的體積,而與鈣化灶的密度變化沒有對應關系。
2005年Nelson等[11]使用了質量積分(Mass Score,MS)進行CAC的定量測量。MS的檢測相對復雜,該方法須使用羥磷灰石鈣校准體模,此體模由數個含鈣濃度不同的模塊組成,且濃度已知。體模與受檢者須同時進行掃描。通過測量各個模塊的CT值便可確定CT值與含鈣濃度的計算關系,即:
測出各個模塊的CT值,可計算出斜率和截距。然后測量冠狀動脈鈣化斑塊的CT值,根據上述關系計算出鈣化斑塊的含鈣濃度,即:
最后通過軟件計算出冠狀動脈全部鈣化斑塊的質量積分,即:
AS是較客觀的CAC量化分析方法,早己受到了影像界的認可,臨床應用廣泛,一直是傳統的鈣化斑塊定量分析參數,但此方法的重復性不高,使病人間的對照及隨訪觀察結果可信度下降。VS的重復性與AS相比有所改善,可用於高危人群隨訪及冠心病人治療后復查。VS主要缺點是由於受部分容積效應的影響,另一方面VS沒有考慮鈣化斑塊的CT值,而CT值的高低可能代表了斑塊的演變階段,因此可能存在一定的片面性,而且此方法的重復性仍有不足。由於每位患者的含鈣濃度均是在體模校准的基礎上獲得,不受設備、掃描協議或患者個體差異的影響,其結果具有可比性,因而MS有良好的可重復性和准確性。在掃描協議標准化后,多中心的體模研究[8]使用不同生產廠商的CT機、不同機型的CT機進行CACS測量,結果為AS、VS與MS的變異系數分別為7.9%、4.0%、2.8%, MS具有最高的重復性和准確性,這與絕大部分研究結果一致。MS是國際心臟CT標准化協會物理工作組(the Physics Task Group ofthe International Consortium on Standardization in Cardiac CT)推薦的鈣化定量分析方法[12]。
1.2窗寬、窗位、CT值
人體組織CT值的范圍為-1000到+1000共2000個分度,人眼不能分辨這樣微小灰度的差別,僅能分辨16個灰階。為了提高組織結構細節的顯示, 能分辨CT值差別小的兩種組織,操作人員可以根據診斷需要調節圖像的對比度和亮度,這種調節技術稱為窗技術-窗寬、窗位的選擇。
窗寬是指顯示圖像時所選用的CT值范圍,在此范圍內的組織結構按其密度高低從白到黑分為16個等級(灰階)。如窗寬為160Hu,則可分辨的CT值為160/16=10Hu,即兩種組織CT值的差別在10Hu以上者即可分辨出來。 由此可見窗寬的寬窄直接影響圖像的對比度;窄窗寬顯示的CT值范圍小,每級灰階代表的CT值幅度小,因而對比度強,可分辨密度較接近的組織或結構,如檢查腦組織宜選用窄的窗寬;反之,窗寬加寬則每級灰階代表的
CT值幅度增大,對比度差,適於分辨密度差別大的結構如肺、骨質。窗位是指窗寬上、下限CT值的平均數。因為不同組織的CT值不同,欲觀察其細微結構最好選擇該組織的CT值為中心進行掃描,這個中心即為窗位。窗位的高低影響圖像的亮度:窗位低圖像亮度高呈白色;窗位高圖像亮度低呈黑色,但在實際操作中尚須兼顧其它結
構選用適當的窗位。總之,如要獲得較清晰且能滿足診斷要求的CT圖像,必須選用合適的窗寬、窗位,否則
圖像不清楚,還往往難以達到診斷要求,降低了CT掃描的診斷效能。
正常人體組織的CT值:
類別
|
CT值(HU)
|
水
|
0±10
|
腦脊液
|
3-8
|
血漿
|
3-14
|
水腫
|
7-17
|
腦白質
|
25-32
|
腦灰質
|
30-40
|
血液
|
13-32
|
血塊
|
64-84
|
肝臟
|
50-70
|
脾臟
|
50-65
|
胰腺
|
45-55
|
腎臟
|
40-50
|
肌肉
|
40-80
|
膽囊
|
10-30
|
脂肪
|
-20~-80
|
鈣化
|
80-300
|
空氣-200HU以上
|
骨骼+400以上
|
窗寬,窗位,CT值之間的數量關系:通俗的說:窗位是窗寬的中位數數值,窗寬是范圍,比如窗寬250,窗位50,CT值范圍是-75~175HU,CT值的范圍的中位數必須是50,而范圍則是250。換算成公式的話如下:
/*****************************************************/ /* @function name:vtk2ipl @function:vtk格式圖片轉化到opencv格式 @parameters srcDataVtk,sliceOrder:輸入,srcDataVtk是vtkImageData格式體數據,sliceOrder是要抽取的體數據的slice編號 dstDataMat :輸出,dstDataMat 是opencv格式圖像 @result: @Notes: */ /*****************************************************/ void calScore::vtk2ipl(vtkImageData *srcDataVtk,Mat &dstDataMat,int sliceOrder) { vtkImageShiftScale* shiftScale = vtkImageShiftScale::New(); shiftScale->SetInput(srcDataVtk); shiftScale->SetShift(-(800 - 0.5*1680));//800level,1680windows 這里 shiftScale->SetScale(255.0/1680); shiftScale->SetOutputScalarTypeToUnsignedChar(); shiftScale->ClampOverflowOn(); shiftScale->Update(); if (c_mode_XYZ =="XZ")//xz平面 { cv::Mat image(dims[0], dims[2], CV_8UC1); for (int i=0; i<dims[0]; ++i) { for (int j=0; j<dims[2]; ++j) { image.at<unsigned char>(cv::Point(j,i)) = *static_cast<unsigned char*>(shiftScale->GetOutput()->GetScalarPointer(i,sliceOrder,j)); } } imwrite("D:\\cvimg.bmp",image); image.copyTo(dstDataMat); } if (c_mode_XYZ =="YZ")//yz平面 { cv::Mat image(dims[1], dims[2], CV_8UC1); for (int i=0; i<dims[1]; ++i) { for (int j=0; j<dims[2]; ++j) { image.at<unsigned char>(cv::Point(j,i)) = *static_cast<unsigned char*>(shiftScale->GetOutput()->GetScalarPointer(sliceOrder,i,j)); } } imwrite("D:\\cvimg.bmp",image); image.copyTo(dstDataMat); } if (c_mode_XYZ =="XY")//xy平面 { cv::Mat image(dims[0], dims[1], CV_8UC1); for (int i=0; i<dims[0]; ++i) { for (int j=0; j<dims[1]; ++j) { image.at<unsigned char>(cv::Point(j,i)) = *static_cast<unsigned char*>(shiftScale->GetOutput()->GetScalarPointer(i,j,sliceOrder)); } } imwrite("D:\\cvimg.bmp",image); image.copyTo(dstDataMat); } //free shiftScale->Delete(); }
3.2 OpenCV to VTK
1 /*****************************************************/ 2 /* 3 @function name:ipl2vtk 4 @function:opencv格式圖片轉化到vtk格式 5 @parameters 6 _src:輸入,-src 是OpenCV數據 7 _dest :輸出,_dest 是vtk格式圖像 8 @result: 9 @Notes: 10 */ 11 /*****************************************************/ 12 void calScore::ipl2vtk(IplImage* _src, vtkImageData* _dest) 13 { 14 vtkImageImport *importer = vtkImageImport::New(); 15 if ( _dest ) 16 { 17 importer->SetOutput( _dest ); 18 } 19 importer->SetDataSpacing( 1, 1, 1 ); 20 importer->SetDataOrigin( 0, 0, 0 ); 21 importer->SetWholeExtent( 0, _src->width-1, 0, _src->height-1, 0, _src->nChannels-1 ); 22 importer->SetDataExtentToWholeExtent(); 23 importer->SetDataScalarTypeToUnsignedChar(); 24 importer->SetNumberOfScalarComponents( _src->nChannels ); 25 importer->SetImportVoidPointer( _src->imageData ); 26 importer->Update(); 27 28 //free 29 importer->Delete(); 30 31 }
3.3 抽取slice
/*****************************************************/ /* @function name:extractSlice @function:抽取slice @parameters _srcvtk,sliceOrder:輸入,_srcvtk是vtkImageData格式體數據,sliceOrder是要抽取的體數據的slice編號 @result:返回抽取的slice數據,數據格式是vtkImageData @Notes: */ /*****************************************************/ vtkImageData *calScore::extractSlice(vtkImageData *_srcvtk,int sliceOrder) { _srcvtk = vtkImageData::New(); _srcvtk->SetDimensions(imageData->GetDimensions()); _srcvtk->SetScalarTypeToDouble(); _srcvtk->SetNumberOfScalarComponents(4); _srcvtk->AllocateScalars(); _srcvtk->DeepCopy(imageData); _srcvtk->Update(); vtkExtractVOI*extractVOI = vtkExtractVOI::New(); //extractVOI->SetInput(_srcvtk); extractVOI->SetInput(imageData); if (c_mode_XYZ =="XZ")//xz平面 { extractVOI->SetVOI(0,dims[0],sliceOrder,sliceOrder,0,dims[2]); } if (c_mode_XYZ =="YZ")//yz平面 { extractVOI->SetVOI(sliceOrder,sliceOrder,0,dims[1],0,dims[2]); } if (c_mode_XYZ =="XY")//xy平面 { extractVOI->SetVOI(0,dims[0],0,dims[1],sliceOrder,sliceOrder); } extractVOI->Update(); //vtkImageData *imgData2D = vtkImageData::New(); //imgData2D->DeepCopy(extractVOI->GetOutput()); //return imgData2D; return extractVOI->GetOutput(); }