# HG changeset patch # User Sebastien Jodogne # Date 1465484698 -7200 # Node ID 3251ec958a29e5c7433bbcf552823939960a94a1 # Parent 2fffa4d0f313c954a70eae566093c87c372c2e14 Option "RestrictTransferSyntaxes" saying which transfer syntaxes should be decoded with GDCM diff -r 2fffa4d0f313 -r 3251ec958a29 CMakeLists.txt --- a/CMakeLists.txt Tue May 31 12:12:59 2016 +0200 +++ b/CMakeLists.txt Thu Jun 09 17:04:58 2016 +0200 @@ -147,6 +147,9 @@ ${CMAKE_SOURCE_DIR}/Orthanc/Core/SQLite/Transaction.cpp ${CMAKE_SOURCE_DIR}/Orthanc/Core/Toolbox.cpp ${CMAKE_SOURCE_DIR}/Orthanc/Core/Uuid.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/Core/DicomFormat/DicomMap.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/Core/DicomFormat/DicomTag.cpp + ${CMAKE_SOURCE_DIR}/Orthanc/Core/DicomFormat/DicomValue.cpp ${CMAKE_SOURCE_DIR}/Orthanc/Resources/ThirdParty/base64/base64.cpp ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheManager.cpp diff -r 2fffa4d0f313 -r 3251ec958a29 NEWS --- a/NEWS Tue May 31 12:12:59 2016 +0200 +++ b/NEWS Thu Jun 09 17:04:58 2016 +0200 @@ -2,6 +2,7 @@ =============================== * Option "EnableGdcm" to replace the built-in decoder of Orthanc with GDCM +* Option "RestrictTransferSyntaxes" saying which transfer syntaxes should be decoded with GDCM * Fixed rendering of 16bpp images if values are < 0 or >= 32768 * Decoding of JPEG2000 lossless images with YBR_RCT photometric interpretation diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/DicomFormat/DicomMap.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/DicomFormat/DicomMap.cpp Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,783 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomMap.h" + +#include +#include +#include "../Endianness.h" +#include "../OrthancException.h" + + +namespace Orthanc +{ + static DicomTag patientTags[] = + { + //DicomTag(0x0010, 0x1010), // PatientAge + //DicomTag(0x0010, 0x1040) // PatientAddress + DicomTag(0x0010, 0x0010), // PatientName + DicomTag(0x0010, 0x0030), // PatientBirthDate + DicomTag(0x0010, 0x0040), // PatientSex + DicomTag(0x0010, 0x1000), // OtherPatientIDs + DICOM_TAG_PATIENT_ID + }; + + static DicomTag studyTags[] = + { + //DicomTag(0x0010, 0x1020), // PatientSize + //DicomTag(0x0010, 0x1030) // PatientWeight + DICOM_TAG_STUDY_DATE, + DicomTag(0x0008, 0x0030), // StudyTime + DicomTag(0x0020, 0x0010), // StudyID + DICOM_TAG_STUDY_DESCRIPTION, + DICOM_TAG_ACCESSION_NUMBER, + DICOM_TAG_STUDY_INSTANCE_UID, + DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, // New in db v6 + DICOM_TAG_INSTITUTION_NAME, // New in db v6 + DICOM_TAG_REQUESTING_PHYSICIAN, // New in db v6 + DICOM_TAG_REFERRING_PHYSICIAN_NAME // New in db v6 + }; + + static DicomTag seriesTags[] = + { + //DicomTag(0x0010, 0x1080), // MilitaryRank + DicomTag(0x0008, 0x0021), // SeriesDate + DicomTag(0x0008, 0x0031), // SeriesTime + DICOM_TAG_MODALITY, + DicomTag(0x0008, 0x0070), // Manufacturer + DicomTag(0x0008, 0x1010), // StationName + DICOM_TAG_SERIES_DESCRIPTION, + DicomTag(0x0018, 0x0015), // BodyPartExamined + DicomTag(0x0018, 0x0024), // SequenceName + DicomTag(0x0018, 0x1030), // ProtocolName + DicomTag(0x0020, 0x0011), // SeriesNumber + 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, + DICOM_TAG_IMAGE_ORIENTATION_PATIENT, // New in db v6 + DICOM_TAG_SERIES_TYPE, // New in db v6 + DICOM_TAG_OPERATOR_NAME, // New in db v6 + DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, // New in db v6 + DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, // New in db v6 + DICOM_TAG_CONTRAST_BOLUS_AGENT // New in db v6 + }; + + static DicomTag instanceTags[] = + { + DicomTag(0x0008, 0x0012), // InstanceCreationDate + DicomTag(0x0008, 0x0013), // InstanceCreationTime + DicomTag(0x0020, 0x0012), // AcquisitionNumber + DICOM_TAG_IMAGE_INDEX, + DICOM_TAG_INSTANCE_NUMBER, + DICOM_TAG_NUMBER_OF_FRAMES, + DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, + DICOM_TAG_SOP_INSTANCE_UID, + DICOM_TAG_IMAGE_POSITION_PATIENT, // New in db v6 + DICOM_TAG_IMAGE_COMMENTS // New in db v6 + }; + + + void DicomMap::LoadMainDicomTags(const DicomTag*& tags, + size_t& size, + ResourceType level) + { + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + void DicomMap::SetValue(uint16_t group, + uint16_t element, + DicomValue* value) + { + DicomTag tag(group, element); + Map::iterator it = map_.find(tag); + + if (it != map_.end()) + { + delete it->second; + it->second = value; + } + else + { + map_.insert(std::make_pair(tag, value)); + } + } + + void DicomMap::SetValue(DicomTag tag, + DicomValue* value) + { + SetValue(tag.GetGroup(), tag.GetElement(), value); + } + + + + + void DicomMap::Clear() + { + for (Map::iterator it = map_.begin(); it != map_.end(); ++it) + { + delete it->second; + } + + map_.clear(); + } + + + void DicomMap::ExtractTags(DicomMap& result, + const DicomTag* tags, + size_t count) const + { + result.Clear(); + + for (unsigned int i = 0; i < count; i++) + { + Map::const_iterator it = map_.find(tags[i]); + if (it != map_.end()) + { + result.SetValue(it->first, it->second->Clone()); + } + } + } + + + void DicomMap::ExtractPatientInformation(DicomMap& result) const + { + ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag)); + } + + void DicomMap::ExtractStudyInformation(DicomMap& result) const + { + ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); + } + + void DicomMap::ExtractSeriesInformation(DicomMap& result) const + { + ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); + } + + void DicomMap::ExtractInstanceInformation(DicomMap& result) const + { + ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); + } + + + + DicomMap* DicomMap::Clone() const + { + std::auto_ptr result(new DicomMap); + + for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it) + { + result->map_.insert(std::make_pair(it->first, it->second->Clone())); + } + + return result.release(); + } + + + void DicomMap::Assign(const DicomMap& other) + { + Clear(); + + for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it) + { + map_.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(const DicomTag& tag) const + { + Map::const_iterator it = map_.find(tag); + + if (it == map_.end()) + { + return NULL; + } + else + { + return it->second; + } + } + + + void DicomMap::Remove(const DicomTag& tag) + { + Map::iterator it = map_.find(tag); + if (it != map_.end()) + { + delete it->second; + map_.erase(it); + } + } + + + static void SetupFindTemplate(DicomMap& result, + const DicomTag* tags, + size_t count) + { + result.Clear(); + + for (size_t i = 0; i < count; i++) + { + result.SetValue(tags[i], "", false); + } + } + + void DicomMap::SetupFindPatientTemplate(DicomMap& result) + { + SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag)); + } + + void DicomMap::SetupFindStudyTemplate(DicomMap& result) + { + SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag)); + result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); + result.SetValue(DICOM_TAG_PATIENT_ID, "", false); + + // These main DICOM tags are only indirectly related to the + // General Study Module, remove them + result.Remove(DICOM_TAG_INSTITUTION_NAME); + result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN); + result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION); + } + + void DicomMap::SetupFindSeriesTemplate(DicomMap& result) + { + SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag)); + result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); + result.SetValue(DICOM_TAG_PATIENT_ID, "", false); + result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); + + // These tags are considered as "main" by Orthanc, but are not in the Series module + result.Remove(DicomTag(0x0008, 0x0070)); // Manufacturer + result.Remove(DicomTag(0x0008, 0x1010)); // Station name + result.Remove(DicomTag(0x0018, 0x0024)); // Sequence name + result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES); + result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION); + result.Remove(DICOM_TAG_NUMBER_OF_SLICES); + result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS); + result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES); + result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT); + result.Remove(DICOM_TAG_SERIES_TYPE); + result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION); + result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT); + } + + void DicomMap::SetupFindInstanceTemplate(DicomMap& result) + { + SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag)); + result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); + result.SetValue(DICOM_TAG_PATIENT_ID, "", false); + result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); + result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false); + } + + + void DicomMap::CopyTagIfExists(const DicomMap& source, + const DicomTag& tag) + { + if (source.HasTag(tag)) + { + SetValue(tag, source.GetValue(tag)); + } + } + + + bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) + { + DicomTag *tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < size; i++) + { + if (tags[i] == tag) + { + return true; + } + } + + return false; + } + + bool DicomMap::IsMainDicomTag(const DicomTag& tag) + { + return (IsMainDicomTag(tag, ResourceType_Patient) || + IsMainDicomTag(tag, ResourceType_Study) || + IsMainDicomTag(tag, ResourceType_Series) || + IsMainDicomTag(tag, ResourceType_Instance)); + } + + + void DicomMap::GetMainDicomTagsInternal(std::set& result, ResourceType level) + { + DicomTag *tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < size; i++) + { + result.insert(tags[i]); + } + } + + + void DicomMap::GetMainDicomTags(std::set& result, ResourceType level) + { + result.clear(); + GetMainDicomTagsInternal(result, level); + } + + + void DicomMap::GetMainDicomTags(std::set& result) + { + result.clear(); + GetMainDicomTagsInternal(result, ResourceType_Patient); + GetMainDicomTagsInternal(result, ResourceType_Study); + GetMainDicomTagsInternal(result, ResourceType_Series); + GetMainDicomTagsInternal(result, ResourceType_Instance); + } + + + void DicomMap::GetTags(std::set& tags) const + { + tags.clear(); + + for (Map::const_iterator it = map_.begin(); + it != map_.end(); ++it) + { + tags.insert(it->first); + } + } + + + static uint16_t ReadUnsignedInteger16(const char* dicom) + { + return le16toh(*reinterpret_cast(dicom)); + } + + + static uint32_t ReadUnsignedInteger32(const char* dicom) + { + return le32toh(*reinterpret_cast(dicom)); + } + + + 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((1llu << 32) - 8); + + case ValueRepresentation_OtherFloat: + return value.size() <= static_cast((1llu << 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((1llu << 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((1llu << 32) - 2); + + case ValueRepresentation_UnsignedShort: + return value.size() == 2; + + case ValueRepresentation_UnlimitedText: + return value.size() <= static_cast((1llu << 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(ReadUnsignedInteger16(dicom + position), + ReadUnsignedInteger16(dicom + position + 2)); + + vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true); + if (vr == ValueRepresentation_NotSupported) + { + return false; + } + + if (vr == ValueRepresentation_OtherByte || + vr == ValueRepresentation_OtherDouble || + vr == ValueRepresentation_OtherFloat || + vr == ValueRepresentation_OtherLong || + vr == ValueRepresentation_OtherWord || + vr == ValueRepresentation_Sequence || + vr == ValueRepresentation_UnlimitedCharacters || + vr == ValueRepresentation_UniversalResource || + vr == ValueRepresentation_UnlimitedText || + vr == ValueRepresentation_Unknown) // Note that "UN" should never appear in the Meta Information + { + if (position + 12 > size) + { + return false; + } + + uint32_t length = ReadUnsignedInteger32(dicom + position + 8); + + if (position + 12 + length > size) + { + return false; + } + + value.assign(dicom + position + 12, length); + position += (12 + length); + } + else + { + if (position + 8 > size) + { + return false; + } + + uint16_t length = ReadUnsignedInteger16(dicom + position + 6); + + if (position + 8 + length > size) + { + return false; + } + + value.assign(dicom + position + 8, length); + position += (8 + length); + } + + if (!ValidateTag(vr, value)) + { + return false; + } + + RemoveTagPadding(value, vr); + + return true; + } + + + bool DicomMap::ParseDicomMetaInformation(DicomMap& result, + const char* 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. + **/ + + if (size < 132 || + dicom[128] != 'D' || + dicom[129] != 'I' || + dicom[130] != 'C' || + dicom[131] != 'M') + { + 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, dicom, size, position) || + tag.GetGroup() != 0x0002 || + tag.GetElement() != 0x0000 || + vr != ValueRepresentation_UnsignedLong || + value.size() != 4) + { + return false; + } + + size_t stopPosition = position + ReadUnsignedInteger32(value.c_str()); + if (stopPosition > size) + { + return false; + } + + while (position < stopPosition) + { + if (ReadNextTag(tag, vr, value, dicom, size, position)) + { + result.SetValue(tag, value, IsBinaryValueRepresentation(vr)); + } + else + { + return false; + } + } + + return true; + } +} diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/DicomFormat/DicomMap.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/DicomFormat/DicomMap.h Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,186 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "DicomTag.h" +#include "DicomValue.h" +#include "../Enumerations.h" + +#include +#include +#include + +namespace Orthanc +{ + class DicomMap : public boost::noncopyable + { + private: + friend class DicomArray; + friend class FromDcmtkBridge; + friend class ToDcmtkBridge; + + typedef std::map Map; + + Map map_; + + // Warning: This takes the ownership of "value" + void SetValue(uint16_t group, + uint16_t element, + DicomValue* value); + + void SetValue(DicomTag tag, + DicomValue* value); + + void ExtractTags(DicomMap& source, + const DicomTag* tags, + size_t count) const; + + static void GetMainDicomTagsInternal(std::set& result, ResourceType level); + + public: + DicomMap() + { + } + + ~DicomMap() + { + Clear(); + } + + size_t GetSize() const + { + return map_.size(); + } + + DicomMap* Clone() const; + + void Assign(const DicomMap& other); + + void Clear(); + + void SetValue(uint16_t group, + uint16_t element, + const DicomValue& value) + { + SetValue(group, element, value.Clone()); + } + + void SetValue(const DicomTag& tag, + const DicomValue& value) + { + SetValue(tag, value.Clone()); + } + + void SetValue(const DicomTag& tag, + const std::string& str, + bool isBinary) + { + SetValue(tag, new DicomValue(str, isBinary)); + } + + void SetValue(uint16_t group, + uint16_t element, + const std::string& str, + bool isBinary) + { + SetValue(group, element, new DicomValue(str, isBinary)); + } + + bool HasTag(uint16_t group, uint16_t element) const + { + return HasTag(DicomTag(group, element)); + } + + bool HasTag(const DicomTag& tag) const + { + return map_.find(tag) != map_.end(); + } + + const DicomValue& GetValue(uint16_t group, uint16_t element) const + { + return GetValue(DicomTag(group, element)); + } + + const DicomValue& GetValue(const DicomTag& tag) const; + + // DO NOT delete the returned value! + const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const + { + return TestAndGetValue(DicomTag(group, element)); + } + + // DO NOT delete the returned value! + const DicomValue* TestAndGetValue(const DicomTag& tag) const; + + void Remove(const DicomTag& tag); + + void ExtractPatientInformation(DicomMap& result) const; + + void ExtractStudyInformation(DicomMap& result) const; + + void ExtractSeriesInformation(DicomMap& result) const; + + void ExtractInstanceInformation(DicomMap& result) const; + + static void SetupFindPatientTemplate(DicomMap& result); + + static void SetupFindStudyTemplate(DicomMap& result); + + static void SetupFindSeriesTemplate(DicomMap& result); + + static void SetupFindInstanceTemplate(DicomMap& result); + + void CopyTagIfExists(const DicomMap& source, + const DicomTag& tag); + + static bool IsMainDicomTag(const DicomTag& tag, ResourceType level); + + static bool IsMainDicomTag(const DicomTag& tag); + + static void GetMainDicomTags(std::set& result, ResourceType level); + + static void GetMainDicomTags(std::set& result); + + void Print(FILE* fp) const; + + void GetTags(std::set& tags) const; + + static void LoadMainDicomTags(const DicomTag*& tags, + size_t& size, + ResourceType level); + + static bool ParseDicomMetaInformation(DicomMap& result, + const char* dicom, + size_t size); + }; +} diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/DicomFormat/DicomTag.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/DicomFormat/DicomTag.cpp Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,252 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomTag.h" + +#include "../OrthancException.h" + +#include +#include +#include + +namespace Orthanc +{ + bool DicomTag::operator< (const DicomTag& other) const + { + if (group_ < other.group_) + return true; + + if (group_ > other.group_) + return false; + + return element_ < other.element_; + } + + + std::ostream& operator<< (std::ostream& o, const DicomTag& tag) + { + using namespace std; + ios_base::fmtflags state = o.flags(); + o.flags(ios::right | ios::hex); + o << "(" << setfill('0') << setw(4) << tag.GetGroup() + << "," << setw(4) << tag.GetElement() << ")"; + o.flags(state); + return o; + } + + + std::string DicomTag::Format() const + { + char b[16]; + sprintf(b, "%04x,%04x", group_, element_); + return std::string(b); + } + + + const char* DicomTag::GetMainTagsName() const + { + if (*this == DICOM_TAG_ACCESSION_NUMBER) + return "AccessionNumber"; + + if (*this == DICOM_TAG_SOP_INSTANCE_UID) + return "SOPInstanceUID"; + + if (*this == DICOM_TAG_PATIENT_ID) + return "PatientID"; + + if (*this == DICOM_TAG_SERIES_INSTANCE_UID) + return "SeriesInstanceUID"; + + if (*this == DICOM_TAG_STUDY_INSTANCE_UID) + return "StudyInstanceUID"; + + if (*this == DICOM_TAG_PIXEL_DATA) + return "PixelData"; + + if (*this == DICOM_TAG_IMAGE_INDEX) + return "ImageIndex"; + + if (*this == DICOM_TAG_INSTANCE_NUMBER) + return "InstanceNumber"; + + if (*this == DICOM_TAG_NUMBER_OF_SLICES) + return "NumberOfSlices"; + + if (*this == DICOM_TAG_NUMBER_OF_FRAMES) + return "NumberOfFrames"; + + if (*this == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES) + return "CardiacNumberOfImages"; + + if (*this == DICOM_TAG_IMAGES_IN_ACQUISITION) + return "ImagesInAcquisition"; + + if (*this == DICOM_TAG_PATIENT_NAME) + return "PatientName"; + + if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT) + return "ImagePositionPatient"; + + if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT) + return "ImageOrientationPatient"; + + return ""; + } + + + void DicomTag::AddTagsForModule(std::set& target, + DicomModule module) + { + // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions + + switch (module) + { + case DicomModule_Patient: + // This is Table C.7-1 "Patient Module Attributes" (p. 373) + target.insert(DicomTag(0x0010, 0x0010)); // Patient's name + target.insert(DicomTag(0x0010, 0x0020)); // Patient ID + target.insert(DicomTag(0x0010, 0x0030)); // Patient's birth date + target.insert(DicomTag(0x0010, 0x0040)); // Patient's sex + target.insert(DicomTag(0x0008, 0x1120)); // Referenced patient sequence + target.insert(DicomTag(0x0010, 0x0032)); // Patient's birth time + target.insert(DicomTag(0x0010, 0x1000)); // Other patient IDs + target.insert(DicomTag(0x0010, 0x1002)); // Other patient IDs sequence + target.insert(DicomTag(0x0010, 0x1001)); // Other patient names + target.insert(DicomTag(0x0010, 0x2160)); // Ethnic group + target.insert(DicomTag(0x0010, 0x4000)); // Patient comments + target.insert(DicomTag(0x0010, 0x2201)); // Patient species description + target.insert(DicomTag(0x0010, 0x2202)); // Patient species code sequence + target.insert(DicomTag(0x0010, 0x2292)); // Patient breed description + target.insert(DicomTag(0x0010, 0x2293)); // Patient breed code sequence + target.insert(DicomTag(0x0010, 0x2294)); // Breed registration sequence + target.insert(DicomTag(0x0010, 0x2297)); // Responsible person + target.insert(DicomTag(0x0010, 0x2298)); // Responsible person role + target.insert(DicomTag(0x0010, 0x2299)); // Responsible organization + target.insert(DicomTag(0x0012, 0x0062)); // Patient identity removed + target.insert(DicomTag(0x0012, 0x0063)); // De-identification method + target.insert(DicomTag(0x0012, 0x0064)); // De-identification method code sequence + + // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112) + target.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID + target.insert(DicomTag(0x0010, 0x0024)); // Issuer of Patient ID qualifiers sequence + break; + + case DicomModule_Study: + // This is Table C.7-3 "General Study Module Attributes" (p. 378) + target.insert(DicomTag(0x0020, 0x000d)); // Study instance UID + target.insert(DicomTag(0x0008, 0x0020)); // Study date + target.insert(DicomTag(0x0008, 0x0030)); // Study time + target.insert(DicomTag(0x0008, 0x0090)); // Referring physician's name + target.insert(DicomTag(0x0008, 0x0096)); // Referring physician identification sequence + target.insert(DicomTag(0x0020, 0x0010)); // Study ID + target.insert(DicomTag(0x0008, 0x0050)); // Accession number + target.insert(DicomTag(0x0008, 0x0051)); // Issuer of accession number sequence + target.insert(DicomTag(0x0008, 0x1030)); // Study description + target.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of record + target.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of record identification sequence + target.insert(DicomTag(0x0008, 0x1060)); // Name of physician(s) reading study + target.insert(DicomTag(0x0008, 0x1062)); // Physician(s) reading study identification sequence + target.insert(DicomTag(0x0032, 0x1034)); // Requesting service code sequence + target.insert(DicomTag(0x0008, 0x1110)); // Referenced study sequence + target.insert(DicomTag(0x0008, 0x1032)); // Procedure code sequence + target.insert(DicomTag(0x0040, 0x1012)); // Reason for performed procedure code sequence + break; + + case DicomModule_Series: + // This is Table C.7-5 "General Series Module Attributes" (p. 385) + target.insert(DicomTag(0x0008, 0x0060)); // Modality + target.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID + target.insert(DicomTag(0x0020, 0x0011)); // Series Number + target.insert(DicomTag(0x0020, 0x0060)); // Laterality + target.insert(DicomTag(0x0008, 0x0021)); // Series Date + target.insert(DicomTag(0x0008, 0x0031)); // Series Time + target.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians’ Name + target.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence + target.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + target.insert(DicomTag(0x0008, 0x103e)); // Series Description + target.insert(DicomTag(0x0008, 0x103f)); // Series Description Code Sequence + target.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + target.insert(DicomTag(0x0008, 0x1072)); // Operator Identification Sequence + target.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence + target.insert(DicomTag(0x0008, 0x1250)); // Related Series Sequence + target.insert(DicomTag(0x0018, 0x0015)); // Body Part Examined + target.insert(DicomTag(0x0018, 0x5100)); // Patient Position + target.insert(DicomTag(0x0028, 0x0108)); // Smallest Pixel Value in Series + target.insert(DicomTag(0x0029, 0x0109)); // Largest Pixel Value in Series + target.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + target.insert(DicomTag(0x0010, 0x2210)); // Anatomical Orientation Type + + // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES + target.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + target.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + target.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + target.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + target.insert(DicomTag(0x0040, 0x0260)); // Performed Protocol Code Sequence + target.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step + break; + + case DicomModule_Instance: + // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207) + target.insert(DicomTag(0x0008, 0x0016)); // SOP Class UID + target.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID + target.insert(DicomTag(0x0008, 0x0005)); // Specific Character Set + target.insert(DicomTag(0x0008, 0x0012)); // Instance Creation Date + target.insert(DicomTag(0x0008, 0x0013)); // Instance Creation Time + target.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + target.insert(DicomTag(0x0008, 0x001a)); // Related General SOP Class UID + target.insert(DicomTag(0x0008, 0x001b)); // Original Specialized SOP Class UID + target.insert(DicomTag(0x0008, 0x0110)); // Coding Scheme Identification Sequence + target.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + target.insert(DicomTag(0x0018, 0xa001)); // Contributing Equipment Sequence + target.insert(DicomTag(0x0020, 0x0013)); // Instance Number + target.insert(DicomTag(0x0100, 0x0410)); // SOP Instance Status + target.insert(DicomTag(0x0100, 0x0420)); // SOP Authorization DateTime + target.insert(DicomTag(0x0100, 0x0424)); // SOP Authorization Comment + target.insert(DicomTag(0x0100, 0x0426)); // Authorization Equipment Certification Number + target.insert(DicomTag(0x0400, 0x0500)); // Encrypted Attributes Sequence + target.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + target.insert(DicomTag(0x0040, 0xa390)); // HL7 Structured Document Reference Sequence + target.insert(DicomTag(0x0028, 0x0303)); // Longitudinal Temporal Information Modified + + // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216) + target.insert(DicomTag(0x4ffe, 0x0001)); // MAC Parameters sequence + target.insert(DicomTag(0xfffa, 0xfffa)); // Digital signatures sequence + break; + + // TODO IMAGE MODULE? + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +} diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/DicomFormat/DicomTag.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/DicomFormat/DicomTag.h Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,182 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include +#include + +#include "../Enumerations.h" + +namespace Orthanc +{ + class DicomTag + { + // This must stay a POD (plain old data structure) + + private: + uint16_t group_; + uint16_t element_; + + public: + DicomTag(uint16_t group, + uint16_t element) : + group_(group), + element_(element) + { + } + + uint16_t GetGroup() const + { + return group_; + } + + uint16_t GetElement() const + { + return element_; + } + + bool IsPrivate() const + { + return group_ % 2 == 1; + } + + const char* GetMainTagsName() const; + + bool operator< (const DicomTag& other) const; + + bool operator== (const DicomTag& other) const + { + return group_ == other.group_ && element_ == other.element_; + } + + bool operator!= (const DicomTag& other) const + { + return !(*this == other); + } + + std::string Format() const; + + friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); + + static void AddTagsForModule(std::set& target, + DicomModule module); + }; + + // Aliases for the most useful tags + static const DicomTag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050); + static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018); + static const DicomTag DICOM_TAG_PATIENT_ID(0x0010, 0x0020); + static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e); + static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d); + static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010); + + static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330); + static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013); + + static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081); + static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101); + static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); + static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090); + static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002); + static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010); + static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011); + + static const DicomTag DICOM_TAG_STUDY_DESCRIPTION(0x0008, 0x1030); + static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e); + static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060); + + // The following is used for "modify/anonymize" operations + static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); + static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002); + static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003); + static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063); + + // DICOM tags used for fMRI (thanks to Will Ryder) + static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); + static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100); + + // Tags for C-FIND and C-MOVE + static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); + static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052); + static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061); + + // Tags for images + static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011); + static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010); + static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002); + static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100); + static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101); + static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102); + static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); + static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006); + static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); + static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037); + static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032); + + // Tags related to date and time + static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022); + static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032); + static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023); + static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033); + static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012); + static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013); + static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030); + static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032); + static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021); + static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031); + static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020); + static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030); + + // Various tags + static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000); + static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060); + static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080); + static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032); + static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090); + static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070); + static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254); + static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000); + static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400); + static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010); + + // Counting patients, studies and series + // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200); + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202); + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204); + static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206); + static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208); + static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209); + static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062); +} diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/DicomFormat/DicomValue.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/DicomFormat/DicomValue.cpp Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomValue.h" + +#include "../OrthancException.h" +#include "../Toolbox.h" + +namespace Orthanc +{ + DicomValue::DicomValue(const DicomValue& other) : + type_(other.type_), + content_(other.content_) + { + } + + + DicomValue::DicomValue(const std::string& content, + bool isBinary) : + type_(isBinary ? Type_Binary : Type_String), + content_(content) + { + } + + + DicomValue::DicomValue(const char* data, + size_t size, + bool isBinary) : + type_(isBinary ? Type_Binary : Type_String) + { + content_.assign(data, size); + } + + + const std::string& DicomValue::GetContent() const + { + if (type_ == Type_Null) + { + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return content_; + } + } + + + DicomValue* DicomValue::Clone() const + { + return new DicomValue(*this); + } + + +#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 + void DicomValue::FormatDataUriScheme(std::string& target, + const std::string& mime) const + { + Toolbox::EncodeBase64(target, GetContent()); + target.insert(0, "data:" + mime + ";base64,"); + } +#endif + +} diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/DicomFormat/DicomValue.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/DicomFormat/DicomValue.h Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,91 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +namespace Orthanc +{ + class DicomValue : public boost::noncopyable + { + private: + enum Type + { + Type_Null, + Type_String, + Type_Binary + }; + + Type type_; + std::string content_; + + DicomValue(const DicomValue& other); + + public: + DicomValue() : type_(Type_Null) + { + } + + DicomValue(const std::string& content, + bool isBinary); + + DicomValue(const char* data, + size_t size, + bool isBinary); + + const std::string& GetContent() const; + + bool IsNull() const + { + return type_ == Type_Null; + } + + bool IsBinary() const + { + return type_ == Type_Binary; + } + + DicomValue* Clone() const; + +#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1 + void FormatDataUriScheme(std::string& target, + const std::string& mime) const; + + void FormatDataUriScheme(std::string& target) const + { + FormatDataUriScheme(target, "application/octet-stream"); + } +#endif + }; +} diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/Endianness.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Orthanc/Core/Endianness.h Thu Jun 09 17:04:58 2016 +0200 @@ -0,0 +1,157 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + + +/******************************************************************** + ** LINUX ARCHITECTURES + ********************************************************************/ + +#if defined(__linux__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include +#endif + + +/******************************************************************** + ** WINDOWS ARCHITECTURES + ** + ** On Windows x86, "host" will always be little-endian ("le"). + ********************************************************************/ + +#if defined(_WIN32) +# if defined(_MSC_VER) +// Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# define be16toh(x) _byteswap_ushort(x) +# define be32toh(x) _byteswap_ulong(x) +# define be64toh(x) _byteswap_uint64(x) +# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +// MinGW >= 4.3 - Use builtin intrinsic for byte swapping +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# define be16toh(x) __builtin_bswap16(x) +# define be32toh(x) __builtin_bswap32(x) +# define be64toh(x) __builtin_bswap64(x) +# else +// MinGW <= 4.2, we must manually implement the byte swapping +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0 +# define be16toh(x) __orthanc_bswap16(x) +# define be32toh(x) __orthanc_bswap32(x) +# define be64toh(x) __orthanc_bswap64(x) +# endif + +# define htobe16(x) be16toh(x) +# define htobe32(x) be32toh(x) +# define htobe64(x) be64toh(x) + +# define htole16(x) x +# define htole32(x) x +# define htole64(x) x + +# define le16toh(x) x +# define le32toh(x) x +# define le64toh(x) x +#endif + + +/******************************************************************** + ** FREEBSD ARCHITECTURES + ********************************************************************/ + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include +#endif + + +/******************************************************************** + ** APPLE ARCHITECTURES (including OS X) + ********************************************************************/ + +#if defined(__APPLE__) +# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 +# include +# define be16toh(x) OSSwapBigToHostInt16(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define be64toh(x) OSSwapBigToHostInt64(x) + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htobe64(x) OSSwapHostToBigInt64(x) + +# define htole16(x) OSSwapHostToLittleInt16(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define htole64(x) OSSwapHostToLittleInt64(x) + +# define le16toh(x) OSSwapLittleToHostInt16(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) +#endif + + +/******************************************************************** + ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING + ********************************************************************/ + +#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1 + +#include + +static inline uint16_t __orthanc_bswap16(uint16_t a) +{ + return (a << 8) | (a >> 8); +} + +static inline uint32_t __orthanc_bswap32(uint32_t a) +{ + const uint8_t* p = reinterpret_cast(&a); + return (static_cast(p[0]) << 24 | + static_cast(p[1]) << 16 | + static_cast(p[2]) << 8 | + static_cast(p[3])); +} + +static inline uint64_t __orthanc_bswap64(uint64_t a) +{ + const uint8_t* p = reinterpret_cast(&a); + return (static_cast(p[0]) << 56 | + static_cast(p[1]) << 48 | + static_cast(p[2]) << 40 | + static_cast(p[3]) << 32 | + static_cast(p[4]) << 24 | + static_cast(p[5]) << 16 | + static_cast(p[6]) << 8 | + static_cast(p[7])); +} + +#endif diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/Enumerations.cpp --- a/Orthanc/Core/Enumerations.cpp Tue May 31 12:12:59 2016 +0200 +++ b/Orthanc/Core/Enumerations.cpp Thu Jun 09 17:04:58 2016 +0200 @@ -35,6 +35,7 @@ #include "OrthancException.h" #include "Toolbox.h" +#include "Logging.h" #include #include @@ -900,6 +901,151 @@ } + ValueRepresentation StringToValueRepresentation(const std::string& vr, + bool throwIfUnsupported) + { + if (vr == "AE") + { + return ValueRepresentation_ApplicationEntity; + } + else if (vr == "AS") + { + return ValueRepresentation_AgeString; + } + else if (vr == "AT") + { + return ValueRepresentation_AttributeTag; + } + else if (vr == "CS") + { + return ValueRepresentation_CodeString; + } + else if (vr == "DA") + { + return ValueRepresentation_Date; + } + else if (vr == "DS") + { + return ValueRepresentation_DecimalString; + } + else if (vr == "DT") + { + return ValueRepresentation_DateTime; + } + else if (vr == "FL") + { + return ValueRepresentation_FloatingPointSingle; + } + else if (vr == "FD") + { + return ValueRepresentation_FloatingPointDouble; + } + else if (vr == "IS") + { + return ValueRepresentation_IntegerString; + } + else if (vr == "LO") + { + return ValueRepresentation_LongString; + } + else if (vr == "LT") + { + return ValueRepresentation_LongText; + } + else if (vr == "OB") + { + return ValueRepresentation_OtherByte; + } + else if (vr == "OD") + { + return ValueRepresentation_OtherDouble; + } + else if (vr == "OF") + { + return ValueRepresentation_OtherFloat; + } + else if (vr == "OL") + { + return ValueRepresentation_OtherLong; + } + else if (vr == "OW") + { + return ValueRepresentation_OtherWord; + } + else if (vr == "PN") + { + return ValueRepresentation_PersonName; + } + else if (vr == "SH") + { + return ValueRepresentation_ShortString; + } + else if (vr == "SL") + { + return ValueRepresentation_SignedLong; + } + else if (vr == "SQ") + { + return ValueRepresentation_Sequence; + } + else if (vr == "SS") + { + return ValueRepresentation_SignedShort; + } + else if (vr == "ST") + { + return ValueRepresentation_ShortText; + } + else if (vr == "TM") + { + return ValueRepresentation_Time; + } + else if (vr == "UC") + { + return ValueRepresentation_UnlimitedCharacters; + } + else if (vr == "UI") + { + return ValueRepresentation_UniqueIdentifier; + } + else if (vr == "UL") + { + return ValueRepresentation_UnsignedLong; + } + else if (vr == "UN") + { + return ValueRepresentation_Unknown; + } + else if (vr == "UR") + { + return ValueRepresentation_UniversalResource; + } + else if (vr == "US") + { + return ValueRepresentation_UnsignedShort; + } + else if (vr == "UT") + { + return ValueRepresentation_UnlimitedText; + } + else + { + std::string s = "Unsupported value representation encountered: " + vr; + + if (throwIfUnsupported) + { + LOG(ERROR) << s; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + LOG(INFO) << s; + return ValueRepresentation_NotSupported; + } + } + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format) @@ -1187,4 +1333,60 @@ return (type >= FileContentType_StartUser && type <= FileContentType_EndUser); } + + + bool IsBinaryValueRepresentation(ValueRepresentation vr) + { + // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html + + switch (vr) + { + case ValueRepresentation_ApplicationEntity: // AE + case ValueRepresentation_AgeString: // AS + case ValueRepresentation_CodeString: // CS + case ValueRepresentation_Date: // DA + case ValueRepresentation_DecimalString: // DS + case ValueRepresentation_DateTime: // DT + case ValueRepresentation_IntegerString: // IS + case ValueRepresentation_LongString: // LO + case ValueRepresentation_LongText: // LT + case ValueRepresentation_PersonName: // PN + case ValueRepresentation_ShortString: // SH + case ValueRepresentation_ShortText: // ST + case ValueRepresentation_Time: // TM + case ValueRepresentation_UnlimitedCharacters: // UC + case ValueRepresentation_UniqueIdentifier: // UI (UID) + case ValueRepresentation_UniversalResource: // UR (URI or URL) + case ValueRepresentation_UnlimitedText: // UT + { + return false; + } + + /** + * Below are all the VR whose character repertoire is tagged as + * "not applicable" + **/ + case ValueRepresentation_AttributeTag: // AT (2 x uint16_t) + case ValueRepresentation_FloatingPointSingle: // FL (float) + case ValueRepresentation_FloatingPointDouble: // FD (double) + case ValueRepresentation_OtherByte: // OB + case ValueRepresentation_OtherDouble: // OD + case ValueRepresentation_OtherFloat: // OF + case ValueRepresentation_OtherLong: // OL + case ValueRepresentation_OtherWord: // OW + case ValueRepresentation_SignedLong: // SL (int32_t) + case ValueRepresentation_Sequence: // SQ + case ValueRepresentation_SignedShort: // SS (int16_t) + case ValueRepresentation_UnsignedLong: // UL (uint32_t) + case ValueRepresentation_Unknown: // UN + case ValueRepresentation_UnsignedShort: // US (uint16_t) + { + return true; + } + + case ValueRepresentation_NotSupported: + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Core/Enumerations.h --- a/Orthanc/Core/Enumerations.h Tue May 31 12:12:59 2016 +0200 +++ b/Orthanc/Core/Enumerations.h Thu Jun 09 17:04:58 2016 +0200 @@ -32,6 +32,8 @@ #pragma once +#include + namespace Orthanc { enum Endianness @@ -385,6 +387,48 @@ /** + * The value representations Orthanc knows about. They correspond to + * the DICOM 2016b version of the standard. + * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html + **/ + enum ValueRepresentation + { + ValueRepresentation_ApplicationEntity = 1, // AE + ValueRepresentation_AgeString = 2, // AS + ValueRepresentation_AttributeTag = 3, // AT (2 x uint16_t) + ValueRepresentation_CodeString = 4, // CS + ValueRepresentation_Date = 5, // DA + ValueRepresentation_DecimalString = 6, // DS + ValueRepresentation_DateTime = 7, // DT + ValueRepresentation_FloatingPointSingle = 8, // FL (float) + ValueRepresentation_FloatingPointDouble = 9, // FD (double) + ValueRepresentation_IntegerString = 10, // IS + ValueRepresentation_LongString = 11, // LO + ValueRepresentation_LongText = 12, // LT + ValueRepresentation_OtherByte = 13, // OB + ValueRepresentation_OtherDouble = 14, // OD + ValueRepresentation_OtherFloat = 15, // OF + ValueRepresentation_OtherLong = 16, // OL + ValueRepresentation_OtherWord = 17, // OW + ValueRepresentation_PersonName = 18, // PN + ValueRepresentation_ShortString = 19, // SH + ValueRepresentation_SignedLong = 20, // SL (int32_t) + ValueRepresentation_Sequence = 21, // SQ + ValueRepresentation_SignedShort = 22, // SS (int16_t) + ValueRepresentation_ShortText = 23, // ST + ValueRepresentation_Time = 24, // TM + ValueRepresentation_UnlimitedCharacters = 25, // UC + ValueRepresentation_UniqueIdentifier = 26, // UI (UID) + ValueRepresentation_UnsignedLong = 27, // UL (uint32_t) + ValueRepresentation_Unknown = 28, // UN + ValueRepresentation_UniversalResource = 29, // UR (URI or URL) + ValueRepresentation_UnsignedShort = 30, // US (uint16_t) + ValueRepresentation_UnlimitedText = 31, // UT + ValueRepresentation_NotSupported // Not supported by Orthanc, or tag not in dictionary + }; + + + /** * WARNING: Do not change the explicit values in the enumerations * below this point. This would result in incompatible databases * between versions of Orthanc! @@ -459,7 +503,10 @@ ImageFormat StringToImageFormat(const char* format); - LogLevel StringToLogLevel(const char* format); + LogLevel StringToLogLevel(const char* level); + + ValueRepresentation StringToValueRepresentation(const std::string& vr, + bool throwIfUnsupported); unsigned int GetBytesPerPixel(PixelFormat format); @@ -477,4 +524,6 @@ HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error); bool IsUserContentType(FileContentType type); + + bool IsBinaryValueRepresentation(ValueRepresentation vr); } diff -r 2fffa4d0f313 -r 3251ec958a29 Orthanc/Resources/CMake/DownloadPackage.cmake --- a/Orthanc/Resources/CMake/DownloadPackage.cmake Tue May 31 12:12:59 2016 +0200 +++ b/Orthanc/Resources/CMake/DownloadPackage.cmake Thu Jun 09 17:04:58 2016 +0200 @@ -83,7 +83,9 @@ # How to silently extract files using 7-zip # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly - if (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz")) + if (("${TMP_EXTENSION}" STREQUAL "gz") OR + ("${TMP_EXTENSION}" STREQUAL "tgz") OR + ("${TMP_EXTENSION}" STREQUAL "xz")) execute_process( COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} @@ -97,8 +99,10 @@ if ("${TMP_EXTENSION}" STREQUAL "tgz") string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}") - else() + elseif ("${TMP_EXTENSION}" STREQUAL "gz") string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}") + elseif ("${TMP_EXTENSION}" STREQUAL "xz") + string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}") endif() execute_process( @@ -138,6 +142,12 @@ WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE Failure ) + elseif ("${TMP_EXTENSION}" STREQUAL "xz") + execute_process( + COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + ) else() message(FATAL_ERROR "Unknown package format.") endif() diff -r 2fffa4d0f313 -r 3251ec958a29 Plugin/Plugin.cpp --- a/Plugin/Plugin.cpp Tue May 31 12:12:59 2016 +0200 +++ b/Plugin/Plugin.cpp Thu Jun 09 17:04:58 2016 +0200 @@ -24,6 +24,7 @@ #include #include "../Orthanc/Core/OrthancException.h" +#include "../Orthanc/Core/DicomFormat/DicomMap.h" #include "ViewerToolbox.h" #include "ViewerPrefetchPolicy.h" #include "DecodedImageAdapter.h" @@ -33,7 +34,8 @@ static OrthancPluginContext* context_ = NULL; - +static bool restrictTransferSyntaxes_ = false; +static std::set enabledTransferSyntaxes_; class CacheContext @@ -323,6 +325,80 @@ } + +static bool ExtractTransferSyntax(std::string& transferSyntax, + const void* dicom, + const uint32_t size) +{ + Orthanc::DicomMap header; + if (!Orthanc::DicomMap::ParseDicomMetaInformation(header, reinterpret_cast(dicom), size)) + { + return false; + } + + const Orthanc::DicomValue* tag = header.TestAndGetValue(0x0002, 0x0010); + if (tag == NULL || + tag->IsNull() || + tag->IsBinary()) + { + return false; + } + else + { + // Stripping spaces should not be required, as this is a UI value + // representation whose stripping is supported by the Orthanc + // core, but let's be careful... + transferSyntax = Orthanc::Toolbox::StripSpaces(tag->GetContent()); + return true; + } +} + + +static bool IsTransferSyntaxEnabled(const void* dicom, + const uint32_t size) +{ + std::string formattedSize; + + { + char tmp[16]; + sprintf(tmp, "%0.1fMB", static_cast(size) / (1024.0f * 1024.0f)); + formattedSize.assign(tmp); + } + + if (!restrictTransferSyntaxes_) + { + std::string s = "Decoding one DICOM instance of " + formattedSize + " using GDCM"; + OrthancPluginLogInfo(context_, s.c_str()); + return true; + } + + std::string transferSyntax; + if (!ExtractTransferSyntax(transferSyntax, dicom, size)) + { + std::string s = ("Cannot extract the transfer syntax of this instance of " + + formattedSize + ", will use GDCM to decode it"); + OrthancPluginLogInfo(context_, s.c_str()); + return true; + } + + if (enabledTransferSyntaxes_.find(transferSyntax) != enabledTransferSyntaxes_.end()) + { + // Decoding for this transfer syntax is enabled + std::string s = ("Using GDCM to decode this instance of " + + formattedSize + " with transfer syntax " + transferSyntax); + OrthancPluginLogInfo(context_, s.c_str()); + return true; + } + else + { + std::string s = ("Won't use GDCM to decode this instance of " + + formattedSize + ", as its transfer syntax " + transferSyntax + " is disabled"); + OrthancPluginLogInfo(context_, s.c_str()); + return false; + } +} + + static OrthancPluginErrorCode DecodeImageCallback(OrthancPluginImage** target, const void* dicom, const uint32_t size, @@ -330,6 +406,12 @@ { try { + if (!IsTransferSyntaxEnabled(dicom, size)) + { + *target = NULL; + return OrthancPluginErrorCode_Success; + } + std::auto_ptr image; #if 0 @@ -364,6 +446,91 @@ } +void ParseConfiguration(bool& enableGdcm, + int& decodingThreads, + boost::filesystem::path& cachePath, + int& cacheSize) +{ + /* Read the configuration of the Web viewer */ + Json::Value configuration; + if (!OrthancPlugins::ReadConfiguration(configuration, context_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + // By default, the cache of the Web viewer is located inside the + // "StorageDirectory" of Orthanc + cachePath = OrthancPlugins::GetStringValue(configuration, "StorageDirectory", "."); + cachePath /= "WebViewerCache"; + + static const char* CONFIG_WEB_VIEWER = "WebViewer"; + if (configuration.isMember(CONFIG_WEB_VIEWER)) + { + + std::string key = "CachePath"; + if (!configuration[CONFIG_WEB_VIEWER].isMember(key)) + { + // For backward compatibility with the initial release of the Web viewer + key = "Cache"; + } + + cachePath = OrthancPlugins::GetStringValue(configuration[CONFIG_WEB_VIEWER], key, cachePath.string()); + cacheSize = OrthancPlugins::GetIntegerValue(configuration[CONFIG_WEB_VIEWER], "CacheSize", cacheSize); + decodingThreads = OrthancPlugins::GetIntegerValue(configuration[CONFIG_WEB_VIEWER], "Threads", decodingThreads); + + static const char* CONFIG_ENABLE_GDCM = "EnableGdcm"; + if (configuration[CONFIG_WEB_VIEWER].isMember(CONFIG_ENABLE_GDCM)) + { + if (configuration[CONFIG_WEB_VIEWER][CONFIG_ENABLE_GDCM].type() != Json::booleanValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + enableGdcm = configuration[CONFIG_WEB_VIEWER][CONFIG_ENABLE_GDCM].asBool(); + } + } + + static const char* CONFIG_RESTRICT_TRANSFER_SYNTAXES = "RestrictTransferSyntaxes"; + if (enableGdcm) + { + if (configuration[CONFIG_WEB_VIEWER].isMember(CONFIG_RESTRICT_TRANSFER_SYNTAXES)) + { + const Json::Value& config = configuration[CONFIG_WEB_VIEWER][CONFIG_RESTRICT_TRANSFER_SYNTAXES]; + + if (config.type() != Json::arrayValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + restrictTransferSyntaxes_ = true; + for (Json::Value::ArrayIndex i = 0; i < config.size(); i++) + { + if (config[i].type() != Json::stringValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + std::string s = "Web viewer will use GDCM to decode transfer syntax " + config[i].asString(); + enabledTransferSyntaxes_.insert(config[i].asString()); + OrthancPluginLogWarning(context_, s.c_str()); + } + } + } + } + } + + if (decodingThreads <= 0 || + cacheSize <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + +} + + + extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) @@ -400,53 +567,18 @@ /* By default, use GDCM */ bool enableGdcm = true; + /* By default, a cache of 100 MB is used */ + int cacheSize = 100; try { - /* Read the configuration of the Web viewer */ - Json::Value configuration; - if (!ReadConfiguration(configuration, context)) - { - OrthancPluginLogError(context_, "Unable to read the configuration file of Orthanc"); - return -1; - } - - // By default, the cache of the Web viewer is located inside the - // "StorageDirectory" of Orthanc - boost::filesystem::path cachePath = GetStringValue(configuration, "StorageDirectory", "."); - cachePath /= "WebViewerCache"; - int cacheSize = 100; // By default, a cache of 100 MB is used - - if (configuration.isMember("WebViewer")) - { - std::string key = "CachePath"; - if (!configuration["WebViewer"].isMember(key)) - { - // For backward compatibility with the initial release of the Web viewer - key = "Cache"; - } - - cachePath = GetStringValue(configuration["WebViewer"], key, cachePath.string()); - cacheSize = GetIntegerValue(configuration["WebViewer"], "CacheSize", cacheSize); - decodingThreads = GetIntegerValue(configuration["WebViewer"], "Threads", decodingThreads); - - if (configuration["WebViewer"].isMember("EnableGdcm") && - configuration["WebViewer"]["EnableGdcm"].type() == Json::booleanValue) - { - enableGdcm = configuration["WebViewer"]["EnableGdcm"].asBool(); - } - } + boost::filesystem::path cachePath; + ParseConfiguration(enableGdcm, decodingThreads, cachePath, cacheSize); std::string message = ("Web viewer using " + boost::lexical_cast(decodingThreads) + " threads for the decoding of the DICOM images"); OrthancPluginLogWarning(context_, message.c_str()); - if (decodingThreads <= 0 || - cacheSize <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - message = "Storing the cache of the Web viewer in folder: " + cachePath.string(); OrthancPluginLogWarning(context_, message.c_str()); @@ -515,7 +647,14 @@ } catch (Orthanc::OrthancException& e) { - OrthancPluginLogError(context_, e.What()); + if (e.GetErrorCode() == Orthanc::ErrorCode_BadFileFormat) + { + OrthancPluginLogError(context_, "Unable to read the configuration of the Web viewer plugin"); + } + else + { + OrthancPluginLogError(context_, e.What()); + } return -1; } diff -r 2fffa4d0f313 -r 3251ec958a29 Resources/SyncOrthancFolder.py --- a/Resources/SyncOrthancFolder.py Tue May 31 12:12:59 2016 +0200 +++ b/Resources/SyncOrthancFolder.py Thu Jun 09 17:04:58 2016 +0200 @@ -19,6 +19,13 @@ 'Core/ChunkedBuffer.h', 'Core/Enumerations.cpp', 'Core/Enumerations.h', + 'Core/Endianness.h', + 'Core/DicomFormat/DicomMap.h', + 'Core/DicomFormat/DicomMap.cpp', + 'Core/DicomFormat/DicomTag.h', + 'Core/DicomFormat/DicomTag.cpp', + 'Core/DicomFormat/DicomValue.h', + 'Core/DicomFormat/DicomValue.cpp', 'Core/FileStorage/FilesystemStorage.cpp', 'Core/FileStorage/FilesystemStorage.h', 'Core/FileStorage/IStorageArea.h',