# HG changeset patch # User Sebastien Jodogne # Date 1688307896 -7200 # Node ID 303e930fff0f19aa074aea9d50633bdcd2adb15d # Parent 4f686f6150c70fabfaec3929c65baeee8c1eb149 "/tools/create-dicom" can now be used to create Encapsulated 3D Manufacturing Model IODs (MTL, OBJ, or STL) diff -r 4f686f6150c7 -r 303e930fff0f NEWS --- 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: diff -r 4f686f6150c7 -r 303e930fff0f OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp --- 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 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); diff -r 4f686f6150c7 -r 303e930fff0f OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h --- 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 diff -r 4f686f6150c7 -r 303e930fff0f OrthancFramework/Sources/Enumerations.cpp --- 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; diff -r 4f686f6150c7 -r 303e930fff0f OrthancFramework/Sources/Enumerations.h --- 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 }; diff -r 4f686f6150c7 -r 303e930fff0f OrthancFramework/Sources/SystemToolbox.cpp --- 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 diff -r 4f686f6150c7 -r 303e930fff0f OrthancFramework/UnitTestsSources/FrameworkTests.cpp --- 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)); diff -r 4f686f6150c7 -r 303e930fff0f OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- 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)