561 lines
18 KiB
C++
561 lines
18 KiB
C++
#ifndef NOMINMAX
|
||
#define NOMINMAX
|
||
#endif
|
||
#include "DvhComparePage.h"
|
||
#include "DvhPlotWidget.h"
|
||
#include "dcmIO.h"
|
||
|
||
#include <QLineEdit>
|
||
#include <QListWidget>
|
||
#include <QTableWidget>
|
||
#include <QTextEdit>
|
||
#include <QProgressBar>
|
||
#include <QCheckBox>
|
||
#include <QPushButton>
|
||
#include <QLabel>
|
||
#include <QHBoxLayout>
|
||
#include <QVBoxLayout>
|
||
#include <QGridLayout>
|
||
#include <QGroupBox>
|
||
#include <QHeaderView>
|
||
#include <QAbstractItemView>
|
||
#include <QFileDialog>
|
||
#include <QMessageBox>
|
||
#include <QSettings>
|
||
#include <QFileInfo>
|
||
#include <QDir>
|
||
#include <QCoreApplication>
|
||
#include <QLibrary>
|
||
#include <QColor>
|
||
#include <QPair>
|
||
#include <QFont>
|
||
|
||
#include <cmath>
|
||
#include <cstring>
|
||
|
||
namespace {
|
||
|
||
struct Rgb {
|
||
int r, g, b;
|
||
};
|
||
|
||
static QVector<Rgb> dvhPalette()
|
||
{
|
||
return {
|
||
{255, 0, 255}, {227, 23, 13}, {128, 42, 42}, {255, 128, 0},
|
||
{255, 153, 18}, {127, 255, 0}, {34, 139, 34}, {64, 224, 205},
|
||
{3, 168, 158}, {0, 0, 255}, {218, 112, 214}, {138, 43, 226}
|
||
};
|
||
}
|
||
|
||
static Qt::PenStyle dosePenStyle(int gid)
|
||
{
|
||
static const Qt::PenStyle styles[] = {
|
||
Qt::SolidLine, Qt::DashLine, Qt::DotLine, Qt::DashDotLine, Qt::DashDotDotLine
|
||
};
|
||
return styles[gid % 5];
|
||
}
|
||
|
||
static bool isMetaPath(const QString& path)
|
||
{
|
||
const QString l = path.toLower();
|
||
return l.endsWith(QStringLiteral(".mhd")) || l.endsWith(QStringLiteral(".mha"))
|
||
|| l.endsWith(QStringLiteral(".nii")) || l.endsWith(QStringLiteral(".nii.gz"));
|
||
}
|
||
|
||
static bool isDcmPath(const QString& path)
|
||
{
|
||
const QString l = path.toLower();
|
||
return l.endsWith(QStringLiteral(".dcm")) || l.endsWith(QStringLiteral(".dicom"));
|
||
}
|
||
|
||
} // namespace
|
||
|
||
DvhComparePage::DvhComparePage(QWidget* parent)
|
||
: QWidget(parent)
|
||
{
|
||
buildUi();
|
||
}
|
||
|
||
DvhComparePage::~DvhComparePage()
|
||
{
|
||
freeResults();
|
||
}
|
||
|
||
void DvhComparePage::buildUi()
|
||
{
|
||
// Original dvhViewer layout:
|
||
// [ ROI LISTS | DVH RESULTS | PATHS / CONTROL / OUTPUT ]
|
||
auto* root = new QHBoxLayout(this);
|
||
root->setContentsMargins(8, 8, 8, 8);
|
||
root->setSpacing(8);
|
||
|
||
// ---- Left: ROI LISTS ----
|
||
auto* roiBox = new QGroupBox(tr("ROI LISTS"), this);
|
||
auto* roiLay = new QGridLayout(roiBox);
|
||
auto* allBtn = new QPushButton(tr("ALL"), this);
|
||
auto* noneBtn = new QPushButton(tr("CLC"), this);
|
||
auto* refreshBtn = new QPushButton(tr("DSP"), this);
|
||
connect(allBtn, &QPushButton::clicked, this, &DvhComparePage::onSelectAllRoi);
|
||
connect(noneBtn, &QPushButton::clicked, this, &DvhComparePage::onClearAllRoi);
|
||
connect(refreshBtn, &QPushButton::clicked, this, &DvhComparePage::onRoiVisibilityChanged);
|
||
roiLay->addWidget(allBtn, 0, 0);
|
||
roiLay->addWidget(noneBtn, 0, 1);
|
||
roiLay->addWidget(refreshBtn, 0, 2);
|
||
m_roiList = new QListWidget(this);
|
||
roiLay->addWidget(m_roiList, 1, 0, 5, 3);
|
||
root->addWidget(roiBox, 1);
|
||
|
||
// ---- Center: DVH RESULTS ----
|
||
auto* plotBox = new QGroupBox(tr("DVH RESULTs"), this);
|
||
auto* plotLay = new QVBoxLayout(plotBox);
|
||
plotLay->setContentsMargins(4, 8, 4, 4);
|
||
m_plot = new DvhPlotWidget(this);
|
||
plotLay->addWidget(m_plot, 1);
|
||
root->addWidget(plotBox, 7);
|
||
|
||
// ---- Right: PATHS + CONTROL + OUTPUT (~20% narrower than before) ----
|
||
auto* right = new QVBoxLayout();
|
||
root->addLayout(right, 3);
|
||
|
||
auto* pathBox = new QGroupBox(tr("PATHS"), this);
|
||
auto* pathLay = new QGridLayout(pathBox);
|
||
|
||
auto* imgLab = new QLabel(tr("Image :"), this);
|
||
imgLab->setFixedWidth(70);
|
||
m_imgEdit = new QLineEdit(this);
|
||
m_imgEdit->setPlaceholderText(tr("CT/参考图像:文件夹、.mhd、.nii"));
|
||
auto* imgBtn = new QPushButton(tr("..."), this);
|
||
imgBtn->setFixedSize(24, 24);
|
||
connect(imgBtn, &QPushButton::clicked, this, &DvhComparePage::onBrowseImage);
|
||
pathLay->addWidget(imgLab, 0, 0);
|
||
pathLay->addWidget(m_imgEdit, 0, 1, 1, 10);
|
||
pathLay->addWidget(imgBtn, 0, 11);
|
||
|
||
auto* rtsLab = new QLabel(tr("Struc :"), this);
|
||
rtsLab->setFixedWidth(70);
|
||
m_rtsEdit = new QLineEdit(this);
|
||
m_rtsEdit->setPlaceholderText(tr("RTSTRUCT .dcm"));
|
||
auto* rtsBtn = new QPushButton(tr("..."), this);
|
||
rtsBtn->setFixedSize(24, 24);
|
||
connect(rtsBtn, &QPushButton::clicked, this, &DvhComparePage::onBrowseRts);
|
||
pathLay->addWidget(rtsLab, 1, 0);
|
||
pathLay->addWidget(m_rtsEdit, 1, 1, 1, 10);
|
||
pathLay->addWidget(rtsBtn, 1, 11);
|
||
|
||
auto* addDose = new QPushButton(tr("+"), this);
|
||
auto* rmDose = new QPushButton(tr("-"), this);
|
||
addDose->setFixedWidth(36);
|
||
rmDose->setFixedWidth(36);
|
||
connect(addDose, &QPushButton::clicked, this, &DvhComparePage::onAddDose);
|
||
connect(rmDose, &QPushButton::clicked, this, &DvhComparePage::onRemoveDose);
|
||
pathLay->addWidget(addDose, 2, 9);
|
||
pathLay->addWidget(rmDose, 2, 10);
|
||
|
||
m_doseTable = new QTableWidget(0, 2, this);
|
||
m_doseTable->setHorizontalHeaderLabels({tr("Name"), tr("Dose Path")});
|
||
m_doseTable->horizontalHeader()->setStretchLastSection(true);
|
||
m_doseTable->verticalHeader()->setVisible(false);
|
||
m_doseTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
m_doseTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
m_doseTable->setShowGrid(false);
|
||
pathLay->addWidget(m_doseTable, 3, 0, 8, 12);
|
||
right->addWidget(pathBox, 5);
|
||
|
||
auto* ctrlBox = new QGroupBox(tr("CONTROL"), this);
|
||
auto* ctrlLay = new QHBoxLayout(ctrlBox);
|
||
m_progress = new QProgressBar(this);
|
||
m_progress->setRange(0, 10);
|
||
m_progress->setValue(0);
|
||
m_calcBtn = new QPushButton(tr("CALCULATE"), this);
|
||
m_calcBtn->setMinimumWidth(100);
|
||
connect(m_calcBtn, &QPushButton::clicked, this, &DvhComparePage::onCalculate);
|
||
ctrlLay->addWidget(m_progress, 8);
|
||
ctrlLay->addWidget(m_calcBtn, 1);
|
||
right->addWidget(ctrlBox, 1);
|
||
|
||
auto* logBox = new QGroupBox(tr("OUT PUT INFO."), this);
|
||
auto* logLay = new QVBoxLayout(logBox);
|
||
m_log = new QTextEdit(this);
|
||
m_log->setReadOnly(true);
|
||
m_log->setFont(QFont(QStringLiteral("Consolas"), 9));
|
||
logLay->addWidget(m_log);
|
||
right->addWidget(logBox, 4);
|
||
}
|
||
|
||
QString DvhComparePage::defaultOpenDir() const
|
||
{
|
||
QSettings settings(QStringLiteral("Manteia"), QStringLiteral("DoseCompare"));
|
||
const QString last = settings.value(QStringLiteral("dvhLastOpenDir")).toString();
|
||
if (!last.isEmpty() && QDir(last).exists())
|
||
return last;
|
||
return QCoreApplication::applicationDirPath();
|
||
}
|
||
|
||
void DvhComparePage::rememberOpenPath(const QString& path)
|
||
{
|
||
const QString dir = QFileInfo(path).absolutePath();
|
||
if (dir.isEmpty())
|
||
return;
|
||
QSettings settings(QStringLiteral("Manteia"), QStringLiteral("DoseCompare"));
|
||
settings.setValue(QStringLiteral("dvhLastOpenDir"), dir);
|
||
}
|
||
|
||
QString DvhComparePage::resolveAlgoDllPath() const
|
||
{
|
||
const QString base = QCoreApplication::applicationDirPath() + QStringLiteral("/algo/");
|
||
#ifdef _DEBUG
|
||
const QString name = QStringLiteral("calcDVHLibraryD.dll");
|
||
#else
|
||
const QString name = QStringLiteral("calcDVHLibrary.dll");
|
||
#endif
|
||
return base + name;
|
||
}
|
||
|
||
void DvhComparePage::appendLog(const QString& msg)
|
||
{
|
||
m_log->append(msg);
|
||
}
|
||
|
||
void DvhComparePage::onBrowseImage()
|
||
{
|
||
const QString path = QFileDialog::getExistingDirectory(
|
||
this, tr("选择图像目录(DICOM 序列)"), defaultOpenDir());
|
||
if (!path.isEmpty()) {
|
||
m_imgEdit->setText(QDir::toNativeSeparators(path));
|
||
rememberOpenPath(path);
|
||
return;
|
||
}
|
||
const QString file = QFileDialog::getOpenFileName(
|
||
this, tr("选择图像文件"), defaultOpenDir(),
|
||
tr("Image (*.mhd *.mha *.nii *.nii.gz);;All (*.*)"));
|
||
if (file.isEmpty())
|
||
return;
|
||
m_imgEdit->setText(QDir::toNativeSeparators(file));
|
||
rememberOpenPath(file);
|
||
}
|
||
|
||
void DvhComparePage::onBrowseRts()
|
||
{
|
||
const QString file = QFileDialog::getOpenFileName(
|
||
this, tr("选择 RT Structure"), defaultOpenDir(),
|
||
tr("DICOM (*.dcm);;All (*.*)"));
|
||
if (file.isEmpty())
|
||
return;
|
||
m_rtsEdit->setText(QDir::toNativeSeparators(file));
|
||
rememberOpenPath(file);
|
||
}
|
||
|
||
void DvhComparePage::onAddDose()
|
||
{
|
||
if (m_doseTable->rowCount() >= 5) {
|
||
QMessageBox::information(this, tr("提示"), tr("最多支持 5 个剂量计划。"));
|
||
return;
|
||
}
|
||
const QString file = QFileDialog::getOpenFileName(
|
||
this, tr("选择剂量文件"), defaultOpenDir(),
|
||
tr("Dose (*.dcm *.mhd *.mha *.nii *.nii.gz);;All (*.*)"));
|
||
if (file.isEmpty())
|
||
return;
|
||
rememberOpenPath(file);
|
||
const int row = m_doseTable->rowCount();
|
||
m_doseTable->insertRow(row);
|
||
m_doseTable->setItem(row, 0, new QTableWidgetItem(QStringLiteral("Plan%1").arg(row + 1)));
|
||
m_doseTable->setItem(row, 1, new QTableWidgetItem(QDir::toNativeSeparators(file)));
|
||
}
|
||
|
||
void DvhComparePage::onRemoveDose()
|
||
{
|
||
const auto rows = m_doseTable->selectionModel()->selectedRows();
|
||
for (int i = rows.size() - 1; i >= 0; --i)
|
||
m_doseTable->removeRow(rows[i].row());
|
||
}
|
||
|
||
void DvhComparePage::freeResults()
|
||
{
|
||
if (!m_strucSet) {
|
||
m_doseNum = 0;
|
||
return;
|
||
}
|
||
for (int gid = 0; gid < m_doseNum; ++gid) {
|
||
if (!m_strucSet[gid].struc)
|
||
continue;
|
||
for (int sid = 0; sid < m_strucSet[gid].strucNum; ++sid) {
|
||
Struc* s = m_strucSet[gid].struc + sid;
|
||
if (s->h_innDat) {
|
||
free(s->h_innDat);
|
||
s->h_innDat = nullptr;
|
||
}
|
||
}
|
||
delete[] m_strucSet[gid].struc;
|
||
m_strucSet[gid].struc = nullptr;
|
||
}
|
||
delete[] m_strucSet;
|
||
m_strucSet = nullptr;
|
||
m_doseNum = 0;
|
||
m_keptDoses.clear();
|
||
m_keptMasks.clear();
|
||
}
|
||
|
||
void DvhComparePage::onCalculate()
|
||
{
|
||
m_log->clear();
|
||
m_plot->setCurves({});
|
||
freeResults();
|
||
m_progress->setValue(0);
|
||
|
||
const QString imgPath = m_imgEdit->text().trimmed();
|
||
const QString rtsPath = m_rtsEdit->text().trimmed();
|
||
if (imgPath.isEmpty() || rtsPath.isEmpty()) {
|
||
QMessageBox::warning(this, tr("警告"), tr("请先指定图像与 RT Structure 路径。"));
|
||
return;
|
||
}
|
||
|
||
QVector<QPair<QString, QString>> doses;
|
||
for (int r = 0; r < m_doseTable->rowCount(); ++r) {
|
||
const QString tag = m_doseTable->item(r, 0) ? m_doseTable->item(r, 0)->text().trimmed() : QString();
|
||
const QString path = m_doseTable->item(r, 1) ? m_doseTable->item(r, 1)->text().trimmed() : QString();
|
||
if (path.isEmpty())
|
||
continue;
|
||
doses.push_back({tag.isEmpty() ? QStringLiteral("Plan%1").arg(r + 1) : tag, path});
|
||
}
|
||
if (doses.isEmpty()) {
|
||
QMessageBox::warning(this, tr("警告"), tr("请至少添加一个剂量文件路径。"));
|
||
return;
|
||
}
|
||
if (doses.size() > 5) {
|
||
QMessageBox::warning(this, tr("警告"), tr("剂量计划数量不能超过 5。"));
|
||
return;
|
||
}
|
||
|
||
// Let user pick dose file if path cell empty was skipped; also allow browse via double-click later.
|
||
// If path looks incomplete, try open dialog for empty ones already skipped.
|
||
|
||
const QString dllPath = resolveAlgoDllPath();
|
||
QLibrary lib(dllPath);
|
||
if (!lib.load()) {
|
||
QMessageBox::warning(this, tr("警告"),
|
||
tr("无法加载 DVH 算法库:\n%1\n%2").arg(dllPath, lib.errorString()));
|
||
m_progress->setValue(10);
|
||
return;
|
||
}
|
||
|
||
typedef int (*DvhAlgoFunc)(const float* ofs, const float* spc, const int* scz,
|
||
const float* dose, StrucSet& sSet);
|
||
auto* dvhAlgoFunc = reinterpret_cast<DvhAlgoFunc>(lib.resolve("calcStrucDVH"));
|
||
if (!dvhAlgoFunc) {
|
||
QMessageBox::warning(this, tr("警告"), tr("DLL 中找不到 calcStrucDVH。"));
|
||
lib.unload();
|
||
return;
|
||
}
|
||
|
||
appendLog(tr("已加载 DLL。"));
|
||
m_progress->setValue(1);
|
||
|
||
int ret = 0;
|
||
itk::Image<float, 3>::Pointer img;
|
||
const std::string imgStd = imgPath.toLocal8Bit().constData();
|
||
if (isMetaPath(imgPath))
|
||
ret = itkMetaDataReader(imgStd, img);
|
||
else
|
||
ret = itkDicomSeriesReader(imgStd, img);
|
||
|
||
if (ret < 0 || !img) {
|
||
appendLog(tr("加载图像失败: %1").arg(ret));
|
||
QMessageBox::warning(this, tr("错误"), tr("加载图像失败。"));
|
||
lib.unload();
|
||
return;
|
||
}
|
||
appendLog(tr("图像加载成功。"));
|
||
m_progress->setValue(3);
|
||
|
||
std::vector<itk::Image<float, 3>::Pointer> doseGroup;
|
||
for (const auto& d : doses) {
|
||
itk::Image<float, 3>::Pointer dose;
|
||
const std::string p = d.second.toLocal8Bit().constData();
|
||
if (isMetaPath(d.second))
|
||
ret = itkMetaDataReader(p, dose);
|
||
else if (isDcmPath(d.second))
|
||
ret = itkDcmDoseReader(p, dose);
|
||
else
|
||
ret = -1;
|
||
if (ret < 0 || !dose) {
|
||
appendLog(tr("加载剂量失败: %1").arg(d.second));
|
||
QMessageBox::warning(this, tr("错误"), tr("加载剂量失败:%1").arg(d.second));
|
||
lib.unload();
|
||
return;
|
||
}
|
||
doseGroup.push_back(dose);
|
||
}
|
||
m_keptDoses = doseGroup;
|
||
appendLog(tr("剂量加载成功(%1)。").arg(doseGroup.size()));
|
||
m_progress->setValue(5);
|
||
|
||
std::vector<std::string> nameVec;
|
||
std::vector<itk::Image<unsigned char, 3>::Pointer> maskVec;
|
||
ret = itkRtStructureReader(rtsPath.toLocal8Bit().constData(), img, nameVec, maskVec);
|
||
if (ret < 0 || maskVec.empty()) {
|
||
appendLog(tr("加载 RT Structure 失败: %1").arg(ret));
|
||
QMessageBox::warning(this, tr("错误"), tr("加载 RT Structure 失败。"));
|
||
lib.unload();
|
||
return;
|
||
}
|
||
m_keptMasks = maskVec;
|
||
appendLog(tr("结构加载成功(%1 个 ROI)。").arg(static_cast<int>(maskVec.size())));
|
||
m_progress->setValue(7);
|
||
appendLog(tr("正在计算 DVH…"));
|
||
|
||
const int doseGroupSz = static_cast<int>(doseGroup.size());
|
||
m_doseNum = doseGroupSz;
|
||
m_strucSet = new StrucSet[doseGroupSz];
|
||
std::memset(m_strucSet, 0, sizeof(StrucSet) * doseGroupSz);
|
||
|
||
for (int gid = 0; gid < doseGroupSz; ++gid) {
|
||
m_strucSet[gid].tagName = doses[gid].first.toStdString();
|
||
m_strucSet[gid].strucNum = static_cast<int>(maskVec.size());
|
||
m_strucSet[gid].struc = new Struc[m_strucSet[gid].strucNum];
|
||
std::memset(m_strucSet[gid].struc, 0, sizeof(Struc) * m_strucSet[gid].strucNum);
|
||
|
||
for (int sid = 0; sid < static_cast<int>(maskVec.size()); ++sid) {
|
||
Struc* struc = m_strucSet[gid].struc + sid;
|
||
struc->strucName = nameVec[sid];
|
||
struc->h_dat = maskVec[sid]->GetBufferPointer();
|
||
for (int i = 0; i < 3; ++i) {
|
||
struc->origin[i] = static_cast<float>(maskVec[sid]->GetOrigin()[i]);
|
||
struc->spacing[i] = static_cast<float>(maskVec[sid]->GetSpacing()[i]);
|
||
struc->size[i] = static_cast<int>(maskVec[sid]->GetLargestPossibleRegion().GetSize()[i]);
|
||
}
|
||
struc->datSz = struc->size[0] * struc->size[1] * struc->size[2];
|
||
}
|
||
|
||
float dsOrigin[3], dsSpacing[3];
|
||
int dsSize[3];
|
||
for (int i = 0; i < 3; ++i) {
|
||
dsOrigin[i] = static_cast<float>(doseGroup[gid]->GetOrigin()[i]);
|
||
dsSpacing[i] = static_cast<float>(doseGroup[gid]->GetSpacing()[i]);
|
||
dsSize[i] = static_cast<int>(doseGroup[gid]->GetLargestPossibleRegion().GetSize()[i]);
|
||
}
|
||
float* dosePtr = doseGroup[gid]->GetBufferPointer();
|
||
dvhAlgoFunc(dsOrigin, dsSpacing, dsSize, dosePtr, m_strucSet[gid]);
|
||
|
||
appendLog(QStringLiteral(".................... %1 ....................").arg(doses[gid].first));
|
||
for (int i = 0; i < m_strucSet[gid].strucNum; ++i) {
|
||
const int datSz = m_strucSet[gid].struc[i].innSz;
|
||
if (datSz <= 0 || !m_strucSet[gid].struc[i].h_innDat)
|
||
continue;
|
||
double sum = 0;
|
||
for (int k = 0; k < datSz; ++k)
|
||
sum += m_strucSet[gid].struc[i].h_innDat[k];
|
||
sum /= datSz;
|
||
const QString name = QString::fromStdString(m_strucSet[gid].struc[i].strucName);
|
||
appendLog(tr("%1 / %2 min=%3 max=%4 ave=%5")
|
||
.arg(doses[gid].first, name)
|
||
.arg(m_strucSet[gid].struc[i].h_innDat[0], 0, 'f', 3)
|
||
.arg(m_strucSet[gid].struc[i].h_innDat[datSz - 1], 0, 'f', 3)
|
||
.arg(sum, 0, 'f', 3));
|
||
}
|
||
}
|
||
|
||
m_progress->setValue(10);
|
||
appendLog(tr("DVH 计算完成。"));
|
||
lib.unload();
|
||
|
||
rebuildRoiList();
|
||
refreshPlot();
|
||
}
|
||
|
||
void DvhComparePage::rebuildRoiList()
|
||
{
|
||
m_roiList->clear();
|
||
m_roiChecks.clear();
|
||
if (!m_strucSet || m_doseNum <= 0)
|
||
return;
|
||
|
||
for (int i = 0; i < m_strucSet[0].strucNum; ++i) {
|
||
auto* item = new QListWidgetItem(m_roiList);
|
||
auto* check = new QCheckBox(QString::fromStdString(m_strucSet[0].struc[i].strucName), m_roiList);
|
||
check->setChecked(true);
|
||
m_roiList->addItem(item);
|
||
m_roiList->setItemWidget(item, check);
|
||
m_roiChecks.push_back(check);
|
||
connect(check, &QCheckBox::toggled, this, &DvhComparePage::onRoiVisibilityChanged);
|
||
}
|
||
}
|
||
|
||
void DvhComparePage::onSelectAllRoi()
|
||
{
|
||
for (QCheckBox* c : m_roiChecks)
|
||
c->setChecked(true);
|
||
}
|
||
|
||
void DvhComparePage::onClearAllRoi()
|
||
{
|
||
for (QCheckBox* c : m_roiChecks)
|
||
c->setChecked(false);
|
||
}
|
||
|
||
void DvhComparePage::onRoiVisibilityChanged()
|
||
{
|
||
refreshPlot();
|
||
}
|
||
|
||
void DvhComparePage::refreshPlot()
|
||
{
|
||
QVector<DvhCurve> curves;
|
||
if (!m_strucSet || m_doseNum <= 0) {
|
||
m_plot->setCurves(curves);
|
||
return;
|
||
}
|
||
|
||
const auto palette = dvhPalette();
|
||
float maxDose = 0.f;
|
||
QVector<float> sampleBuf(DVH_MAX_SPN);
|
||
|
||
for (int gid = 0; gid < m_doseNum; ++gid) {
|
||
for (int i = 0; i < m_strucSet[gid].strucNum; ++i) {
|
||
if (i < m_roiChecks.size() && !m_roiChecks[i]->isChecked())
|
||
continue;
|
||
Struc* struc = m_strucSet[gid].struc + i;
|
||
if (!struc->h_innDat || struc->innSz <= 0)
|
||
continue;
|
||
|
||
int dvhSz = struc->innSz;
|
||
float* dvhDat = struc->h_innDat;
|
||
if (dvhSz > DVH_MAX_SPN) {
|
||
sampleBuf[0] = dvhDat[0];
|
||
sampleBuf[DVH_MAX_SPN - 1] = dvhDat[dvhSz - 1];
|
||
const float span = (struc->innSz - 1) * 1.f / (DVH_MAX_SPN - 1);
|
||
for (int k = 1; k < DVH_MAX_SPN - 1; ++k) {
|
||
const float p = k * span;
|
||
const int id0 = static_cast<int>(std::floor(p));
|
||
const int id1 = id0 + 1;
|
||
const float w0 = id1 - p;
|
||
const float w1 = p - id0;
|
||
sampleBuf[k] = w0 * dvhDat[id0] + w1 * dvhDat[id1];
|
||
}
|
||
dvhDat = sampleBuf.data();
|
||
dvhSz = DVH_MAX_SPN;
|
||
}
|
||
|
||
DvhCurve curve;
|
||
curve.name = QStringLiteral("%1 (%2)")
|
||
.arg(QString::fromStdString(struc->strucName))
|
||
.arg(QString::fromStdString(m_strucSet[gid].tagName));
|
||
const Rgb rgb = palette[i % palette.size()];
|
||
curve.color = QColor(rgb.r, rgb.g, rgb.b);
|
||
curve.style = dosePenStyle(gid);
|
||
curve.points.reserve(dvhSz + 1);
|
||
curve.points.push_back(QPointF(0.0, 1.0));
|
||
for (int j = 0; j < dvhSz; ++j) {
|
||
curve.points.push_back(QPointF(dvhDat[j], 1.0 - (j + 1) / (1.0 * dvhSz)));
|
||
if (dvhDat[j] > maxDose)
|
||
maxDose = dvhDat[j];
|
||
}
|
||
curves.push_back(curve);
|
||
}
|
||
}
|
||
|
||
m_plot->setXMax(maxDose > 0 ? maxDose * 1.2 : 1.0);
|
||
m_plot->setCurves(curves);
|
||
}
|