changeset 5348:303e930fff0f

"/tools/create-dicom" can now be used to create Encapsulated 3D Manufacturing Model IODs (MTL, OBJ, or STL)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 02 Jul 2023 16:24:56 +0200
parents 4f686f6150c7
children 0223315871e8
files NEWS OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/Sources/SystemToolbox.cpp OrthancFramework/UnitTestsSources/FrameworkTests.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp
diffstat 8 files changed, 109 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Jun 29 09:43:41 2023 +0200
+++ b/NEWS	Sun Jul 02 16:24:56 2023 +0200
@@ -14,6 +14,8 @@
 --------
 
 * API version upgraded to 21
+* "/tools/create-dicom" can now be used to create Encapsulated 3D
+  Manufacturing Model IODs (MTL, OBJ, or STL)
 * Added a route to delete the output of an asynchronous job (right now
   only for archive jobs): e.g. DELETE /jobs/../archive
 
@@ -29,10 +31,10 @@
 * Fix decoding of YBR_FULL RLE images for which the "Planar Configuration" 
   tag (0028,0006) equals 1
 * Made Orthanc more resilient to common spelling errors in SpecificCharacterSet
-* Modality worklists plugin: allow searching on private tags (exact match only)
+* Modality worklists plugin: Allow searching on private tags (exact match only)
 * Fix orphan files remaining in storage when working with MaximumStorageSize
   (https://discourse.orthanc-server.org/t/issue-with-deleting-incoming-dicoms-when-maximumstoragesize-is-reached/3510)
-* When deleting a resource, its parents LastUpdate metadata are now updated
+* When deleting a resource, the "LastUpdate" metadata of its parents are now updated
 * Reduced the memory usage when downloading archives when "ZipLoaderThreads" > 0
 * Metrics can be stored either as floating-point numbers, or as integers
 * Upgraded dependencies for static builds:
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sun Jul 02 16:24:56 2023 +0200
@@ -1203,7 +1203,42 @@
         break;
 
       case MimeType_Pdf:
-        EmbedPdf(content);
+      {
+        if (content.size() < 5 ||  // (*)
+            strncmp("%PDF-", content.c_str(), 5) != 0)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file");
+        }
+        
+        EncapsulateDocument(MimeType_Pdf, content);
+
+        // In Orthanc <= 1.9.7, the "Modality" would have always be overwritten as "OT"
+        // https://groups.google.com/g/orthanc-users/c/eNSddNrQDtM/m/wc1HahimAAAJ
+
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
+        SetIfAbsent(DICOM_TAG_MODALITY, "OT");
+        SetIfAbsent(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
+        //SetIfAbsent(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+
+        break;
+      }
+
+      case MimeType_Mtl:
+        EncapsulateDocument(mime, content);
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.104.5");
+        SetIfAbsent(DICOM_TAG_MODALITY, "M3D");
+        break;
+
+      case MimeType_Obj:
+        EncapsulateDocument(mime, content);
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.104.4");
+        SetIfAbsent(DICOM_TAG_MODALITY, "M3D");
+        break;
+
+      case MimeType_Stl:
+        EncapsulateDocument(mime, content);
+        SetIfAbsent(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.104.3");
+        SetIfAbsent(DICOM_TAG_MODALITY, "M3D");
         break;
 
       default:
@@ -1526,28 +1561,16 @@
   }
 
 
-  void ParsedDicomFile::EmbedPdf(const std::string& pdf)
+  void ParsedDicomFile::EncapsulateDocument(MimeType mime,
+                                            const std::string& document)
   {
-    if (pdf.size() < 5 ||  // (*)
-        strncmp("%PDF-", pdf.c_str(), 5) != 0)
-    {
-      throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file");
-    }
-
     InvalidateCache();
 
-    // In Orthanc <= 1.9.7, the "Modality" would have always be overwritten as "OT"
-    // https://groups.google.com/g/orthanc-users/c/eNSddNrQDtM/m/wc1HahimAAAJ
-    
-    ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
-    SetIfAbsent(FromDcmtkBridge::Convert(DCM_Modality), "OT");
-    SetIfAbsent(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
-    SetIfAbsent(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF);
-    //SetIfAbsent(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+    ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), EnumerationToString(mime));
 
     std::unique_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
 
-    size_t s = pdf.size();
+    size_t s = document.size();
     if (s & 1)
     {
       // The size of the buffer must be even
@@ -1561,10 +1584,12 @@
       throw OrthancException(ErrorCode_NotEnoughMemory);
     }
 
-    // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
-    bytes[s - 1] = 0;
+    if (s > 0)
+    {
+      bytes[s - 1] = 0;
+    }
 
-    memcpy(bytes, pdf.c_str(), pdf.size());
+    memcpy(bytes, document.c_str(), document.size());
       
     DcmPolymorphOBOW* obj = element.release();
     result = GetDcmtkObject().getDataset()->insert(obj);
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Sun Jul 02 16:24:56 2023 +0200
@@ -97,6 +97,9 @@
 
     bool EmbedContentInternal(const std::string& dataUriScheme);
 
+    void EncapsulateDocument(MimeType mime,
+                             const std::string& document);
+
     // For internal use only, in order to provide const-correctness on
     // the top of DCMTK API
     DcmFileFormat& GetDcmtkObjectConst() const;
@@ -205,6 +208,7 @@
     void SaveToFile(const std::string& path);
 #endif
 
+    // This method must only be used on the PixelData and EncapsulatedDocument tags
     void EmbedContent(const std::string& dataUriScheme);
 
     void EmbedImage(const ImageAccessor& accessor);
@@ -236,8 +240,6 @@
 
     bool HasTag(const DicomTag& tag) const;
 
-    void EmbedPdf(const std::string& pdf);
-
     bool ExtractPdf(std::string& pdf) const;
 
     void GetRawFrame(std::string& target, // OUT
--- a/OrthancFramework/Sources/Enumerations.cpp	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Sun Jul 02 16:24:56 2023 +0200
@@ -1111,6 +1111,15 @@
       case MimeType_Ico:
         return MIME_ICO;
 
+      case MimeType_Obj:
+        return MIME_OBJ;
+
+      case MimeType_Mtl:
+        return MIME_MTL;
+
+      case MimeType_Stl:
+        return MIME_STL;
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -1853,6 +1862,21 @@
       target = MimeType_Ico;
       return true;
     }
+    else if (source == MIME_OBJ)
+    {
+      target = MimeType_Obj;
+      return true;
+    }
+    else if (source == MIME_MTL)
+    {
+      target = MimeType_Mtl;
+      return true;
+    }
+    else if (source == MIME_STL)
+    {
+      target = MimeType_Stl;
+      return true;
+    }
     else
     {
       return false;
--- a/OrthancFramework/Sources/Enumerations.h	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Sun Jul 02 16:24:56 2023 +0200
@@ -42,6 +42,11 @@
   static const char* const MIME_XML = "application/xml";
   static const char* const MIME_XML_UTF8 = "application/xml; charset=utf-8";
 
+  // Added in Orthanc 1.12.1
+  static const char* const MIME_OBJ = "model/obj";
+  static const char* const MIME_MTL = "model/mtl";
+  static const char* const MIME_STL = "model/stl";
+
   /**
    * "No Internet Media Type (aka MIME type, content type) for PBM has
    * been registered with IANA, but the unofficial value
@@ -79,7 +84,10 @@
     MimeType_PrometheusText,  // Prometheus text-based exposition format (for metrics)
     MimeType_DicomWebJson,
     MimeType_DicomWebXml,
-    MimeType_Ico
+    MimeType_Ico,
+    MimeType_Mtl,             // MTL - New in Orthanc 1.12.1
+    MimeType_Obj,             // OBJ - New in Orthanc 1.12.1
+    MimeType_Stl              // STL - New in Orthanc 1.12.1
   };
 
   
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Sun Jul 02 16:24:56 2023 +0200
@@ -825,6 +825,18 @@
     {
       return MimeType_Zip;
     }
+    else if (extension == ".mtl")
+    {
+      return MimeType_Mtl;
+    }
+    else if (extension == ".obj")
+    {
+      return MimeType_Obj;
+    }
+    else if (extension == ".stl")
+    {
+      return MimeType_Stl;
+    }
 
     // Default type
     else
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Sun Jul 02 16:24:56 2023 +0200
@@ -363,6 +363,10 @@
   ASSERT_STREQ("image/svg+xml", EnumerationToString(SystemToolbox::AutodetectMimeType(".svg")));
   ASSERT_STREQ("text/css", EnumerationToString(SystemToolbox::AutodetectMimeType(".css")));
   ASSERT_STREQ("text/html", EnumerationToString(SystemToolbox::AutodetectMimeType(".html")));
+
+  ASSERT_STREQ("model/obj", EnumerationToString(SystemToolbox::AutodetectMimeType(".obj")));
+  ASSERT_STREQ("model/mtl", EnumerationToString(SystemToolbox::AutodetectMimeType(".mtl")));
+  ASSERT_STREQ("model/stl", EnumerationToString(SystemToolbox::AutodetectMimeType(".stl")));
 }
 #endif
 
@@ -791,6 +795,9 @@
   ASSERT_EQ(MimeType_Xml, StringToMimeType(EnumerationToString(MimeType_Xml)));
   ASSERT_EQ(MimeType_DicomWebJson, StringToMimeType(EnumerationToString(MimeType_DicomWebJson)));
   ASSERT_EQ(MimeType_DicomWebXml, StringToMimeType(EnumerationToString(MimeType_DicomWebXml)));
+  ASSERT_EQ(MimeType_Mtl, StringToMimeType(EnumerationToString(MimeType_Mtl)));
+  ASSERT_EQ(MimeType_Obj, StringToMimeType(EnumerationToString(MimeType_Obj)));
+  ASSERT_EQ(MimeType_Stl, StringToMimeType(EnumerationToString(MimeType_Stl)));
   ASSERT_THROW(StringToMimeType("nope"), OrthancException);
 
   ASSERT_TRUE(IsResourceLevelAboveOrEqual(ResourceType_Patient, ResourceType_Patient));
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Jun 29 09:43:41 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Sun Jul 02 16:24:56 2023 +0200
@@ -959,7 +959,7 @@
     InjectTags(dicom, request[TAGS], decodeBinaryTags, privateCreator, force);
 
 
-    // Inject the content (either an image, or a PDF file)
+    // Inject the content (either an image, a PDF file, or a STL/OBJ/MTL file)
     if (request.isMember(CONTENT))
     {
       const Json::Value& content = request[CONTENT];
@@ -1000,8 +1000,9 @@
         .SetRequestField(TAGS, RestApiCallDocumentation::Type_JsonObject,
                          "Associative array containing the tags of the new instance to be created", true)
         .SetRequestField(CONTENT, RestApiCallDocumentation::Type_String,
-                         "This field can be used to embed an image (pixel data) or a PDF inside the created DICOM instance. "
-                         "The PNG image, the JPEG image or the PDF file must be provided using their "
+                         "This field can be used to embed an image (pixel data encoded as PNG or JPEG), a PDF, or a "
+                         "3D manufactoring model (MTL/OBJ/STL) inside the created DICOM instance. "
+                         "The file to be encapsulated must be provided using its "
                          "[data URI scheme encoding](https://en.wikipedia.org/wiki/Data_URI_scheme). "
                          "This field can possibly contain a JSON array, in which case a DICOM series is created "
                          "containing one DICOM instance for each item in the `Content` field.", false)