changeset 402:0c5eb3253019

making the DICOM instances valid by default
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 05 Nov 2025 11:27:34 +0100
parents 1be37c2e96c2
children 330cd7f020e2
files Applications/Dicomizer.cpp Framework/DicomToolbox.cpp Framework/DicomToolbox.h NEWS Resources/SampleDataset.json
diffstat 5 files changed, 122 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Dicomizer.cpp	Tue Nov 04 19:21:58 2025 +0100
+++ b/Applications/Dicomizer.cpp	Wed Nov 05 11:27:34 2025 +0100
@@ -353,7 +353,7 @@
   }
 
   std::unique_ptr<DcmDataset> dataset(Orthanc::FromDcmtkBridge::FromJson(json, true, true, Orthanc::Encoding_Latin1,
-                                                                       "" /* no private tag, thus no private creator */));
+                                                                         "" /* no private tag, thus no private creator */));
   if (dataset.get() == NULL)
   {
     LOG(ERROR) << "Cannot convert to JSON file to a DICOM dataset: " << path;
@@ -383,6 +383,76 @@
   OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ContentTime, time);
   OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_AcquisitionDateTime, date + time);
 
+  // Write version information
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_Manufacturer, "Orthanc");
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ManufacturerModelName, "OrthancWSIDicomizer");
+
+  {
+    const std::string version = "OrthancWSIDicomizer-" + std::string(ORTHANC_WSI_VERSION);
+    OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_DeviceSerialNumber, version);
+    OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_SoftwareVersions, version);
+  }
+
+  // Write the tags that are needed for a valid DICOM instance
+  OrthancWSI::DicomToolbox::SetStringTag(*dataset, DCM_ImageType, "DERIVED\\PRIMARY\\VOLUME\\NONE");
+
+  OrthancWSI::DicomToolbox::SetStringTagWithWarning(*dataset, DCM_BurnedInAnnotation, "NO");
+  OrthancWSI::DicomToolbox::SetStringTagWithWarning(*dataset, DCM_ExtendedDepthOfField, "NO");
+  OrthancWSI::DicomToolbox::SetStringTagWithWarning(*dataset, DCM_FocusMethod, "AUTO");
+  OrthancWSI::DicomToolbox::SetStringTagWithWarning(*dataset, DCM_SpecimenLabelInImage, "NO");
+  OrthancWSI::DicomToolbox::SetStringTagWithWarning(*dataset, DCM_ContainerIdentifier, "MY_CONTAINER");
+
+  {
+    std::set<DcmTagKey> tagsStringType2;
+    tagsStringType2.insert(DCM_AccessionNumber);
+    tagsStringType2.insert(DCM_PatientBirthDate);
+    tagsStringType2.insert(DCM_PatientName);
+    tagsStringType2.insert(DCM_PatientSex);
+    tagsStringType2.insert(DCM_ReferringPhysicianName);
+    tagsStringType2.insert(DCM_SeriesNumber);
+    tagsStringType2.insert(DCM_StudyID);
+
+    for (std::set<DcmTagKey>::const_iterator it = tagsStringType2.begin(); it != tagsStringType2.end(); ++it)
+    {
+      OrthancWSI::DicomToolbox::SetStringTag(*dataset, *it, "");
+    }
+  }
+
+  {
+    std::set<DcmTagKey> tagsSequenceType2;
+    tagsSequenceType2.insert(DCM_AcquisitionContextSequence);
+    tagsSequenceType2.insert(DCM_ContainerTypeCodeSequence);
+    tagsSequenceType2.insert(DCM_IssuerOfTheContainerIdentifierSequence);
+
+    for (std::set<DcmTagKey>::const_iterator it = tagsSequenceType2.begin(); it != tagsSequenceType2.end(); ++it)
+    {
+      std::unique_ptr<DcmElement> empty(new DcmSequenceOfItems(*it));
+
+      if (!dataset->tagExists(*it) &&
+          !dataset->insert(empty.release(), true /* replace */, false).good())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+  }
+
+  if (!dataset->tagExists(DCM_SpecimenDescriptionSequence))
+  {
+    std::unique_ptr<DcmItem> item(new DcmItem);
+    OrthancWSI::DicomToolbox::SetStringTagWithWarning(*item, DCM_SpecimenIdentifier, "MY_SPECIMEN");
+    OrthancWSI::DicomToolbox::SetStringTagWithWarning(*item, DCM_SpecimenUID, Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance));
+
+    std::unique_ptr<DcmSequenceOfItems> specimen(new DcmSequenceOfItems(DCM_SpecimenDescriptionSequence));
+
+    if (!item->insert(new DcmSequenceOfItems(DCM_IssuerOfTheSpecimenIdentifierSequence), false, false).good() ||
+        !item->insert(new DcmSequenceOfItems(DCM_SpecimenPreparationSequence), false, false).good() ||
+        !specimen->insert(item.release(), false, false).good() ||
+        !dataset->insert(specimen.release(), true /* replace */, false).good())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
   return dataset.release();
 }
 
@@ -406,6 +476,8 @@
   {
     // No Dimension Organization provided: Generate an unique identifier
     organization = Orthanc::FromDcmtkBridge::GenerateUniqueIdentifier(Orthanc::ResourceType_Instance);
+    LOG(WARNING) << "Generating an unique identifier for the tags \""
+                 << OrthancWSI::DicomToolbox::GetTagName(DCM_DimensionOrganizationUID) << "\": \"" << organization << "\"";
   }
 
 
@@ -451,13 +523,19 @@
     // Construct tag "Shared Functional Groups Sequence" (5200,9229)
     std::unique_ptr<DcmItem> item(new DcmItem);
 
+    std::unique_ptr<DcmItem> item2(new DcmItem);
+    OrthancWSI::DicomToolbox::SetStringTag(*item2, DCM_OpticalPathIdentifier, opticalPathId);
+
     std::unique_ptr<DcmItem> item3(new DcmItem);
-    OrthancWSI::DicomToolbox::SetStringTag(*item3, DCM_OpticalPathIdentifier, opticalPathId);
+    OrthancWSI::DicomToolbox::SetStringTag(*item3, DCM_FrameType, "DERIVED\\PRIMARY\\VOLUME\\NONE");
 
     std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_SharedFunctionalGroupsSequence));
-    std::unique_ptr<DcmSequenceOfItems> sequence3(new DcmSequenceOfItems(DCM_OpticalPathIdentificationSequence));
+    std::unique_ptr<DcmSequenceOfItems> sequence2(new DcmSequenceOfItems(DCM_OpticalPathIdentificationSequence));
+    std::unique_ptr<DcmSequenceOfItems> sequence3(new DcmSequenceOfItems(DCM_WholeSlideMicroscopyImageFrameTypeSequence));
 
-    if (!sequence3->insert(item3.release(), false, false).good() ||
+    if (!sequence2->insert(item2.release(), false, false).good() ||
+        !sequence3->insert(item3.release(), false, false).good() ||
+        !item->insert(sequence2.release(), false, false).good() ||
         !item->insert(sequence3.release(), false, false).good() ||
         !sequence->insert(item.release(), false, false).good() ||
         !dataset.insert(sequence.release(), true /* replace */, false).good())
--- a/Framework/DicomToolbox.cpp	Tue Nov 04 19:21:58 2025 +0100
+++ b/Framework/DicomToolbox.cpp	Wed Nov 05 11:27:34 2025 +0100
@@ -41,6 +41,12 @@
   namespace DicomToolbox
   {
 #if ORTHANC_ENABLE_DCMTK == 1
+    std::string GetTagName(const DcmTagKey& key)
+    {
+      DcmTag tmp(key);
+      return tmp.getTagName();
+    }
+
     void SetStringTag(DcmItem& dataset,
                       const DcmTagKey& key,
                       const std::string& value)
@@ -52,6 +58,21 @@
       }
     }
 
+    void SetStringTagWithWarning(DcmItem& dataset,
+                                 const DcmTagKey& key,
+                                 const std::string& value)
+    {
+      if (!dataset.tagExists(key))
+      {
+        LOG(WARNING) << "Setting value of tag \"" << GetTagName(key) << "\" to placeholder value: \"" << value << "\"";
+
+        if (!dataset.putAndInsertString(key, value.c_str()).good())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+      }
+    }
+
     void SetUint16Tag(DcmItem& dataset,
                       const DcmTagKey& key,
                       uint16_t value)
--- a/Framework/DicomToolbox.h	Tue Nov 04 19:21:58 2025 +0100
+++ b/Framework/DicomToolbox.h	Wed Nov 05 11:27:34 2025 +0100
@@ -38,10 +38,16 @@
   namespace DicomToolbox
   {
 #if ORTHANC_ENABLE_DCMTK == 1
+    std::string GetTagName(const DcmTagKey& key);
+
     void SetStringTag(DcmItem& dataset,
                       const DcmTagKey& key,
                       const std::string& value);
 
+    void SetStringTagWithWarning(DcmItem& dataset,
+                                 const DcmTagKey& key,
+                                 const std::string& value);
+
     void SetUint16Tag(DcmItem& dataset,
                       const DcmTagKey& key,
                       uint16_t value);
--- a/NEWS	Tue Nov 04 19:21:58 2025 +0100
+++ b/NEWS	Wed Nov 05 11:27:34 2025 +0100
@@ -1,9 +1,13 @@
 Pending changes in the mainline
 ===============================
 
-* Added rotation button in the viewer
-* The viewer displays a label if the "description" GET parameter is provided
-* Upgraded to OpenLayers 10.6.1
+* OrthancWSIDicomizer:
+  - Placeholder tags are now automatically inserted when the "--dataset" option
+    provides incomplete data, ensuring the generated DICOM instances remain valid
+* Viewer plugin:
+  - Added rotation button in the viewer
+  - The viewer displays a label if the "description" GET parameter is provided
+  - Upgraded to OpenLayers 10.6.1
 
 
 Version 3.2 (2025-04-08)
--- a/Resources/SampleDataset.json	Tue Nov 04 19:21:58 2025 +0100
+++ b/Resources/SampleDataset.json	Wed Nov 05 11:27:34 2025 +0100
@@ -7,12 +7,7 @@
   "SeriesNumber" : "1",
   "ReferringPhysicianName" : "SOME^PHYSICIAN",
   "AccessionNumber" : "123456789",
-  "Manufacturer" : "MyManufacturer",
-  "ManufacturerModelName" : "MyModel",
-  "DeviceSerialNumber" : "MySerialNumber",
-  "SoftwareVersions" : "MyVersion",
 
-  "ImageType" : "DERIVED\\PRIMARY\\VOLUME\\NONE",
   "FocusMethod" : "AUTO",
   "ExtendedDepthOfField" : "NO",
 
@@ -38,6 +33,12 @@
       ]
     }
   ],
+  "DimensionOrganizationSequence": [
+    {
+      "DimensionOrganizationUID" : "1.2.276.0.7230010.3.1.4.1757367822.227496.1762338018.13870"
+    }
+  ],
+
   "SpecimenLabelInImage" : "NO",
   "BurnedInAnnotation" : "NO"
 }