Mercurial > hg > orthanc
view OrthancFramework/Sources/DicomFormat/DicomMap.cpp @ 5819:7c2b4fa94633 find-refactoring
ReadOnly transactions
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Fri, 27 Sep 2024 14:38:20 +0200 |
parents | 9990b4140c1c |
children | aeb9f63923b1 |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2023 Osimis S.A., Belgium * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/>. **/ #include "../PrecompiledHeaders.h" #include "DicomMap.h" #include <stdio.h> #include <memory> #include <boost/algorithm/string/join.hpp> #include "../Compatibility.h" #include "../Endianness.h" #include "../Logging.h" #include "../OrthancException.h" #include "../Toolbox.h" #include "DicomArray.h" #include "DicomImageInformation.h" #if ORTHANC_ENABLE_DCMTK == 1 #include "../DicomParsing/FromDcmtkBridge.h" #endif #if !defined(__EMSCRIPTEN__) // Multithreading is not supported in WebAssembly # include <boost/thread/shared_mutex.hpp> #endif namespace Orthanc { // WARNING: the DEFAULT list of main dicom tags below are the list as they // were in Orthanc 1.10 before we introduced the dynamic main dicom tags. // This list has not changed since Orthanc 1.4.2 and had a single change since // Orthanc 0.9.5. // These lists have a specific signature. When a resource does not have // the metadata "MainDicomTagsSignature", we'll assume that they were stored // with an Orthanc prior to 1.11. It is therefore very important that you never // change these lists ! Update ResetDefaultMainDicomTags instead. static const DicomTag DEFAULT_1_11_PATIENT_MAIN_DICOM_TAGS[] = { // { DicomTag(0x0010, 0x1010), "PatientAge" }, // { DicomTag(0x0010, 0x1040), "PatientAddress" }, DICOM_TAG_PATIENT_NAME, DICOM_TAG_PATIENT_BIRTH_DATE, DICOM_TAG_PATIENT_SEX, DICOM_TAG_OTHER_PATIENT_IDS, DICOM_TAG_PATIENT_ID // don't add tags here, check ResetDefaultMainDicomTags instead }; static const DicomTag DEFAULT_1_11_STUDY_MAIN_DICOM_TAGS[] = { // { DicomTag(0x0010, 0x1020), "PatientSize" }, // { DicomTag(0x0010, 0x1030), "PatientWeight" }, DICOM_TAG_STUDY_DATE, DICOM_TAG_STUDY_TIME, DICOM_TAG_STUDY_ID, DICOM_TAG_STUDY_DESCRIPTION, DICOM_TAG_ACCESSION_NUMBER, DICOM_TAG_STUDY_INSTANCE_UID, // New in db v6 (Orthanc 0.9.5) DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, DICOM_TAG_INSTITUTION_NAME, DICOM_TAG_REQUESTING_PHYSICIAN, DICOM_TAG_REFERRING_PHYSICIAN_NAME // don't add tags here, check ResetDefaultMainDicomTags instead }; static const DicomTag DEFAULT_1_11_SERIES_MAIN_DICOM_TAGS[] = { // { DicomTag(0x0010, 0x1080), "MilitaryRank" }, DICOM_TAG_SERIES_DATE, DICOM_TAG_SERIES_TIME, DICOM_TAG_MODALITY, DICOM_TAG_MANUFACTURER, DICOM_TAG_STATION_NAME, DICOM_TAG_SERIES_DESCRIPTION, DICOM_TAG_BODY_PART_EXAMINED, DICOM_TAG_SEQUENCE_NAME, DICOM_TAG_PROTOCOL_NAME, DICOM_TAG_SERIES_NUMBER, DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, DICOM_TAG_IMAGES_IN_ACQUISITION, DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, DICOM_TAG_NUMBER_OF_SLICES, DICOM_TAG_NUMBER_OF_TIME_SLICES, DICOM_TAG_SERIES_INSTANCE_UID, // New in db v6 (Orthanc 0.9.5) DICOM_TAG_IMAGE_ORIENTATION_PATIENT, DICOM_TAG_SERIES_TYPE, DICOM_TAG_OPERATOR_NAME, DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, DICOM_TAG_CONTRAST_BOLUS_AGENT // don't add tags here, check ResetDefaultMainDicomTags instead }; static const DicomTag DEFAULT_1_11_INSTANCE_MAIN_DICOM_TAGS[] = { DICOM_TAG_INSTANCE_CREATION_DATE, DICOM_TAG_INSTANCE_CREATION_TIME, DICOM_TAG_ACQUISITION_NUMBER, DICOM_TAG_IMAGE_INDEX, DICOM_TAG_INSTANCE_NUMBER, DICOM_TAG_NUMBER_OF_FRAMES, DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, DICOM_TAG_SOP_INSTANCE_UID, // New in db v6 (Orthanc 0.9.5) DICOM_TAG_IMAGE_POSITION_PATIENT, DICOM_TAG_IMAGE_COMMENTS, /** * Main DICOM tags that are not part of any release of the * database schema yet, and that will be part of future db v7. In * the meantime, the user must call "/tools/reconstruct" once to * access these tags if the corresponding DICOM files where * indexed in the database by an older version of Orthanc. **/ DICOM_TAG_IMAGE_ORIENTATION_PATIENT // New in Orthanc 1.4.2 // don't add tags here, check ResetDefaultMainDicomTags instead }; class DicomMap::MainDicomTagsConfiguration : public boost::noncopyable { private: #if !defined(__EMSCRIPTEN__) typedef boost::unique_lock<boost::shared_mutex> WriterLock; typedef boost::shared_lock<boost::shared_mutex> ReaderLock; boost::shared_mutex mutex_; #endif std::set<DicomTag> patientsMainDicomTagsByLevel_; std::set<DicomTag> studiesMainDicomTagsByLevel_; std::set<DicomTag> seriesMainDicomTagsByLevel_; std::set<DicomTag> instancesMainDicomTagsByLevel_; std::set<DicomTag> allMainDicomTags_; std::map<ResourceType, std::string> signatures_; std::map<ResourceType, std::string> defaultSignatures_; MainDicomTagsConfiguration() { ResetDefaultMainDicomTags(); } std::string ComputeSignature(const std::set<DicomTag>& tags) { // std::set are sorted by default (which is important for us !) std::set<std::string> tagsIds; for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) { tagsIds.insert(it->Format()); } std::string signatureText = boost::algorithm::join(tagsIds, ";"); return signatureText; } void LoadDefaultMainDicomTags(ResourceType level) { const DicomTag* tags = NULL; size_t size; switch (level) { case ResourceType_Patient: tags = DEFAULT_1_11_PATIENT_MAIN_DICOM_TAGS; size = sizeof(DEFAULT_1_11_PATIENT_MAIN_DICOM_TAGS) / sizeof(DicomTag); break; case ResourceType_Study: tags = DEFAULT_1_11_STUDY_MAIN_DICOM_TAGS; size = sizeof(DEFAULT_1_11_STUDY_MAIN_DICOM_TAGS) / sizeof(DicomTag); break; case ResourceType_Series: tags = DEFAULT_1_11_SERIES_MAIN_DICOM_TAGS; size = sizeof(DEFAULT_1_11_SERIES_MAIN_DICOM_TAGS) / sizeof(DicomTag); break; case ResourceType_Instance: tags = DEFAULT_1_11_INSTANCE_MAIN_DICOM_TAGS; size = sizeof(DEFAULT_1_11_INSTANCE_MAIN_DICOM_TAGS) / sizeof(DicomTag); break; default: throw OrthancException(ErrorCode_ParameterOutOfRange); } assert(tags != NULL && size != 0); for (size_t i = 0; i < size; i++) { AddMainDicomTagInternal(tags[i], level); } } std::set<DicomTag>& GetMainDicomTagsByLevelInternal(ResourceType level) { switch (level) { case ResourceType_Patient: return patientsMainDicomTagsByLevel_; case ResourceType_Study: return studiesMainDicomTagsByLevel_; case ResourceType_Series: return seriesMainDicomTagsByLevel_; case ResourceType_Instance: return instancesMainDicomTagsByLevel_; default: throw OrthancException(ErrorCode_InternalError); } } void AddMainDicomTagInternal(const DicomTag& tag, ResourceType level) { std::set<DicomTag>& existingLevelTags = GetMainDicomTagsByLevelInternal(level); if (existingLevelTags.find(tag) != existingLevelTags.end()) { throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", false); } existingLevelTags.insert(tag); allMainDicomTags_.insert(tag); signatures_[level] = ComputeSignature(GetMainDicomTagsByLevelInternal(level)); } public: // Singleton pattern static MainDicomTagsConfiguration& GetInstance() { static MainDicomTagsConfiguration parameters; return parameters; } void ResetDefaultMainDicomTags() { #if !defined(__EMSCRIPTEN__) WriterLock lock(mutex_); #endif patientsMainDicomTagsByLevel_.clear(); studiesMainDicomTagsByLevel_.clear(); seriesMainDicomTagsByLevel_.clear(); instancesMainDicomTagsByLevel_.clear(); allMainDicomTags_.clear(); // by default, initialize with the previous static list (up to 1.10.0) LoadDefaultMainDicomTags(ResourceType_Patient); LoadDefaultMainDicomTags(ResourceType_Study); LoadDefaultMainDicomTags(ResourceType_Series); LoadDefaultMainDicomTags(ResourceType_Instance); defaultSignatures_[ResourceType_Patient] = signatures_[ResourceType_Patient]; defaultSignatures_[ResourceType_Study] = signatures_[ResourceType_Study]; defaultSignatures_[ResourceType_Series] = signatures_[ResourceType_Series]; defaultSignatures_[ResourceType_Instance] = signatures_[ResourceType_Instance]; // only add new tags here ! // introduced in v 1.12.5 AddMainDicomTagInternal(DICOM_TAG_TIMEZONE_OFFSET_FROM_UTC, ResourceType_Study); // used in default QIDO-RS queries AddMainDicomTagInternal(DICOM_TAG_TIMEZONE_OFFSET_FROM_UTC, ResourceType_Series); // used in default QIDO-RS queries AddMainDicomTagInternal(DICOM_TAG_PERFORMED_PROCEDURE_STEP_START_DATE, ResourceType_Series); // used in default QIDO-RS queries AddMainDicomTagInternal(DICOM_TAG_PERFORMED_PROCEDURE_STEP_START_TIME, ResourceType_Series); // used in default QIDO-RS queries AddMainDicomTagInternal(DICOM_TAG_REQUEST_ATTRIBUTES_SEQUENCE, ResourceType_Series); // used in default QIDO-RS queries // TODO-FIND: remove it from metadata when adding it ! AddMainDicomTagInternal(DICOM_TAG_SOP_CLASS_UID, ResourceType_Instance); // previously saved in a metadata; makes more sense to store it in a DICOM tag } void AddMainDicomTag(const DicomTag& tag, ResourceType level) { #if !defined(__EMSCRIPTEN__) WriterLock lock(mutex_); #endif AddMainDicomTagInternal(tag, level); } void GetAllMainDicomTags(std::set<DicomTag>& target) { #if !defined(__EMSCRIPTEN__) ReaderLock lock(mutex_); #endif target = allMainDicomTags_; } void GetMainDicomTagsByLevel(std::set<DicomTag>& target, ResourceType level) { #if !defined(__EMSCRIPTEN__) ReaderLock lock(mutex_); #endif target = GetMainDicomTagsByLevelInternal(level); } std::string GetMainDicomTagsSignature(ResourceType level) { #if !defined(__EMSCRIPTEN__) ReaderLock lock(mutex_); #endif assert(signatures_.find(level) != signatures_.end()); return signatures_[level]; } std::string GetDefaultMainDicomTagsSignatureFrom1_11(ResourceType level) { #if !defined(__EMSCRIPTEN__) ReaderLock lock(mutex_); #endif assert(defaultSignatures_.find(level) != defaultSignatures_.end()); return defaultSignatures_[level]; } bool IsMainDicomTag(const DicomTag& tag) { #if !defined(__EMSCRIPTEN__) ReaderLock lock(mutex_); #endif return allMainDicomTags_.find(tag) != allMainDicomTags_.end(); } bool IsMainDicomTag(const DicomTag& tag, ResourceType level) { #if !defined(__EMSCRIPTEN__) ReaderLock lock(mutex_); #endif const std::set<DicomTag>& mainDicomTags = GetMainDicomTagsByLevelInternal(level); return mainDicomTags.find(tag) != mainDicomTags.end(); } }; void DicomMap::SetValueInternal(uint16_t group, uint16_t element, DicomValue* value) { DicomTag tag(group, element); Content::iterator it = content_.find(tag); if (it != content_.end()) { delete it->second; it->second = value; } else { content_.insert(std::make_pair(tag, value)); } } void DicomMap::Clear() { for (Content::iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); delete it->second; } content_.clear(); } void DicomMap::SetNullValue(uint16_t group, uint16_t element) { SetValueInternal(group, element, new DicomValue); } void DicomMap::SetNullValue(const DicomTag &tag) { SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue); } void DicomMap::SetValue(uint16_t group, uint16_t element, const DicomValue &value) { SetValueInternal(group, element, value.Clone()); } void DicomMap::SetValue(const DicomTag &tag, const DicomValue &value) { SetValueInternal(tag.GetGroup(), tag.GetElement(), value.Clone()); } void DicomMap::SetValue(const DicomTag &tag, const std::string &str, bool isBinary) { SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(str, isBinary)); } void DicomMap::SetValue(uint16_t group, uint16_t element, const std::string &str, bool isBinary) { SetValueInternal(group, element, new DicomValue(str, isBinary)); } void DicomMap::SetSequenceValue(const DicomTag& tag, const Json::Value& value) { SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(value)); } bool DicomMap::HasTag(uint16_t group, uint16_t element) const { return HasTag(DicomTag(group, element)); } bool DicomMap::HasTag(const DicomTag &tag) const { return content_.find(tag) != content_.end(); } const DicomValue &DicomMap::GetValue(uint16_t group, uint16_t element) const { return GetValue(DicomTag(group, element)); } static void ExtractTagsInternal(DicomMap& result, const DicomMap::Content& source, const std::set<DicomTag>& mainDicomTags) { result.Clear(); for (std::set<DicomTag>::const_iterator itmt = mainDicomTags.begin(); itmt != mainDicomTags.end(); ++itmt) { DicomMap::Content::const_iterator it = source.find(*itmt); if (it != source.end()) { result.SetValue(it->first, *it->second /* value will be cloned */); } } } void DicomMap::ExtractTags(DicomMap& result, const std::set<DicomTag>& tags) const { result.Clear(); for (std::set<DicomTag>::const_iterator itmt = tags.begin(); itmt != tags.end(); ++itmt) { DicomMap::Content::const_iterator it = content_.find(*itmt); if (it != content_.end()) { result.SetValue(it->first, *it->second /* value will be cloned */); } } } void DicomMap::ExtractResourceInformation(DicomMap& result, ResourceType level) const { std::set<DicomTag> mainDicomTags; DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsByLevel(mainDicomTags, level); ExtractTagsInternal(result, content_, mainDicomTags); } void DicomMap::ExtractPatientInformation(DicomMap& result) const { ExtractResourceInformation(result, ResourceType_Patient); } void DicomMap::ExtractStudyInformation(DicomMap& result) const { ExtractResourceInformation(result, ResourceType_Study); } void DicomMap::ExtractSeriesInformation(DicomMap& result) const { ExtractResourceInformation(result, ResourceType_Series); } void DicomMap::ExtractInstanceInformation(DicomMap& result) const { ExtractResourceInformation(result, ResourceType_Instance); } DicomMap::~DicomMap() { Clear(); } size_t DicomMap::GetSize() const { return content_.size(); } DicomMap* DicomMap::Clone() const { std::unique_ptr<DicomMap> result(new DicomMap); for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { result->content_.insert(std::make_pair(it->first, it->second->Clone())); } return result.release(); } void DicomMap::Assign(const DicomMap& other) { Clear(); for (Content::const_iterator it = other.content_.begin(); it != other.content_.end(); ++it) { content_.insert(std::make_pair(it->first, it->second->Clone())); } } const DicomValue& DicomMap::GetValue(const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value) { return *value; } else { throw OrthancException(ErrorCode_InexistentTag); } } const DicomValue *DicomMap::TestAndGetValue(uint16_t group, uint16_t element) const { return TestAndGetValue(DicomTag(group, element)); } const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const { Content::const_iterator it = content_.find(tag); if (it == content_.end()) { return NULL; } else { return it->second; } } void DicomMap::Remove(const DicomTag& tag) { Content::iterator it = content_.find(tag); if (it != content_.end()) { delete it->second; content_.erase(it); } } void DicomMap::RemoveTags(const std::set<DicomTag>& tags) { for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) { Remove(*it); } } void DicomMap::SetupFindPatientTemplate(DicomMap& result) { result.Clear(); // Identifying tags result.SetValue(DICOM_TAG_PATIENT_ID, "", false); // Other tags in the "Patient" module result.SetValue(DICOM_TAG_OTHER_PATIENT_IDS, "", false); result.SetValue(DICOM_TAG_PATIENT_BIRTH_DATE, "", false); result.SetValue(DICOM_TAG_PATIENT_NAME, "", false); result.SetValue(DICOM_TAG_PATIENT_SEX, "", false); } void DicomMap::SetupFindStudyTemplate(DicomMap& result) { result.Clear(); // Identifying tags result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); // Other tags in the "General Study" module result.SetValue(DICOM_TAG_REFERRING_PHYSICIAN_NAME, "", false); result.SetValue(DICOM_TAG_STUDY_DATE, "", false); result.SetValue(DICOM_TAG_STUDY_DESCRIPTION, "", false); result.SetValue(DICOM_TAG_STUDY_ID, "", false); result.SetValue(DICOM_TAG_STUDY_TIME, "", false); } void DicomMap::SetupFindSeriesTemplate(DicomMap& result) { result.Clear(); // Identifying tags result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false); // Other tags in the "General Series" module result.SetValue(DICOM_TAG_BODY_PART_EXAMINED, "", false); result.SetValue(DICOM_TAG_MODALITY, "", false); result.SetValue(DICOM_TAG_OPERATOR_NAME, "", false); result.SetValue(DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, "", false); result.SetValue(DICOM_TAG_PROTOCOL_NAME, "", false); result.SetValue(DICOM_TAG_SERIES_DATE, "", false); result.SetValue(DICOM_TAG_SERIES_DESCRIPTION, "", false); result.SetValue(DICOM_TAG_SERIES_NUMBER, "", false); result.SetValue(DICOM_TAG_SERIES_TIME, "", false); } void DicomMap::SetupFindInstanceTemplate(DicomMap& result) { result.Clear(); // Identifying tags result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false); result.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "", false); // Other tags in the "SOP Common" module result.SetValue(DICOM_TAG_ACQUISITION_NUMBER, "", false); result.SetValue(DICOM_TAG_IMAGE_COMMENTS, "", false); result.SetValue(DICOM_TAG_IMAGE_INDEX, "", false); result.SetValue(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "", false); result.SetValue(DICOM_TAG_IMAGE_POSITION_PATIENT, "", false); result.SetValue(DICOM_TAG_INSTANCE_CREATION_DATE, "", false); result.SetValue(DICOM_TAG_INSTANCE_CREATION_TIME, "", false); result.SetValue(DICOM_TAG_INSTANCE_NUMBER, "", false); result.SetValue(DICOM_TAG_NUMBER_OF_FRAMES, "", false); result.SetValue(DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, "", false); } bool DicomMap::CopyTagIfExists(const DicomMap& source, const DicomTag& tag) { if (source.HasTag(tag)) { SetValue(tag, source.GetValue(tag)); return true; } return false; } bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) { return DicomMap::MainDicomTagsConfiguration::GetInstance().IsMainDicomTag(tag, level); } bool DicomMap::IsMainDicomTag(const DicomTag& tag) { return (IsMainDicomTag(tag, ResourceType_Patient) || IsMainDicomTag(tag, ResourceType_Study) || IsMainDicomTag(tag, ResourceType_Series) || IsMainDicomTag(tag, ResourceType_Instance)); } static bool IsGenericComputedTag(const DicomTag& tag) { return tag == DICOM_TAG_RETRIEVE_URL || tag == DICOM_TAG_RETRIEVE_AE_TITLE; } bool DicomMap::IsComputedTag(const DicomTag& tag) { return (IsComputedTag(tag, ResourceType_Patient) || IsComputedTag(tag, ResourceType_Study) || IsComputedTag(tag, ResourceType_Series) || IsComputedTag(tag, ResourceType_Instance) || IsGenericComputedTag(tag)); } bool DicomMap::IsComputedTag(const DicomTag& tag, ResourceType level) { switch (level) { case ResourceType_Patient: return ( tag == DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES || tag == DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES || tag == DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES ); case ResourceType_Study: return ( tag == DICOM_TAG_MODALITIES_IN_STUDY || tag == DICOM_TAG_SOP_CLASSES_IN_STUDY || tag == DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES || tag == DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES ); case ResourceType_Series: return ( tag == DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES ); case ResourceType_Instance: return ( tag == DICOM_TAG_INSTANCE_AVAILABILITY ); default: throw OrthancException(ErrorCode_ParameterOutOfRange); } } bool DicomMap::HasOnlyComputedTags(const std::set<DicomTag>& tags) { if (tags.size() == 0) { return false; } for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) { if (!IsComputedTag(*it)) { return false; } } return true; } bool DicomMap::HasComputedTags(const std::set<DicomTag>& tags) { for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) { if (IsComputedTag(*it)) { return true; } } return false; } bool DicomMap::HasComputedTags(const std::set<DicomTag>& tags, ResourceType level) { for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) { if (IsComputedTag(*it, level)) { return true; } } return false; } void DicomMap::GetMainDicomTags(std::set<DicomTag>& target, ResourceType level) { DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsByLevel(target, level); } void DicomMap::GetAllMainDicomTags(std::set<DicomTag>& target) { DicomMap::MainDicomTagsConfiguration::GetInstance().GetAllMainDicomTags(target); } void DicomMap::AddMainDicomTag(const DicomTag& tag, ResourceType level) { DicomMap::MainDicomTagsConfiguration::GetInstance().AddMainDicomTag(tag, level); } void DicomMap::ResetDefaultMainDicomTags() { DicomMap::MainDicomTagsConfiguration::GetInstance().ResetDefaultMainDicomTags(); } std::string DicomMap::GetMainDicomTagsSignature(ResourceType level) { return DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsSignature(level); } std::string DicomMap::GetDefaultMainDicomTagsSignatureFrom1_11(ResourceType level) { return DicomMap::MainDicomTagsConfiguration::GetInstance().GetDefaultMainDicomTagsSignatureFrom1_11(level); } void DicomMap::GetTags(std::set<DicomTag>& tags) const { tags.clear(); for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { tags.insert(it->first); } } static uint16_t ReadLittleEndianUint16(const char* dicom) { const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); return (static_cast<uint16_t>(p[0]) | (static_cast<uint16_t>(p[1]) << 8)); } static uint32_t ReadLittleEndianUint32(const char* dicom) { const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); return (static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) | (static_cast<uint32_t>(p[2]) << 16) | (static_cast<uint32_t>(p[3]) << 24)); } static bool ValidateTag(const ValueRepresentation& vr, const std::string& value) { switch (vr) { case ValueRepresentation_ApplicationEntity: return value.size() <= 16; case ValueRepresentation_AgeString: return (value.size() == 4 && isdigit(value[0]) && isdigit(value[1]) && isdigit(value[2]) && (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y')); case ValueRepresentation_AttributeTag: return value.size() == 4; case ValueRepresentation_CodeString: return value.size() <= 16; case ValueRepresentation_Date: return value.size() <= 18; case ValueRepresentation_DecimalString: return value.size() <= 16; case ValueRepresentation_DateTime: return value.size() <= 54; case ValueRepresentation_FloatingPointSingle: return value.size() == 4; case ValueRepresentation_FloatingPointDouble: return value.size() == 8; case ValueRepresentation_IntegerString: return value.size() <= 12; case ValueRepresentation_LongString: return value.size() <= 64; case ValueRepresentation_LongText: return value.size() <= 10240; case ValueRepresentation_OtherByte: return true; case ValueRepresentation_OtherDouble: return value.size() <= (static_cast<uint64_t>(1) << 32) - 8; case ValueRepresentation_OtherFloat: return value.size() <= (static_cast<uint64_t>(1) << 32) - 4; case ValueRepresentation_OtherLong: return true; case ValueRepresentation_OtherWord: return true; case ValueRepresentation_PersonName: return true; case ValueRepresentation_ShortString: return value.size() <= 16; case ValueRepresentation_SignedLong: return value.size() == 4; case ValueRepresentation_Sequence: return true; case ValueRepresentation_SignedShort: return value.size() == 2; case ValueRepresentation_ShortText: return value.size() <= 1024; case ValueRepresentation_Time: return value.size() <= 28; case ValueRepresentation_UnlimitedCharacters: return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; case ValueRepresentation_UniqueIdentifier: return value.size() <= 64; case ValueRepresentation_UnsignedLong: return value.size() == 4; case ValueRepresentation_Unknown: return true; case ValueRepresentation_UniversalResource: return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; case ValueRepresentation_UnsignedShort: return value.size() == 2; case ValueRepresentation_UnlimitedText: return value.size() <= (static_cast<uint64_t>(1) << 32) - 2; default: // Assume unsupported tags are OK return true; } } static void RemoveTagPadding(std::string& value, const ValueRepresentation& vr) { /** * Remove padding from character strings, if need be. For the time * being, only the UI VR is supported. * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html **/ switch (vr) { case ValueRepresentation_UniqueIdentifier: { /** * "Values with a VR of UI shall be padded with a single * trailing NULL (00H) character when necessary to achieve even * length." **/ if (!value.empty() && value[value.size() - 1] == '\0') { value.resize(value.size() - 1); } break; } /** * TODO implement other VR **/ default: // No padding is applicable to this VR break; } } static bool ReadNextTag(DicomTag& tag, ValueRepresentation& vr, std::string& value, const char* dicom, size_t size, size_t& position) { /** * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 * This function reads a data element with Explicit VR encoded using Little-Endian. **/ if (position + 6 > size) { return false; } tag = DicomTag(ReadLittleEndianUint16(dicom + position), ReadLittleEndianUint16(dicom + position + 2)); vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true); if (vr == ValueRepresentation_NotSupported) { return false; } // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 if (vr == ValueRepresentation_ApplicationEntity /* AE */ || vr == ValueRepresentation_AgeString /* AS */ || vr == ValueRepresentation_AttributeTag /* AT */ || vr == ValueRepresentation_CodeString /* CS */ || vr == ValueRepresentation_Date /* DA */ || vr == ValueRepresentation_DecimalString /* DS */ || vr == ValueRepresentation_DateTime /* DT */ || vr == ValueRepresentation_FloatingPointSingle /* FL */ || vr == ValueRepresentation_FloatingPointDouble /* FD */ || vr == ValueRepresentation_IntegerString /* IS */ || vr == ValueRepresentation_LongString /* LO */ || vr == ValueRepresentation_LongText /* LT */ || vr == ValueRepresentation_PersonName /* PN */ || vr == ValueRepresentation_ShortString /* SH */ || vr == ValueRepresentation_SignedLong /* SL */ || vr == ValueRepresentation_SignedShort /* SS */ || vr == ValueRepresentation_ShortText /* ST */ || vr == ValueRepresentation_Time /* TM */ || vr == ValueRepresentation_UniqueIdentifier /* UI */ || vr == ValueRepresentation_UnsignedLong /* UL */ || vr == ValueRepresentation_UnsignedShort /* US */) { /** * This is Table 7.1-2. "Data Element with Explicit VR of AE, * AS, AT, CS, DA, DS, DT, FL, FD, IS, LO, LT, PN, SH, SL, SS, * ST, TM, UI, UL and US" **/ if (position + 8 > size) { return false; } uint16_t length = ReadLittleEndianUint16(dicom + position + 6); if (position + 8 + length > size) { return false; } value.assign(dicom + position + 8, length); position += (8 + length); } else { /** * This is Table 7.1-1. "Data Element with Explicit VR other * than as shown in Table 7.1-2" **/ if (position + 12 > size) { return false; } uint16_t reserved = ReadLittleEndianUint16(dicom + position + 6); if (reserved != 0) { return false; } uint32_t length = ReadLittleEndianUint32(dicom + position + 8); if (position + 12 + length > size) { return false; } value.assign(dicom + position + 12, length); position += (12 + length); } if (!ValidateTag(vr, value)) { return false; } RemoveTagPadding(value, vr); return true; } bool DicomMap::IsDicomFile(const void* dicom, size_t size) { /** * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html * According to Table 7.1-1, besides the "DICM" DICOM prefix, the * file preamble (i.e. dicom[0..127]) should not be taken into * account to determine whether the file is or is not a DICOM file. **/ const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); return (size >= 132 && p[128] == 'D' && p[129] == 'I' && p[130] == 'C' && p[131] == 'M'); } bool DicomMap::ParseDicomMetaInformation(DicomMap& result, const void* dicom, size_t size) { if (!IsDicomFile(dicom, size)) { return false; } /** * The DICOM File Meta Information must be encoded using the * Explicit VR Little Endian Transfer Syntax * (UID=1.2.840.10008.1.2.1). **/ result.Clear(); // First, we read the "File Meta Information Group Length" tag // (0002,0000) to know where to stop reading the meta header size_t position = 132; DicomTag tag(0x0000, 0x0000); // Dummy initialization ValueRepresentation vr; std::string value; if (!ReadNextTag(tag, vr, value, reinterpret_cast<const char*>(dicom), size, position) || tag.GetGroup() != 0x0002 || tag.GetElement() != 0x0000 || vr != ValueRepresentation_UnsignedLong || value.size() != 4) { return false; } size_t stopPosition = position + ReadLittleEndianUint32(value.c_str()); if (stopPosition > size) { return false; } while (position < stopPosition) { if (ReadNextTag(tag, vr, value, reinterpret_cast<const char*>(dicom), size, position)) { result.SetValue(tag, value, IsBinaryValueRepresentation(vr)); } else { return false; } } return true; } static std::string ValueAsString(const DicomMap& summary, const DicomTag& tag) { const DicomValue& value = summary.GetValue(tag); if (value.IsNull()) { return "(null)"; } else { return value.GetContent(); } } void DicomMap::LogMissingTagsForStore() const { std::string patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid; if (HasTag(DICOM_TAG_PATIENT_ID)) { patientId = ValueAsString(*this, DICOM_TAG_PATIENT_ID); } if (HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) { studyInstanceUid = ValueAsString(*this, DICOM_TAG_STUDY_INSTANCE_UID); } if (HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) { seriesInstanceUid = ValueAsString(*this, DICOM_TAG_SERIES_INSTANCE_UID); } if (HasTag(DICOM_TAG_SOP_INSTANCE_UID)) { sopInstanceUid = ValueAsString(*this, DICOM_TAG_SOP_INSTANCE_UID); } LogMissingTagsForStore(patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid); } void DicomMap::LogMissingTagsForStore(const std::string& patientId, const std::string& studyInstanceUid, const std::string& seriesInstanceUid, const std::string& sopInstanceUid) { std::string s, t; if (!patientId.empty()) { if (t.size() > 0) t += ", "; t += "PatientID=" + patientId; } else { if (s.size() > 0) s += ", "; s += "PatientID"; } if (!studyInstanceUid.empty()) { if (t.size() > 0) t += ", "; t += "StudyInstanceUID=" + studyInstanceUid; } else { if (s.size() > 0) s += ", "; s += "StudyInstanceUID"; } if (!seriesInstanceUid.empty()) { if (t.size() > 0) t += ", "; t += "SeriesInstanceUID=" + seriesInstanceUid; } else { if (s.size() > 0) s += ", "; s += "SeriesInstanceUID"; } if (!sopInstanceUid.empty()) { if (t.size() > 0) t += ", "; t += "SOPInstanceUID=" + sopInstanceUid; } else { if (s.size() > 0) s += ", "; s += "SOPInstanceUID"; } if (t.size() == 0) { LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; } else { LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; } } bool DicomMap::LookupStringValue(std::string& result, const DicomTag& tag, bool allowBinary) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->CopyToString(result, allowBinary); } } bool DicomMap::ParseInteger32(int32_t& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseInteger32(result); } } bool DicomMap::ParseInteger64(int64_t& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseInteger64(result); } } bool DicomMap::ParseUnsignedInteger32(uint32_t& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseUnsignedInteger32(result); } } bool DicomMap::ParseUnsignedInteger64(uint64_t& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseUnsignedInteger64(result); } } bool DicomMap::ParseFloat(float& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseFloat(result); } } bool DicomMap::ParseFirstFloat(float& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseFirstFloat(result); } } bool DicomMap::ParseDouble(double& result, const DicomTag& tag) const { const DicomValue* value = TestAndGetValue(tag); if (value == NULL) { return false; } else { return value->ParseDouble(result); } } void DicomMap::FromDicomAsJson(const Json::Value& dicomAsJson, bool append, bool parseSequences) { if (dicomAsJson.type() != Json::objectValue) { throw OrthancException(ErrorCode_BadFileFormat); } if (!append) { Clear(); } Json::Value::Members tags = dicomAsJson.getMemberNames(); for (Json::Value::Members::const_iterator it = tags.begin(); it != tags.end(); ++it) { DicomTag tag(0, 0); if (!DicomTag::ParseHexadecimal(tag, it->c_str())) { throw OrthancException(ErrorCode_CorruptedFile); } const Json::Value& value = dicomAsJson[*it]; if (value.type() != Json::objectValue || !value.isMember("Type") || !value.isMember("Value") || value["Type"].type() != Json::stringValue) { throw OrthancException(ErrorCode_CorruptedFile); } if (value["Type"] == "String") { if (value["Value"].type() != Json::stringValue) { throw OrthancException(ErrorCode_CorruptedFile); } else { SetValue(tag, value["Value"].asString(), false /* not binary */); } } else if (value["Type"] == "Sequence" && parseSequences) { if (value["Value"].type() != Json::arrayValue) { throw OrthancException(ErrorCode_CorruptedFile); } else { SetSequenceValue(tag, value["Value"]); } } } } void DicomMap::Merge(const DicomMap& other) { for (Content::const_iterator it = other.content_.begin(); it != other.content_.end(); ++it) { assert(it->second != NULL); if (content_.find(it->first) == content_.end()) { content_[it->first] = it->second->Clone(); } } } void DicomMap::MergeMainDicomTags(const DicomMap& other, ResourceType level) { std::set<DicomTag> mainDicomTags; DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsByLevel(mainDicomTags, level); for (std::set<DicomTag>::const_iterator itmt = mainDicomTags.begin(); itmt != mainDicomTags.end(); ++itmt) { Content::const_iterator found = other.content_.find(*itmt); if (found != other.content_.end() && content_.find(*itmt) == content_.end()) { assert(found->second != NULL); content_[*itmt] = found->second->Clone(); } } } void DicomMap::ExtractMainDicomTags(const DicomMap& other) { Clear(); MergeMainDicomTags(other, ResourceType_Patient); MergeMainDicomTags(other, ResourceType_Study); MergeMainDicomTags(other, ResourceType_Series); MergeMainDicomTags(other, ResourceType_Instance); } bool DicomMap::HasOnlyMainDicomTags() const { for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { if (!DicomMap::MainDicomTagsConfiguration::GetInstance().IsMainDicomTag(it->first)) { return false; } } return true; } void DicomMap::ExtractSequences(DicomMap& result) const { result.Clear(); for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { if (it->second->IsSequence()) { result.SetSequenceValue(it->first, it->second->GetSequenceContent()); } } } void DicomMap::Serialize(Json::Value& target) const { target = Json::objectValue; for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); std::string tag = it->first.Format(); Json::Value value; it->second->Serialize(value); target[tag] = value; } } void DicomMap::Unserialize(const Json::Value& source) { Clear(); if (source.type() != Json::objectValue) { throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members tags = source.getMemberNames(); for (size_t i = 0; i < tags.size(); i++) { DicomTag tag(0, 0); if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) || content_.find(tag) != content_.end()) { throw OrthancException(ErrorCode_BadFileFormat); } std::unique_ptr<DicomValue> value(new DicomValue); value->Unserialize(source[tags[i]]); content_[tag] = value.release(); } } void DicomMap::FromDicomWeb(const Json::Value& source) { static const char* const ALPHABETIC = "Alphabetic"; static const char* const IDEOGRAPHIC = "Ideographic"; static const char* const INLINE_BINARY = "InlineBinary"; static const char* const PHONETIC = "Phonetic"; static const char* const VALUE = "Value"; static const char* const VR = "vr"; Clear(); if (source.type() != Json::objectValue) { throw OrthancException(ErrorCode_BadFileFormat); } Json::Value::Members tags = source.getMemberNames(); for (size_t i = 0; i < tags.size(); i++) { const Json::Value& item = source[tags[i]]; DicomTag tag(0, 0); if (item.type() != Json::objectValue || !item.isMember(VR) || item[VR].type() != Json::stringValue || !DicomTag::ParseHexadecimal(tag, tags[i].c_str())) { throw OrthancException(ErrorCode_BadFileFormat); } ValueRepresentation vr = StringToValueRepresentation(item[VR].asString(), false); if (item.isMember(INLINE_BINARY)) { const Json::Value& value = item[INLINE_BINARY]; if (value.type() == Json::stringValue) { std::string decoded; Toolbox::DecodeBase64(decoded, value.asString()); SetValue(tag, decoded, true /* binary data */); } } else if (!item.isMember(VALUE)) { // Tag is present, but it has a null value SetValue(tag, "", false /* not binary */); } else { const Json::Value& value = item[VALUE]; if (value.type() == Json::arrayValue) { bool supported = true; std::string s; for (Json::Value::ArrayIndex j = 0; j < value.size() && supported; j++) { if (!s.empty()) { s += '\\'; } switch (value[j].type()) { case Json::objectValue: if (vr == ValueRepresentation_PersonName && value[j].type() == Json::objectValue) { if (value[j].isMember(ALPHABETIC) && value[j][ALPHABETIC].type() == Json::stringValue) { s += value[j][ALPHABETIC].asString(); } bool hasIdeographic = false; if (value[j].isMember(IDEOGRAPHIC) && value[j][IDEOGRAPHIC].type() == Json::stringValue) { s += '=' + value[j][IDEOGRAPHIC].asString(); hasIdeographic = true; } if (value[j].isMember(PHONETIC) && value[j][PHONETIC].type() == Json::stringValue) { if (!hasIdeographic) { s += '='; } s += '=' + value[j][PHONETIC].asString(); } } else { // This is the case of sequences supported = false; } break; case Json::stringValue: s += value[j].asString(); break; case Json::intValue: s += boost::lexical_cast<std::string>(value[j].asInt64()); break; case Json::uintValue: s += boost::lexical_cast<std::string>(value[j].asUInt64()); break; case Json::realValue: s += boost::lexical_cast<std::string>(value[j].asDouble()); break; default: break; } } if (supported) { SetValue(tag, s, false /* not binary */); } } } } } std::string DicomMap::GetStringValue(const DicomTag& tag, const std::string& defaultValue, bool allowBinary) const { std::string s; if (LookupStringValue(s, tag, allowBinary)) { return s; } else { return defaultValue; } } void DicomMap::RemoveBinaryTags() { Content kept; for (Content::iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); if (!it->second->IsBinary() && !it->second->IsNull()) { kept[it->first] = it->second; } else { delete it->second; } } content_ = kept; } void DicomMap::RemoveSequences() { Content kept; for (Content::iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); if (!it->second->IsSequence()) { kept[it->first] = it->second; } else { delete it->second; } } content_ = kept; } void DicomMap::DumpMainDicomTags(Json::Value& target, ResourceType level) const { std::set<DicomTag> mainDicomTags; DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsByLevel(mainDicomTags, level); target = Json::objectValue; for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { assert(it->second != NULL); if (!it->second->IsBinary() && !it->second->IsNull()) { std::set<DicomTag>::const_iterator found = mainDicomTags.find(it->first); if (found != mainDicomTags.end()) { #if ORTHANC_ENABLE_DCMTK == 1 target[FromDcmtkBridge::GetTagName(*found, "")] = it->second->GetContent(); #else target[found->Format()] = it->second->GetContent(); #endif } } } } ValueRepresentation DicomMap::GuessPixelDataValueRepresentation(DicomTransferSyntax transferSyntax) const { const DicomValue* value = TestAndGetValue(DICOM_TAG_BITS_ALLOCATED); uint32_t bitsAllocated; if (value == NULL || !value->ParseUnsignedInteger32(bitsAllocated)) { bitsAllocated = 8; } return DicomImageInformation::GuessPixelDataValueRepresentation(transferSyntax, bitsAllocated); } void DicomMap::Print(FILE* fp) const { DicomArray a(*this); a.Print(fp); } }