DoseCompare/src/gamma/RoiLoader.cpp

196 lines
6.1 KiB
C++

#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "RoiLoader.h"
#include "dcmIO.h"
#include <QFileInfo>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDir>
#include <itkResampleImageFilter.h>
#include <itkNearestNeighborInterpolateImageFunction.h>
#include <itkImageRegionIterator.h>
#include <itkImageDuplicator.h>
#include <gdcmReader.h>
#include <gdcmMediaStorage.h>
#include <gdcmTag.h>
#include <gdcmDataSet.h>
DropContentKind classifyMedicalPath(const QString& path)
{
if (path.isEmpty())
return DropContentKind::Unknown;
const QString lower = path.toLower();
if (lower.endsWith(QStringLiteral(".json"))) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly))
return DropContentKind::Unknown;
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
if (doc.isObject() && doc.object().contains(QStringLiteral("roiList")))
return DropContentKind::Structure;
return DropContentKind::Unknown;
}
if (lower.endsWith(QStringLiteral(".mhd")) || lower.endsWith(QStringLiteral(".mha"))
|| lower.endsWith(QStringLiteral(".nii")) || lower.endsWith(QStringLiteral(".nii.gz")))
return DropContentKind::Dose;
if (lower.endsWith(QStringLiteral(".dcm")) || lower.endsWith(QStringLiteral(".dicom"))) {
gdcm::Reader reader;
reader.SetFileName(path.toLocal8Bit().constData());
if (!reader.Read())
return DropContentKind::Unknown;
gdcm::MediaStorage ms;
ms.SetFromFile(reader.GetFile());
if (ms == gdcm::MediaStorage::RTStructureSetStorage)
return DropContentKind::Structure;
if (ms == gdcm::MediaStorage::RTDoseStorage)
return DropContentKind::Dose;
// Other DICOM: treat as dose candidate (CT series folder is handled separately).
const gdcm::DataSet& ds = reader.GetFile().GetDataSet();
if (ds.FindDataElement(gdcm::Tag(0x3006, 0x0020)))
return DropContentKind::Structure;
if (ds.FindDataElement(gdcm::Tag(0x3004, 0x000e)) || ds.FindDataElement(gdcm::Tag(0x3004, 0x000E)))
return DropContentKind::Dose;
return DropContentKind::Dose;
}
return DropContentKind::Unknown;
}
int loadRoiStructures(
const QString& path,
RoiFloatImage::Pointer referenceImage,
std::vector<std::string>& nameVec,
std::vector<RoiMaskType::Pointer>& maskVec,
QString* error)
{
nameVec.clear();
maskVec.clear();
if (!referenceImage) {
if (error)
*error = QStringLiteral("缺少参考图像网格");
return -1;
}
if (path.isEmpty() || !QFileInfo::exists(path)) {
if (error)
*error = QStringLiteral("勾画文件不存在");
return -1;
}
const QString lower = path.toLower();
if (lower.endsWith(QStringLiteral(".dcm")) || lower.endsWith(QStringLiteral(".dicom"))) {
const int ret = itkRtStructureReader(path.toLocal8Bit().constData(), referenceImage, nameVec, maskVec);
if (ret < 0) {
if (error)
*error = QStringLiteral("读取 RTSTRUCT 失败 (%1)").arg(ret);
return ret;
}
return 0;
}
if (lower.endsWith(QStringLiteral(".json"))) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) {
if (error)
*error = QStringLiteral("无法打开 JSON");
return -1;
}
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
if (!doc.isObject()) {
if (error)
*error = QStringLiteral("JSON 格式错误");
return -1;
}
const QJsonArray roiList = doc.object().value(QStringLiteral("roiList")).toArray();
if (roiList.isEmpty()) {
if (error)
*error = QStringLiteral("JSON 中没有 roiList");
return -1;
}
const QString folder = QFileInfo(path).absolutePath();
for (const QJsonValue& v : roiList) {
const QJsonObject o = v.toObject();
QString niiPath = o.value(QStringLiteral("niiPath")).toString();
const QString roiName = o.value(QStringLiteral("roiName")).toString();
if (niiPath.isEmpty())
continue;
if (!QFileInfo(niiPath).isAbsolute())
niiPath = QDir(folder).filePath(niiPath);
RoiMaskType::Pointer mask;
const int ret = itkMetaMaskReader(niiPath.toLocal8Bit().constData(), mask);
if (ret < 0 || !mask) {
if (error)
*error = QStringLiteral("读取 ROI NIfTI 失败: %1").arg(niiPath);
return -1;
}
nameVec.push_back(roiName.isEmpty() ? QFileInfo(niiPath).baseName().toStdString()
: roiName.toStdString());
maskVec.push_back(mask);
}
if (maskVec.empty()) {
if (error)
*error = QStringLiteral("未加载到任何 ROI");
return -1;
}
return 0;
}
if (error)
*error = QStringLiteral("仅支持 .dcm RTSTRUCT 或 .json ROI 列表");
return -1;
}
RoiMaskType::Pointer resampleMaskToImage(
RoiMaskType::Pointer mask,
RoiFloatImage::Pointer target)
{
if (!mask || !target)
return nullptr;
using Resample = itk::ResampleImageFilter<RoiMaskType, RoiMaskType>;
using Interp = itk::NearestNeighborInterpolateImageFunction<RoiMaskType, double>;
auto filter = Resample::New();
auto interp = Interp::New();
filter->SetInterpolator(interp);
filter->SetInput(mask);
filter->SetSize(target->GetLargestPossibleRegion().GetSize());
filter->SetOutputOrigin(target->GetOrigin());
filter->SetOutputSpacing(target->GetSpacing());
filter->SetOutputDirection(target->GetDirection());
filter->SetDefaultPixelValue(0);
filter->Update();
return filter->GetOutput();
}
RoiFloatImage::Pointer applyMaskToDose(
RoiFloatImage::Pointer dose,
RoiMaskType::Pointer maskOnDoseGrid)
{
if (!dose)
return nullptr;
using Dup = itk::ImageDuplicator<RoiFloatImage>;
auto dup = Dup::New();
dup->SetInputImage(dose);
dup->Update();
auto out = dup->GetOutput();
if (!maskOnDoseGrid)
return out;
itk::ImageRegionIterator<RoiFloatImage> dit(out, out->GetLargestPossibleRegion());
itk::ImageRegionConstIterator<RoiMaskType> mit(maskOnDoseGrid, maskOnDoseGrid->GetLargestPossibleRegion());
for (dit.GoToBegin(), mit.GoToBegin(); !dit.IsAtEnd() && !mit.IsAtEnd(); ++dit, ++mit) {
if (mit.Get() == 0)
dit.Set(0.f);
}
return out;
}