上回书说到,我们已经解析到了pixel数据了。但是这个TAG标签我们解析成了(ffe0,0010),其实应该是数据范围问题,读取使用的是char类型,它的范围是-127到127,如果是0到255就不会出现这个问题了。读到的低8位e0是没有问题的,它的二进制位11100000,16进制就是e0。高8位是11111111,是两个f。如果想变成7fe0,只需要&0x7fff就可以了。
长度没有解析出来,可以根据行和列还有数据的位数进行计算就可以。这里面使用的是512X512X2,因为像素是16位,两个字节。
为了显示图像,我们使用了OpenCV库,进行快速显示,见代码如下:
void display(unsigned short *pixel, int rows, int cols){
//
cv::Mat dstMat(rows,cols,CV_16SC1,pixel);
// 根据传入的行和列,创建16位单通道图像。
cv::Mat image;
cv::normalize(dstMat,image,0,255,cv::NORM_MINMAX,CV_8UC1);
// 进行归一化,创建的图像是16位,我们显示器只可以显示8位,256个色阶
// 因此,医学影像中有窗宽窗位的概念,窗位是在0-65535之间选一个像素
// 位置,然后在此位置上下范围进行限制,上限-下限<=256。
cv::imshow("DICOM Image",image);
cv::waitKey(0);
}
然后我们就获取图像吧,代码如下:
void getPixel(std::fstream& file,unsigned short* pixel){
// 512X512X2 bytes
// 使用传递指针的方法将数据直接写到pixel中。
// short是两字节16位(-32767~32767)
// unsigned short 影像中常用的数据类型,16位(0~65535)
int size=512*512; // 像素大小(16位)
int buffersize=size*2; //读取时使用的数组的大小(8位)
char byte[buffersize]; //读取时使用的数组
file.read(byte,buffersize);
for(int i=0;i<size;i++){
*pixel=byte[2*i]|byte[2*i+1]<<8;
// 使用小端字节序 little endian
pixel++;
}
}
然后进行测试,看看是不是直接就是数据文件。结果没错,就是Tag后面是VR,然后就是数据了。
本文中使用的DICOM数据在这里:https://download.csdn.net/download/kangdehua/89850946
这个DICOM文件呢,是BrainLab计划系统里面导出的图像,相对于CT机里面dicom出来的数据简化了很多信息,因此本文使用了此数据。
总结:
这个手搓项目基本完成了,除了最后一个Tag [7fe0,0010]这个有问题之外,其他的基本正确吧,有这方面的专业人员可以在评论区或者是私信中指导一下。
里面的重点是数据类型,小端字节序,指针传递。
数据类型
char: 通常占用一个字节,用于存储字符。
short: 短整型,占用两个字节,范围-32767~32767
unsigned short: 短整型,占用两个字节,范围0~65535
int: 整型,占用四个字节。
图像使用两个字节,需要使用unsigned short数组进行存储数据,否则会出现错误。char数组是C++中iostream中读取字符的最小单元,每次读取一个字节的整数单位。
一般商用软件都会自己写自己公司的DICOM解析方法的,使用DCMTK来验证自己的方法。但是也可以购买第三方的解析库,比如DCMTK,只要付钱就可以了。