changeset 2499:83b8b6743531

ITagVisitor - for anonymization relationships
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 23 Mar 2018 18:13:21 +0100
parents 0188c21e417a
children 58e4a48c322c
files Core/DicomFormat/DicomTag.h Core/DicomParsing/FromDcmtkBridge.cpp Core/DicomParsing/FromDcmtkBridge.h Core/DicomParsing/ITagVisitor.h Core/DicomParsing/ParsedDicomFile.cpp Core/DicomParsing/ParsedDicomFile.h Core/Enumerations.cpp Core/Enumerations.h UnitTestsSources/UnitTestsMain.cpp
diffstat 9 files changed, 546 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- 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);
--- 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<DicomTag>& parentTags,
+                                    const std::vector<size_t>& parentIndexes,
+                                    Encoding encoding);
+ 
+  static void ApplyVisitorToDataset(DcmItem& dataset,
+                                    ITagVisitor& visitor,
+                                    const std::vector<DicomTag>& parentTags,
+                                    const std::vector<size_t>& 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<DicomTag>& parentTags,
+                                 const std::vector<size_t>& 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<DcmSignedLong&>(element).getSint32(f).good())
+          {
+            visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f);
+          }
+
+          break;
+        }
+
+        case EVR_SS:  // signed short
+        {
+          Sint16 f;
+          if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good())
+          {
+            visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f);
+          }
+
+          break;
+        }
+
+        case EVR_UL:  // unsigned long
+        {
+          Uint32 f;
+          if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good())
+          {
+            visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f);
+          }
+
+          break;
+        }
+
+        case EVR_US:  // unsigned short
+        {
+          Uint16 f;
+          if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good())
+          {
+            visitor.VisitInteger(parentTags, parentIndexes, tag, vr, f);
+          }
+
+          break;
+        }
+
+        case EVR_FL:  // float single-precision
+        {
+          Float32 f;
+          if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good())
+          {
+            visitor.VisitDouble(parentTags, parentIndexes, tag, vr, f);
+          }
+
+          break;
+        }
+
+        case EVR_FD:  // float double-precision
+        {
+          Float64 f;
+          if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good())
+          {
+            visitor.VisitDouble(parentTags, parentIndexes, tag, vr, f);
+          }
+
+          break;
+        }
+
+
+        /**
+         * Attribute tag.
+         **/
+
+        case EVR_AT:
+        {
+          DcmTagKey tagKey;
+          if (dynamic_cast<DcmAttributeTag&>(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<DicomTag>& parentTags,
+                                    const std::vector<size_t>& 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<DcmSequenceOfItems&>(element);
+
+      std::vector<DicomTag> tags = parentTags;
+      std::vector<size_t> indexes = parentIndexes;
+      tags.push_back(tag);
+      indexes.push_back(0);
+
+      for (unsigned long i = 0; i < sequence.card(); i++)
+      {
+        indexes.back() = static_cast<size_t>(i);
+        DcmItem* child = sequence.getItem(i);
+        ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding);
+      }
+    }
+  }
+
+
+  void FromDcmtkBridge::Apply(DcmItem& dataset,
+                              ITagVisitor& visitor,
+                              Encoding defaultEncoding)
+  {
+    std::vector<DicomTag> parentTags;
+    std::vector<size_t> parentIndexes;
+    Encoding encoding = DetectEncoding(dataset, defaultEncoding);
+    ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding);
+  }
 }
--- 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);
   };
 }
--- /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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../DicomFormat/DicomTag.h"
+
+#include <vector>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ITagVisitor : public boost::noncopyable
+  {
+  public:
+    enum Action
+    {
+      Action_Replace,
+      Action_None
+    };
+
+    virtual ~ITagVisitor()
+    {
+    }
+
+    virtual void VisitUnknown(const std::vector<DicomTag>& parentTags,
+                              const std::vector<size_t>& parentIndexes,
+                              const DicomTag& tag,
+                              ValueRepresentation vr) = 0;
+
+    virtual void VisitBinary(const std::vector<DicomTag>& parentTags,
+                             const std::vector<size_t>& parentIndexes,
+                             const DicomTag& tag,
+                             ValueRepresentation vr,
+                             const void* data,
+                             size_t size) = 0;
+
+    virtual void VisitInteger(const std::vector<DicomTag>& parentTags,
+                              const std::vector<size_t>& parentIndexes,
+                              const DicomTag& tag,
+                              ValueRepresentation vr,
+                              int64_t value) = 0;
+
+    virtual void VisitDouble(const std::vector<DicomTag>& parentTags,
+                             const std::vector<size_t>& parentIndexes,
+                             const DicomTag& tag,
+                             ValueRepresentation vr,
+                             double value) = 0;
+
+    virtual void VisitAttribute(const std::vector<DicomTag>& parentTags,
+                                const std::vector<size_t>& parentIndexes,
+                                const DicomTag& tag,
+                                ValueRepresentation vr,
+                                const DicomTag& value) = 0;
+
+    virtual Action VisitString(std::string& newValue,
+                               const std::vector<DicomTag>& parentTags,
+                               const std::vector<size_t>& parentIndexes,
+                               const DicomTag& tag,
+                               ValueRepresentation vr,
+                               const std::string& value) = 0;
+  };
+}
--- 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());
+  }
 }
--- 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);
   };
 }
--- 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);
--- 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);
--- 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<int>(ValueRepresentation_ApplicationEntity);
+       i < static_cast<int>(ValueRepresentation_NotSupported); i += 1)
+  {
+    ValueRepresentation vr = static_cast<ValueRepresentation>(i);
+    ASSERT_EQ(vr, StringToValueRepresentation(EnumerationToString(vr), true));
+  }
+
+  ASSERT_THROW(StringToValueRepresentation("nope", true), OrthancException);
 }