第01章 11 分别使用DCMTK和gdcm库,解析DICOM文件系列的dicom标准数据信息
下面是一个使用 Qt 和 C++ 的示例,分别使用 DCMTK 和 GDCM 库解析 DICOM 文件,将解析到的 DICOM 标准数据信息显示在 QT 属性控件中。我们将创建一个简单的 GUI 应用程序,用户可以选择 DICOM 文件,解析其信息,并显示在属性控件中。
环境准备
- 安装 Qt 6.x。
- 安装 DCMTK 和 GDCM 库。
- 创建一个 Qt 项目。
项目结构
DicomInfoViewer/
├── CMakeLists.txt
├── main.cpp
├── dicom_loader.h
├── dicom_loader.cpp
├── viewer.h
├── viewer.cpp
├── dicom_properties_model.h
├── dicom_properties_model.cpp
├── mainwindow.h
├── mainwindow.cpp
├── mainwindow.ui
└── CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(DicomInfoViewer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
add_executable(DicomInfoViewer
main.cpp
dicom_loader.cpp
dicom_loader.h
viewer.cpp
viewer.h
dicom_properties_model.cpp
dicom_properties_model.h
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
target_link_libraries(DicomInfoViewer PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
# 链接 DCMTK 和 GDCM 库
find_package(DCMTK REQUIRED)
find_package(GDCM REQUIRED)
target_link_libraries(DicomInfoViewer PRIVATE ${DCMTK_LIBRARIES} ${GDCM_LIBRARIES})
# 包含路径
target_include_directories(DicomInfoViewer PRIVATE ${DCMTK_INCLUDE_DIRS} ${GDCM_INCLUDE_DIRS})
main.cpp
#include <QtWidgets>
#include "mainwindow.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}
dicom_loader.h
#ifndef DICOM_LOADER_H
#define DICOM_LOADER_H
#include <string>
#include <unordered_map>
#include <QVector>
#include <QMap>
#include <QPair>
class DicomLoader {
public:
struct DicomTag {
std::string tag;
std::string value;
};
using DicomTags = QVector<DicomTag>;
DicomTags loadTagsWithDCMTK(const std::string& filePath);
DicomTags loadTagsWithGDCM(const std::string& filePath);
};
#endif // DICOM_LOADER_H
dicom_loader.cpp
#include "dicom_loader.h"
#include <dcmtk/dcmdata/dctk.h>
#include <gdcmReader.h>
#include <gdcmTag.h>
#include <gdcmStringFilter.h>
DicomLoader::DicomTags DicomLoader::loadTagsWithDCMTK(const std::string& filePath) {
DicomTags tags;
DcmFileFormat fileformat;
if (fileformat.loadFile(filePath.c_str()).good()) {
DcmDataset *dataset = fileformat.getDataset();
OFString tagValue;
for (int i = 0x0002; i < 0x1000; ++i) {
for (int j = 0x0000; j < 0xFFFF; ++j) {
if (dataset->search(tagValue, makeTag(i, j),<hsearch_mode>(EEM Std) | EGL create)) {
tags.append({fmt::format("({:04X},{:04X})", i, j).c_str(), tagValue.c_str()});
}
}
}
}
return tags;
}
DicomLoader::DicomTags DicomLoader::loadTagsWithGDCM(const std::string& filePath) {
DicomTags tags;
gdcm::Reader reader;
reader.SetFileName(filePath.c_str());
if (reader.Read()) {
gdcm::File &file = reader.GetFile();
gdcm::DataSet &dataset = file.GetDataSet();
std::vector<gdcm::Tag> tags = file.GetHeader().GetTags();
for (const auto& tag : tags) {
gdcm::SmartPointer<gdcm::DataElement> element = dataset.GetDataElement(tag);
if (element) {
gdcm::StringFilter sf;
sf.SetElement(element);
tags.append({fmt::format("({:04X},{:04X})", tag.GetGroup(), tag.GetElement()).c_str(), sf.ToString().c_str()});
}
}
}
return tags;
}
dicom_properties_model.h
#ifndef DICOM_PROPERTIES_MODEL_H
#define DICOM_PROPERTIES_MODEL_H
#include <QAbstractTableModel>
#include <QVector>
#include <QPair>
class DicomPropertiesModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit DicomPropertiesModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void setTags(const QVector<QPair<QString, QString>>& tags);
private:
QVector<QPair<QString, QString>> m_tags;
};
#endif // DICOM_PROPERTIES_MODEL_H
dicom_properties_model.cpp
#include "dicom_properties_model.h"
DicomPropertiesModel::DicomPropertiesModel(QObject *parent)
: QAbstractTableModel(parent) {}
int DicomPropertiesModel::rowCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : m_tags.size();
}
int DicomPropertiesModel::columnCount(const QModelIndex &parent) const {
return parent.isValid() ? 0 : 2;
}
QVariant DicomPropertiesModel::data(const QModelIndex &index, int role) const {
if (!index.isValid() || index.row() >= m_tags.size() || index.column() >= 2) {
return QVariant();
}
if (role == Qt::DisplayRole) {
const auto& tag = m_tags[index.row()];
return index.column() == 0 ? tag.first : tag.second;
}
return QVariant();
}
QVariant DicomPropertiesModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role != Qt::DisplayRole || orientation != Qt::Horizontal) {
return QVariant();
}
return section == 0 ? "Tag" : "Value";
}
void DicomPropertiesModel::setTags(const QVector<QPair<QString, QString>>& tags) {
beginResetModel();
m_tags = tags;
endResetModel();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "dicom_loader.h"
#include "dicom_properties_model.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onOpenFile();
void onLoadTagsWithDCMTK();
void onLoadTagsWithGDCM();
private:
Ui::MainWindow *ui;
DicomLoader loader;
DicomPropertiesModel model;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
ui->tableView->setModel(&model);
connect(ui->actionOpenFile, &QAction::triggered, this, &MainWindow::onOpenFile);
connect(ui->actionLoadTagsWithDCMTK, &QAction::triggered, this, &MainWindow::onLoadTagsWithDCMTK);
connect(ui->actionLoadTagsWithGDCM, &QAction::triggered, this, &MainWindow::onLoadTagsWithGDCM);
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::onOpenFile() {
QString filePath = QFileDialog::getOpenFileName(this, "Open DICOM File", "", "DICOM Files (*.dcm)");
if (filePath.isEmpty()) {
return;
}
onOpenFile(filePath.toStdString());
}
void MainWindow::onLoadTagsWithDCMTK() {
QString filePath = QFileDialog::getOpenFileName(this, "Open DICOM File", "", "DICOM Files (*.dcm)");
if (filePath.isEmpty()) {
return;
}
auto tags = loader.loadTagsWithDCMTK(filePath.toStdString());
QVector<QPair<QString, QString>> tagPairs;
for (const auto& tag : tags) {
tagPairs.append({tag.tag.c_str(), tag.value.c_str()});
}
model.setTags(tagPairs);
}
void MainWindow::onLoadTagsWithGDCM() {
QString filePath = QFileDialog::getOpenFileName(this, "Open DICOM File", "", "DICOM Files (*.dcm)");
if (filePath.isEmpty()) {
return;
}
auto tags = loader.loadTagsWithGDCM(filePath.toStdString());
QVector<QPair<QString, QString>> tagPairs;
for (const auto& tag : tags) {
tagPairs.append({tag.tag.c_str(), tag.value.c_str()});
}
model.setTags(tagPairs);
}
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>DICOM Info Viewer</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpenFile"/>
</widget>
<widget class="QMenu" name="menuLoadTags">
<property name="title">
<string>Load Tags</string>
</property>
<addaction name="actionLoadTagsWithDCMTK"/>
<addaction name="actionLoadTagsWithGDCM"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuLoadTags"/>
</widget>
<action name="actionOpenFile">
<property name="text">
<string>Open File</string>
</property>
</action>
<action name="actionLoadTagsWithDCMTK">
<property name="text">
<string>Load Tags with DCMTK</string>
</property>
</action>
<action name="actionLoadTagsWithGDCM">
<property name="text">
<string>Load Tags with GDCM</string>
</property>
</action>
</widget>
</ui>
构建和运行
-
使用 CMake 构建项目:
mkdir build cd build cmake .. make
-
运行应用程序:
./DicomInfoViewer
-
打开应用程序后,选择一个 DICOM 文件,选择使用 DCMTK 或 GDCM 解析其标签信息,信息将显示在表格中。