#ifndef NOMINMAX #define NOMINMAX #endif #include "RoiLoader.h" #include "dcmIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& nameVec, std::vector& 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; using Interp = itk::NearestNeighborInterpolateImageFunction; 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; auto dup = Dup::New(); dup->SetInputImage(dose); dup->Update(); auto out = dup->GetOutput(); if (!maskOnDoseGrid) return out; itk::ImageRegionIterator dit(out, out->GetLargestPossibleRegion()); itk::ImageRegionConstIterator 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; }