DCMTK 라이브러리를 VC++ 2012 프로젝트에 설정하기
참고: http://studiomosaic.blogspot.kr/2012/06/dicom-image-display-example-with-dcmtk.html?m=1
http://blog.naver.com/PostView.nhn?blogId=mkkim1129&logNo=80109163837
http://dicom.offis.de/dcmtk.php.en
DCMTK Documentation : http://support.dcmtk.org/docs/index.html
개요
VC++ 프로젝트에서 사용할 수 있는 DICOM 라이브러리 중에서 널리 알려진 2개의 라이브러리(DCMTK, GDCM) 중에서 DCMTK를 이용하여 화면에 DICOM영상을 출력하고자 한다. DCMTK 3.6.1 부터는 설정(Configure)할 때 하나의 DLL 파일로 빌드되도록 설정할 수 있으므로 여기서 적용하려고 한다.
DCMTK Download
http://dicom.offis.de/dcmtk.php.en
- 압축을 해제하면 dcmtk-3.6.1 폴더 안에 CMakeLists.txt 파일이 포함되어 있는 것을 확인할 수 있다.
- CMakeLists.txt 파일은 CMake 툴을 이용하여 VC++ 2012 프로젝트 파일을 생성할 때 참조되는 프로젝트 구성정보를 포함하고 있다
CMake를 이용하여 다운로드한 DCMTK 소스를 VC++ 2012 프로젝트로 설정하기
소스코드를 다운로드하여 Windows 라이브러리(*.lib) 로 컴파일하여 프로젝트에 사용해야 하므로 CMake 라는 프로그램을 이용하여 VC++ 2012 프로젝트 파일을 생성하고, VC++2012 에서 그 프로젝트 파일을 열고 컴파일하여 lib 파일을 생성해야 한다. 이렇게 생성된 Static Library는 다른 VC 프로젝트에서 사용될 수 있다.
1. CMake 다운로드 및 설치
- 위의 링크를 클릭하여 최신버전을 다운로드하고 압축을 해제한 후 실행파일을 더블클릭하면 쉽게 설치할 수 있다.
2. CMake 실행 및 DCMTK 프로젝트 정보 설정
- Where is the source code : DCMTK의 폴더 중에서 CMakeLists.txt 파일을 포함한 폴더를 확인하여 그 중 최상위에 있는 폴더를 선택한다
- Where to build the binaries: 프로젝트 파일을 생성할 디렉토리, 위의 디렉토리 아래에 projects 등의 이름으로 지정해주면 된다
3. Configure
- CMake화면 왼쪽 하단에 있는 'Configure' 버튼을 클릭한다.
- 컴파일러를 선택하는 콤보박스에서 Visual Studio 11 를 선택한다 (참고로, VC++2012 의 컴파일러 버전은 11 이다)
- 위의 절차를 마치면 CMake는 몇분에 걸쳐 긴 설정작업을 한다.
- 빨강색으로 선택된 리스트가 나타나면 화면 중앙 상부에 있는 [Advanced] 체크박스를 선택한다
- 화면중앙에 BUILD 항목 아래에 BUILD_SINGLE_SHARED_LIBRARY 체크하면 모든 모듈이 합쳐져서 하나의 DLL 파일과 하나의 LIB 파일을 생성할 수 있다 체크후에 다시 'Configure' 버튼을 눌러주면 적색바탕이 헤제된다
4. Generate
- 'Configure' 버튼 오른쪽에 있는 'Generate' 버튼을 누르면 CMake는 CMakeLists.txt와 소스코드를 이용하여 VC++2012 프로젝트 파일을 생성해낸다
5. 생성된 프로젝트 파일의 확인
- 2번 항목에서 지정한 폴더에 VC++2012 프로젝트 파일이 생성될 것이다. 예를 들어 projects 라는 폴더를 지정했다면 그 폴더 안을 확인한다
- 중요: 위에서 지정한 projects 폴더 안에는 VC++ 2012 용 헤더파일도 생성되는데, 다른 프로젝트에서 DCMTK 라이브러리를 사용할 때는 projects 폴더 안에 생성된 헤더파일을 소스코드에 포함해야 한다는 것을 주의해야 한다. 그렇지않고 DCMTK 원본 소스에 포함된 헤더파일을 사용하는 경우에는 컴파일할 때 오류가 발생하며 그 오류의 원인을 추측하기가 매우 어렵게 된다. 특히 config/include/osconfig.h 헤더파일을 소스코드에 include 할 때는 반드시 이 점을 지켜야 한다.
생성된 프로젝트 파일을 VC++ 2012에서 열고 컴파일하기
- CMake 가 생성해준 프로젝트 파일(DCMTK.sln)을 VC++ 2012에서 연다
- 프로젝트 위에서 마우스 우측을 눌러서 Configuration Properties > General > Character Set : Use Unicode Character Set 으로 설정
- VC++ 2012에 열린 프로젝트 중에서 ALL_BUILD 프로젝트를 빌드( Build > Build Solution)한다 (약 20분정도 지나면 빌드가 완성된다)
- 이어서 프로젝트 중에 INSTALL 위에서 마우스 우측을 눌러 Project Only Build INSTALL을 선택하면 위에서 빌드된 dcmtk.lib, dcmtk.dll 그리고 include 폴더에 헤더파일이 저장되어 최종적으로 C:/Program Files/DCMTK 안에 저장된다
- 위의 DCMTK 프로젝트가 Release 모드에서 /MT 로 설정하고 라이브러리를 빌드했다면 이 라이브러리를 사용하는 프로젝트가 Debug 모드일 경우에 Multi-Threaded debug(/MTd) 으로 설정하여 테스트하고 Release일 경우에는Multi-Threaded(/MT)으로 설정해야 오류가 발생하지 않았다
VC++ 2012 에서 빌드한 결과물 확인하기
- 위에서 지정한 projects /bin 폴더 안에 Debug 혹은 Release 폴더가 설정에 따라 생성되고 그 안에 dcmtk.dll 파일이 생성된다.
- 위에서 지정한 projects / lib 폴더 안에는 dcmtk.lib 파일이 생성된다.
- dcmtk 라이브러리를 사용하는 프로젝트에서는 dcmtk.dll 파일과 dcmtk.lib을 동시에 사용하도록 설정해야 한다.
위에서 생성한 lib 파일을 다른 프로젝트에서 사용하기
- 프로젝트를 생성하고 프로젝트 위에서 마우스 우측을 눌러 Properties항목을 선택하여 VC++ Directories 에서 다음 2가지 정보를 등록하면 된다
- Include Directories: DCMTK 가 포함하고 있는 헤더파일이 있는 폴더를 등록한다
- Lib Directories : 위에서 생성한 dcmtk.lib 파일을 포함한 폴더(lib) 를 등록한다
- DLL 파일을 사용할 경우에는 DLL 파일을 사용하고자 하는 프로젝트의 Debug/Release 안에 복사해주면 된다
소스코드에서 설정할 것들
필요한 DCMTK 헤더파일을 포함한다 (#include "dcmtk/dcmimgle/dcmimage.h" 등 )
필요한 라이브러리를 링크한다 (#pragma comment(lib, "dcmtk.lib") )
test.dcm 파일을 로드하고 DCM 파일에서 환자의 이름을 추출하여 콘솔창에 출력하는 예 (http://support.dcmtk.org/docs-dcmrt/mod_dcmdata.html)
dcmtk.dll 파일은 현재 프로젝트의 Debug 혹은 Release 폴더에 두면 된다
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dctk.h"
#include <iostream>
#pragma comment(lib, "dcmtk.lib")
using namespace std;
int main()
{
DcmFileFormat fileformat;
OFCondition status = fileformat.loadFile("test.dcm");
if (status.good())
{
OFString patientName;
if (fileformat.getDataset()->findAndGetOFString(DCM_PatientName, patientName).good())
{
cout << "Patient's Name: " << patientName << endl;
}
else
{
cerr << "Error: cannot access Patient's Name!" << endl;
}
}
else
{
cerr << "Error: cannot read DICOM file (" << status.text() << ")" << endl;
}
return 0;
}
DICOM 파일에 태그를 삽입하는 예
char uid[100]; DcmFileFormat fileformat; DcmDataset *dataset = fileformat.getDataset();
dataset->putAndInsertString(DCM_SOPClassUID, UID_SecondaryCaptureImageStorage); dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT)); dataset->putAndInsertString(DCM_PatientName, "Doe^John"); /* ... */ dataset->putAndInsertUint8Array(DCM_PixelData, pixelData, pixelLength);
//변경된 태그를 실제파일에 반영함. 이때 기존동일파일이 있으면 둘다 파일포맷이 손상되므로 주의요. 반드시 새로운 파일에 저장 OFCondition status = fileformat.saveFile("test.dcm", EXS_LittleEndianExplicit); if (status.bad()) cerr << "Error: cannot write DICOM file (" << status.text() << ")" << endl;
DICOMDIR을 생성하고 파일을 추가하는 예
DicomDirInterface dicomdir; OFCondition status = dicomdir.createNewDicomDir(); if (status.good()) { while ( /* there are files */ ) dicomdir.addDicomFile( /* current filename */ ); status = dicomdir.writeDicomDir(); if (status.bad()) cerr << "Error: cannot write DICOMDIR (" << status.text() << ")" << endl; } else cerr << "Error: cannot create DICOMDIR (" << status.text() << ")" << endl;
File open
// File open OPENFILENAME ofn; TCHAR lpstrFile[MAX_PATH] = L""; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.lpstrFile = lpstrFile; ofn.nMaxFile = MAX_PATH; if (GetOpenFileName(&ofn) != 0) { USES_CONVERSION; char* filename = W2A(ofn.lpstrFile); if(ptrDicomImage) delete ptrDicomImage; E_DecompressionColorSpaceConversion opt_decompCSconversion = EDC_photometricInterpretation; // register JPEG decompression codecs DJDecoderRegistration::registerCodecs(opt_decompCSconversion); ptrDicomImage = new DicomImage(filename); if(!ptrDicomImage) { // Fail to open file } if(ptrDicomImage->getStatus() == EIS_Normal) { // Fail to open file } InvalidateRect(hWnd, NULL, FALSE); }
Display
int width = (int)ptrDicomImage->getWidth(); int height = (int)ptrDicomImage->getHeight(); void *data = NULL; ptrDicomImage->setMinMaxWindow(); if(ptrDicomImage->createWindowsDIB(data, width*height) && data) { BITMAPINFO bi; bi.bmiHeader.biSize = sizeof(bi); bi.bmiHeader.biWidth = width; bi.bmiHeader.biHeight = -height; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 24; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biSizeImage = 0; HBITMAP hbmp = CreateDIBitmap(hdc, &bi.bmiHeader, CBM_INIT, data, &bi, DIB_RGB_COLORS); if(hbmp) { HDC memDC = CreateCompatibleDC(hdc); SelectObject(memDC, hbmp); SetStretchBltMode(hdc, HALFTONE); StretchBlt(hdc, 0, 0, width, height, memDC, 0, 0, width, height, SRCCOPY); DeleteDC(memDC); DeleteObject(hbmp); } } delete data;