Mercurial > hg > orthanc-stone
changeset 2223:e928629d7df0
added support for non-TID1500 in DicomStructuredReport
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 23 Apr 2025 17:52:01 +0200 (6 days ago) |
parents | 22975e748165 |
children | bdb28e2d2767 a943ce01ce0a |
files | OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp OrthancStone/Sources/Toolbox/DicomStructuredReport.h OrthancStone/Sources/Toolbox/StoneToolbox.cpp OrthancStone/Sources/Toolbox/StoneToolbox.h |
diffstat | 4 files changed, 285 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp Wed Apr 23 13:14:42 2025 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.cpp Wed Apr 23 17:52:01 2025 +0200 @@ -23,8 +23,10 @@ #include "DicomStructuredReport.h" +#include "StoneToolbox.h" #include "../Scene2D/ScenePoint2D.h" +#include <ChunkedBuffer.h> #include <OrthancException.h> #include <SerializationToolbox.h> @@ -310,24 +312,13 @@ } - DicomStructuredReport::DicomStructuredReport(Orthanc::ParsedDicomFile& dicom) + void DicomStructuredReport::ReadTID1500(Orthanc::ParsedDicomFile& dicom) { DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - studyInstanceUid_ = GetStringValue(dataset, DCM_StudyInstanceUID); - seriesInstanceUid_ = GetStringValue(dataset, DCM_SeriesInstanceUID); - sopInstanceUid_ = GetStringValue(dataset, DCM_SOPInstanceUID); - - CheckStringValue(dataset, DCM_Modality, "SR"); CheckStringValue(dataset, DCM_SOPClassUID, "1.2.840.10008.5.1.4.1.1.88.33"); // Comprehensive SR IOD CheckStringValue(dataset, DCM_ValueType, "CONTAINER"); - if (!IsDicomConcept(dataset, "126000") /* Imaging measurement report */ || - !IsDicomTemplate(dataset, "1500")) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - DcmSequenceOfItems& sequence = GetSequenceValue(dataset, DCM_CurrentRequestedProcedureEvidenceSequence); std::list<std::string> tmp; @@ -507,6 +498,174 @@ } + static bool ReadTextualReport(Json::Value& target, + DcmItem& dataset) + { + target = Json::arrayValue; + + if (dataset.tagExists(DCM_ContentSequence)) + { + DcmSequenceOfItems& content = GetSequenceValue(dataset, DCM_ContentSequence); + for (unsigned long i = 0; i < content.card(); i++) + { + DcmItem* item = content.getItem(i); + if (item != NULL && + item->tagExists(DCM_ValueType) && + item->tagExists(DCM_ConceptNameCodeSequence)) + { + const std::string valueType = GetStringValue(*item, DCM_ValueType); + + DcmSequenceOfItems& concepts = GetSequenceValue(*item, DCM_ConceptNameCodeSequence); + if (concepts.card() == 1 && + concepts.getItem(0) != NULL) + { + DcmItem& concept = *concepts.getItem(0); + if (concept.tagExists(DCM_CodeMeaning)) + { + const std::string codeMeaning = GetStringValue(concept, DCM_CodeMeaning); + + bool hasValue = false; + std::string value; + + if (valueType == "TEXT" && + item->tagExists(DCM_TextValue)) + { + value = GetStringValue(*item, DCM_TextValue); + hasValue = true; + } + else if (valueType == "UIDREF" && + item->tagExists(DCM_UID)) + { + value = GetStringValue(*item, DCM_UID); + hasValue = true; + } + else if (valueType == "CODE" && + item->tagExists(DCM_ConceptCodeSequence)) + { + DcmSequenceOfItems& codes = GetSequenceValue(*item, DCM_ConceptCodeSequence); + if (codes.card() == 1 && + codes.getItem(0) != NULL) + { + DcmItem& code = *codes.getItem(0); + if (code.tagExists(DCM_CodeMeaning)) + { + value = GetStringValue(code, DCM_CodeMeaning); + hasValue = true; + } + } + } + else if (valueType == "NUM" && + item->tagExists(DCM_MeasuredValueSequence)) + { + DcmSequenceOfItems& measurements = GetSequenceValue(*item, DCM_MeasuredValueSequence); + if (measurements.card() == 1 && + measurements.getItem(0) != NULL) + { + DcmItem& measurement = *measurements.getItem(0); + if (measurement.tagExists(DCM_NumericValue)) + { + value = GetStringValue(measurement, DCM_NumericValue); + + if (measurement.tagExists(DCM_MeasurementUnitsCodeSequence)) + { + DcmSequenceOfItems& units = GetSequenceValue(measurement, DCM_MeasurementUnitsCodeSequence); + if (units.card() == 1 && + units.getItem(0) != NULL) + { + DcmItem& unit = *units.getItem(0); + if (unit.tagExists(DCM_CodeValue) && + unit.tagExists(DCM_CodingSchemeDesignator)) + { + const std::string& code = GetStringValue(unit, DCM_CodeValue); + const std::string& scheme = GetStringValue(unit, DCM_CodingSchemeDesignator); + if (scheme != "UCUM" || + code != "1") + { + // In UCUM, the "1" code means "no unit" + value += " " + code; + } + } + } + } + + hasValue = true; + } + } + } + + if (!hasValue && + valueType != "CONTAINER") + { + value = "<" + valueType + ">"; + hasValue = true; + } + + Json::Value line = Json::arrayValue; + line.append(codeMeaning); + + if (hasValue) + { + line.append(value); + } + else + { + line.append(Json::nullValue); + } + + Json::Value children; + if (ReadTextualReport(children, *item)) // Recursive call + { + line.append(children); + } + + target.append(line); + } + } + } + } + + return true; + } + else + { + return false; + } + } + + + DicomStructuredReport::DicomStructuredReport(Orthanc::ParsedDicomFile& dicom) : + isTID1500_(false) + { + StoneToolbox::ExtractMainDicomTags(mainDicomTags_, dicom); + StoneToolbox::CopyDicomTag(mainDicomTags_, dicom, Orthanc::DicomTag(0x0040, 0xa491)); // "Completion Flag" + StoneToolbox::CopyDicomTag(mainDicomTags_, dicom, Orthanc::DicomTag(0x0040, 0xa493)); // "Verification Flag" + + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + + studyInstanceUid_ = GetStringValue(dataset, DCM_StudyInstanceUID); + seriesInstanceUid_ = GetStringValue(dataset, DCM_SeriesInstanceUID); + sopInstanceUid_ = GetStringValue(dataset, DCM_SOPInstanceUID); + + SopClassUid sopClassUid = StringToSopClassUid(GetStringValue(dataset, DCM_SOPClassUID)); + if (sopClassUid != SopClassUid_ComprehensiveSR) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + CheckStringValue(dataset, DCM_Modality, "SR"); + + ReadTextualReport(textualReport_, dataset); + + if (IsDicomConcept(dataset, "126000") /* Imaging measurement report */ && + IsDicomTemplate(dataset, "1500") && + dataset.tagExists(DCM_CurrentRequestedProcedureEvidenceSequence)) + { + ReadTID1500(dicom); + isTID1500_ = true; + } + } + + DicomStructuredReport::DicomStructuredReport(const DicomStructuredReport& other) : studyInstanceUid_(other.studyInstanceUid_), seriesInstanceUid_(other.seriesInstanceUid_), @@ -628,4 +787,45 @@ found->second->GetSeriesInstanceUid() == seriesInstanceUid); } } + + + static void Flatten(Orthanc::ChunkedBuffer& buffer, + const Json::Value& node, + const std::string& indent) + { + assert(node.type() == Json::arrayValue); + for (Json::ArrayIndex i = 0; i < node.size(); i++) + { + assert(node[i].type() == Json::arrayValue); + assert(node[i].size() == 2 || node[i].size() == 3); + assert(node[i][0].type() == Json::stringValue); + assert(node[i][1].type() == Json::stringValue || node[i][1].type() == Json::nullValue); + + std::string line = indent + boost::lexical_cast<std::string>(i + 1) + ". " + node[i][0].asString(); + + if (node[i][1].type() == Json::stringValue) + { + line += ": " + node[i][1].asString(); + } + else + { + assert(node[i][1].type() == Json::nullValue); + } + + buffer.AddChunk(line + "\n"); + + if (node[i].size() == 3) + { + Flatten(buffer, node[i][2], indent + " "); + } + } + } + + + void DicomStructuredReport::FlattenTextualReport(std::string& target) const + { + Orthanc::ChunkedBuffer buffer; + Flatten(buffer, textualReport_, ""); + buffer.Flatten(target); + } }
--- a/OrthancStone/Sources/Toolbox/DicomStructuredReport.h Wed Apr 23 13:14:42 2025 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomStructuredReport.h Wed Apr 23 17:52:01 2025 +0200 @@ -205,9 +205,15 @@ bool hasProbabilityOfCancer, float probabilityOfCancer); + void ReadTID1500(Orthanc::ParsedDicomFile& dicom); + std::string studyInstanceUid_; std::string seriesInstanceUid_; std::string sopInstanceUid_; + Orthanc::DicomMap mainDicomTags_; + Json::Value textualReport_; + + bool isTID1500_; std::map<std::string, ReferencedInstance*> instancesInformation_; std::vector<std::string> orderedInstances_; std::deque<Structure*> structures_; @@ -283,6 +289,21 @@ return sopInstanceUid_; } + const Orthanc::DicomMap& GetMainDicomTags() const + { + return mainDicomTags_; + } + + const Json::Value& GetTextualReport() const + { + return textualReport_; + } + + bool IsTID1500() const + { + return isTID1500_; + } + size_t GetReferencedInstancesCount() const { return orderedInstances_.size(); @@ -306,5 +327,7 @@ bool IsReferencedInstance(const std::string& studyInstanceUid, const std::string& seriesInstanceUid, const std::string& sopInstanceUid) const; + + void FlattenTextualReport(std::string& target) const; }; }
--- a/OrthancStone/Sources/Toolbox/StoneToolbox.cpp Wed Apr 23 13:14:42 2025 +0200 +++ b/OrthancStone/Sources/Toolbox/StoneToolbox.cpp Wed Apr 23 17:52:01 2025 +0200 @@ -46,5 +46,36 @@ return base.substr(0, end) + "/" + path.substr(start); } + + +#if ORTHANC_ENABLE_DCMTK == 1 + void CopyDicomTag(Orthanc::DicomMap& target, + const Orthanc::ParsedDicomFile& source, + const Orthanc::DicomTag& tag) + { + std::string s; + if (source.GetTagValue(s, tag)) + { + target.SetValue(tag, s, false); + } + } +#endif + + +#if ORTHANC_ENABLE_DCMTK == 1 + void ExtractMainDicomTags(Orthanc::DicomMap& target, + const Orthanc::ParsedDicomFile& source) + { + target.Clear(); + + std::set<Orthanc::DicomTag> tags; + Orthanc::DicomMap::GetAllMainDicomTags(tags); + + for (std::set<Orthanc::DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + CopyDicomTag(target, source, *it); + } + } +#endif } }
--- a/OrthancStone/Sources/Toolbox/StoneToolbox.h Wed Apr 23 13:14:42 2025 +0200 +++ b/OrthancStone/Sources/Toolbox/StoneToolbox.h Wed Apr 23 17:52:01 2025 +0200 @@ -23,6 +23,14 @@ #pragma once +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 +# include <DicomParsing/ParsedDicomFile.h> +#endif + #include <string> namespace OrthancStone @@ -31,5 +39,16 @@ { std::string JoinUrl(const std::string& base, const std::string& path); + +#if ORTHANC_ENABLE_DCMTK == 1 + void CopyDicomTag(Orthanc::DicomMap& target, + const Orthanc::ParsedDicomFile& source, + const Orthanc::DicomTag& tag); +#endif + +#if ORTHANC_ENABLE_DCMTK == 1 + void ExtractMainDicomTags(Orthanc::DicomMap& target, + const Orthanc::ParsedDicomFile& source); +#endif } }