# HG changeset patch # User Sebastien Jodogne # Date 1521825201 -3600 # Node ID 83b8b67435311b70b576623a2ecdd786c1e0c04d # Parent 0188c21e417aee3ecec6ababc905e5acb634b99f ITagVisitor - for anonymization relationships diff -r 0188c21e417a -r 83b8b6743531 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/DicomFormat/DicomTag.h Fri Mar 23 18:13:21 2018 +0100 @@ -123,6 +123,8 @@ 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); + static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); + static const DicomTag DICOM_TAG_FRAME_OF_REFERENCE_UID(0x0020, 0x0052); // DICOM tags used for fMRI (thanks to Will Ryder) static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); diff -r 0188c21e417a -r 83b8b6743531 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Fri Mar 23 18:13:21 2018 +0100 @@ -667,11 +667,11 @@ return new DicomValue; } } - catch (boost::bad_lexical_cast) + catch (boost::bad_lexical_cast&) { return new DicomValue; } - catch (std::bad_cast) + catch (std::bad_cast&) { return new DicomValue; } @@ -2096,4 +2096,322 @@ DJDecoderRegistration::cleanup(); #endif } + + + + // Forward declaration + static void ApplyVisitorToElement(DcmElement& element, + ITagVisitor& visitor, + const std::vector& parentTags, + const std::vector& parentIndexes, + Encoding encoding); + + static void ApplyVisitorToDataset(DcmItem& dataset, + ITagVisitor& visitor, + const std::vector& parentTags, + const std::vector& parentIndexes, + Encoding encoding) + { + assert(parentTags.size() == parentIndexes.size()); + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding); + } + } + } + + + static void ApplyVisitorToLeaf(DcmElement& element, + ITagVisitor& visitor, + const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + Encoding encoding) + { + // TODO - Merge this function with ConvertLeafElement() + + assert(element.isLeaf()); + + DcmEVR evr = element.getTag().getEVR(); + ValueRepresentation vr = FromDcmtkBridge::Convert(evr); + + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) + { + std::string utf8; + + if (c != NULL) // This case corresponds to the empty string + { + std::string s(c); + utf8 = Toolbox::ConvertToUtf8(s, encoding); + } + + std::string newValue; + ITagVisitor::Action action = visitor.VisitString + (newValue, parentTags, parentIndexes, tag, vr, utf8); + + switch (action) + { + case ITagVisitor::Action_None: + break; + + case ITagVisitor::Action_Replace: + { + std::string s = Toolbox::ConvertFromUtf8(newValue, encoding); + if (element.putString(s.c_str(), s.size()) != EC_Normal) + { + LOG(ERROR) << "Cannot replace value of tag: " << tag.Format(); + throw OrthancException(ErrorCode_InternalError); + } + + break; + } + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return; // We're done + } + + + try + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + switch (element.getVR()) + { + + /** + * Deal with binary data (including PixelData). + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_UN: // unknown value representation + case EVR_ox: // OB or OW depending on context + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + Uint8* data = NULL; + + if (element.getUint8Array(data) == EC_Normal) + { + visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength()); + } + else + { + visitor.VisitUnknown(parentTags, parentIndexes, tag, vr); + } + + break; + } + + /** + * Numeric types + **/ + + case EVR_SL: // signed long + { + Sint32 f; + if (dynamic_cast(element).getSint32(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_SS: // signed short + { + Sint16 f; + if (dynamic_cast(element).getSint16(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_UL: // unsigned long + { + Uint32 f; + if (dynamic_cast(element).getUint32(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_US: // unsigned short + { + Uint16 f; + if (dynamic_cast(element).getUint16(f).good()) + { + visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_FL: // float single-precision + { + Float32 f; + if (dynamic_cast(element).getFloat32(f).good()) + { + visitor.VisitDouble(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + case EVR_FD: // float double-precision + { + Float64 f; + if (dynamic_cast(element).getFloat64(f).good()) + { + visitor.VisitDouble(parentTags, parentIndexes, tag, vr, f); + } + + break; + } + + + /** + * Attribute tag. + **/ + + case EVR_AT: + { + DcmTagKey tagKey; + if (dynamic_cast(element).getTagVal(tagKey, 0).good()) + { + DicomTag t(tagKey.getGroup(), tagKey.getElement()); + visitor.VisitAttribute(parentTags, parentIndexes, tag, vr, t); + } + + break; + } + + + /** + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ + + case EVR_SQ: // sequence of items + return; + + + /** + * Internal to DCMTK. + **/ + + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + visitor.VisitUnknown(parentTags, parentIndexes, tag, vr); + return; + + + /** + * Default case. + **/ + + default: + return; + } + } + catch (boost::bad_lexical_cast&) + { + return; + } + catch (std::bad_cast&) + { + return; + } + } + + + static void ApplyVisitorToElement(DcmElement& element, + ITagVisitor& visitor, + const std::vector& parentTags, + const std::vector& parentIndexes, + Encoding encoding) + { + assert(parentTags.size() == parentIndexes.size()); + + DicomTag tag(FromDcmtkBridge::Convert(element.getTag())); + + if (element.isLeaf()) + { + ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding); + } + else + { + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast(element); + + std::vector tags = parentTags; + std::vector indexes = parentIndexes; + tags.push_back(tag); + indexes.push_back(0); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + indexes.back() = static_cast(i); + DcmItem* child = sequence.getItem(i); + ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding); + } + } + } + + + void FromDcmtkBridge::Apply(DcmItem& dataset, + ITagVisitor& visitor, + Encoding defaultEncoding) + { + std::vector parentTags; + std::vector parentIndexes; + Encoding encoding = DetectEncoding(dataset, defaultEncoding); + ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding); + } } diff -r 0188c21e417a -r 83b8b6743531 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.h Fri Mar 23 18:13:21 2018 +0100 @@ -33,6 +33,7 @@ #pragma once +#include "ITagVisitor.h" #include "../DicomFormat/DicomElement.h" #include "../DicomFormat/DicomMap.h" @@ -240,5 +241,9 @@ static void InitializeCodecs(); static void FinalizeCodecs(); + + static void Apply(DcmItem& dataset, + ITagVisitor& visitor, + Encoding defaultEncoding); }; } diff -r 0188c21e417a -r 83b8b6743531 Core/DicomParsing/ITagVisitor.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ITagVisitor.h Fri Mar 23 18:13:21 2018 +0100 @@ -0,0 +1,93 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., 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 "../DicomFormat/DicomTag.h" + +#include +#include + +namespace Orthanc +{ + class ITagVisitor : public boost::noncopyable + { + public: + enum Action + { + Action_Replace, + Action_None + }; + + virtual ~ITagVisitor() + { + } + + virtual void VisitUnknown(const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr) = 0; + + virtual void VisitBinary(const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const void* data, + size_t size) = 0; + + virtual void VisitInteger(const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + int64_t value) = 0; + + virtual void VisitDouble(const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + double value) = 0; + + virtual void VisitAttribute(const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const DicomTag& value) = 0; + + virtual Action VisitString(std::string& newValue, + const std::vector& parentTags, + const std::vector& parentIndexes, + const DicomTag& tag, + ValueRepresentation vr, + const std::string& value) = 0; + }; +} diff -r 0188c21e417a -r 83b8b6743531 Core/DicomParsing/ParsedDicomFile.cpp --- a/Core/DicomParsing/ParsedDicomFile.cpp Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Fri Mar 23 18:13:21 2018 +0100 @@ -1520,4 +1520,10 @@ return false; } } + + + void ParsedDicomFile::Apply(ITagVisitor& visitor) + { + FromDcmtkBridge::Apply(*pimpl_->file_->getDataset(), visitor, GetDefaultDicomEncoding()); + } } diff -r 0188c21e417a -r 83b8b6743531 Core/DicomParsing/ParsedDicomFile.h --- a/Core/DicomParsing/ParsedDicomFile.h Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/DicomParsing/ParsedDicomFile.h Fri Mar 23 18:13:21 2018 +0100 @@ -49,6 +49,7 @@ # error Macro ORTHANC_ENABLE_MONGOOSE must be defined to use this file #endif +#include "ITagVisitor.h" #include "../DicomFormat/DicomInstanceHasher.h" #include "../Images/ImageAccessor.h" #include "../IDynamicObject.h" @@ -220,5 +221,7 @@ bool LookupTransferSyntax(std::string& result); bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; + + void Apply(ITagVisitor& visitor); }; } diff -r 0188c21e417a -r 83b8b6743531 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/Enumerations.cpp Fri Mar 23 18:13:21 2018 +0100 @@ -878,6 +878,112 @@ } + const char* EnumerationToString(ValueRepresentation vr) + { + switch (vr) + { + case ValueRepresentation_ApplicationEntity: // AE + return "AE"; + + case ValueRepresentation_AgeString: // AS + return "AS"; + + case ValueRepresentation_AttributeTag: // AT (2 x uint16_t) + return "AT"; + + case ValueRepresentation_CodeString: // CS + return "CS"; + + case ValueRepresentation_Date: // DA + return "DA"; + + case ValueRepresentation_DecimalString: // DS + return "DS"; + + case ValueRepresentation_DateTime: // DT + return "DT"; + + case ValueRepresentation_FloatingPointSingle: // FL (float) + return "FL"; + + case ValueRepresentation_FloatingPointDouble: // FD (double) + return "FD"; + + case ValueRepresentation_IntegerString: // IS + return "IS"; + + case ValueRepresentation_LongString: // LO + return "LO"; + + case ValueRepresentation_LongText: // LT + return "LT"; + + case ValueRepresentation_OtherByte: // OB + return "OB"; + + case ValueRepresentation_OtherDouble: // OD + return "OD"; + + case ValueRepresentation_OtherFloat: // OF + return "OF"; + + case ValueRepresentation_OtherLong: // OL + return "OL"; + + case ValueRepresentation_OtherWord: // OW + return "OW"; + + case ValueRepresentation_PersonName: // PN + return "PN"; + + case ValueRepresentation_ShortString: // SH + return "SH"; + + case ValueRepresentation_SignedLong: // SL (int32_t) + return "SL"; + + case ValueRepresentation_Sequence: // SQ + return "SQ"; + + case ValueRepresentation_SignedShort: // SS (int16_t) + return "SS"; + + case ValueRepresentation_ShortText: // ST + return "ST"; + + case ValueRepresentation_Time: // TM + return "TM"; + + case ValueRepresentation_UnlimitedCharacters: // UC + return "UC"; + + case ValueRepresentation_UniqueIdentifier: // UI (UID) + return "UI"; + + case ValueRepresentation_UnsignedLong: // UL (uint32_t) + return "UL"; + + case ValueRepresentation_Unknown: // UN + return "UN"; + + case ValueRepresentation_UniversalResource: // UR (URI or URL) + return "UR"; + + case ValueRepresentation_UnsignedShort: // US (uint16_t) + return "US"; + + case ValueRepresentation_UnlimitedText: // UT + return "UT"; + + case ValueRepresentation_NotSupported: + return "Not supported"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); diff -r 0188c21e417a -r 83b8b6743531 Core/Enumerations.h --- a/Core/Enumerations.h Thu Mar 22 17:31:36 2018 +0100 +++ b/Core/Enumerations.h Fri Mar 23 18:13:21 2018 +0100 @@ -620,6 +620,8 @@ const char* EnumerationToString(DicomVersion version); + const char* EnumerationToString(ValueRepresentation vr); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); diff -r 0188c21e417a -r 83b8b6743531 UnitTestsSources/UnitTestsMain.cpp --- a/UnitTestsSources/UnitTestsMain.cpp Thu Mar 22 17:31:36 2018 +0100 +++ b/UnitTestsSources/UnitTestsMain.cpp Fri Mar 23 18:13:21 2018 +0100 @@ -668,6 +668,15 @@ ASSERT_EQ(DicomVersion_2008, StringToDicomVersion(EnumerationToString(DicomVersion_2008))); ASSERT_EQ(DicomVersion_2017c, StringToDicomVersion(EnumerationToString(DicomVersion_2017c))); + + for (int i = static_cast(ValueRepresentation_ApplicationEntity); + i < static_cast(ValueRepresentation_NotSupported); i += 1) + { + ValueRepresentation vr = static_cast(i); + ASSERT_EQ(vr, StringToValueRepresentation(EnumerationToString(vr), true)); + } + + ASSERT_THROW(StringToValueRepresentation("nope", true), OrthancException); }