196 lines
6.1 KiB
C++
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;
|
|
}
|