Mercurial > hg > orthanc
changeset 6494:f9fd84b5ffeb
merge
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Tue, 25 Nov 2025 15:52:40 +0100 |
| parents | ecdc569d04a2 (current diff) 028624638b10 (diff) |
| children | 31e793f606bc |
| files | OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h |
| diffstat | 40 files changed, 490 insertions(+), 112 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Tue Nov 25 15:48:26 2025 +0100 +++ b/.hgignore Tue Nov 25 15:52:40 2025 +0100 @@ -9,6 +9,7 @@ *~ *.cmake.orig .idea/ +OrthancFramework/Resources/CodeGeneration/.venv/ # when opening Orthanc in VSCode, it might find a java project and create files we wan't to ignore: .settings/
--- a/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Resources/CMake/LibCurlConfiguration.cmake Tue Nov 25 15:52:40 2025 +0100 @@ -181,6 +181,10 @@ check_type_size("off_t" SIZEOF_OFF_T) check_type_size("socklen_t" CURL_SIZEOF_CURL_SOCKLEN_T) + if (SIZEOF_LONG_LONG) + set(HAVE_LONGLONG 1) + endif() + check_function_exists("accept4" HAVE_ACCEPT4) check_function_exists("fnmatch" HAVE_FNMATCH) check_symbol_exists("basename" "${CURL_INCLUDES};string.h" HAVE_BASENAME) # libgen.h unistd.h
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Tue Nov 25 15:52:40 2025 +0100 @@ -600,6 +600,7 @@ list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/DcmtkTranscoder.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/IDicomTranscoder.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/Internals/SopInstanceUidFixer.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomParsing/MemoryBufferTranscoder.cpp ) else()
--- a/OrthancFramework/Resources/CodeGeneration/CheckDcmtkTransferSyntaxes.py Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/CheckDcmtkTransferSyntaxes.py Tue Nov 25 15:52:40 2025 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
--- a/OrthancFramework/Resources/CodeGeneration/GenerateErrorCodes.py Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/GenerateErrorCodes.py Tue Nov 25 15:52:40 2025 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
--- a/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxes.py Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxes.py Tue Nov 25 15:52:40 2025 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # Orthanc - A Lightweight, RESTful DICOM Store # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
--- a/OrthancFramework/Sources/DicomFormat/DicomTag.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h Tue Nov 25 15:52:40 2025 +0100 @@ -102,6 +102,8 @@ static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e); static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060); + static const DicomTag DICOM_TAG_DETECTOR_INFORMATION_SEQUENCE(0x0054, 0x0022); + // The following is used for "modify/anonymize" operations static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002); @@ -196,6 +198,7 @@ static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); + static const DicomTag DICOM_TAG_SPACING_BETWEEN_SLICES(0x0018, 0x0088); static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); static const DicomTag DICOM_TAG_DOSE_GRID_SCALING(0x3004, 0x000e);
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -581,7 +581,7 @@ targetSyntaxes.insert(preferredTransferSyntax); attemptedSyntaxes.insert(preferredTransferSyntax); - success = transcoder.Transcode(transcoded, source, targetSyntaxes, true); + success = transcoder.Transcode(transcoded, source, targetSyntaxes, TranscodingSopInstanceUidMode_AllowNew); isDestructiveCompressionAllowed = true; } @@ -612,7 +612,7 @@ if (!targetSyntaxes.empty()) { - success = transcoder.Transcode(transcoded, source, targetSyntaxes, false); + success = transcoder.Transcode(transcoded, source, targetSyntaxes, TranscodingSopInstanceUidMode_NoChange); isDestructiveCompressionAllowed = false; } }
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -36,6 +36,7 @@ #include "FromDcmtkBridge.h" +#include "Internals/SopInstanceUidFixer.h" #include "../Logging.h" #include "../OrthancException.h" #include "../Toolbox.h" @@ -105,11 +106,12 @@ return false; } + bool DcmtkTranscoder::InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */, std::string& failureReason /* out */, DcmFileFormat& dicom, /* in/out */ const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) { std::vector<std::string> failureReasons; @@ -155,6 +157,12 @@ return true; } + // The SOP Instance UID fixer has only an effect if "mode" is TranscodingSopInstanceUidMode_Preserve + // and if transcoding to a lossy transfer syntax + const Internals::SopInstanceUidFixer fixer(mode, dicom); + + const bool allowNewSopInstanceUid = (mode == TranscodingSopInstanceUidMode_AllowNew || + mode == TranscodingSopInstanceUidMode_Preserve); #if ORTHANC_ENABLE_DCMTK_JPEG == 1 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end()) @@ -174,6 +182,7 @@ if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) { + fixer.Apply(dicom); selectedSyntax = DicomTransferSyntax_JPEGProcess1; return true; } @@ -199,6 +208,7 @@ DJ_RPLossy parameters(lossyQuality); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) { + fixer.Apply(dicom); selectedSyntax = DicomTransferSyntax_JPEGProcess2_4; return true; } @@ -215,6 +225,7 @@ 0 /* opt_point_transform */); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14, ¶meters)) { + fixer.Apply(dicom); selectedSyntax = DicomTransferSyntax_JPEGProcess14; return true; } @@ -271,6 +282,7 @@ **/ if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, ¶meters)) { + fixer.Apply(dicom); selectedSyntax = DicomTransferSyntax_JPEGLSLossy; return true; } @@ -316,16 +328,16 @@ bool DcmtkTranscoder::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) + TranscodingSopInstanceUidMode mode) { - return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, defaultLossyQuality_); + return Transcode(target, source, allowedSyntaxes, mode, defaultLossyQuality_); } bool DcmtkTranscoder::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) { Semaphore::Locker lock(maxConcurrentExecutionsSemaphore_); // limit the number of concurrent executions @@ -373,7 +385,7 @@ return true; } else if (InplaceTranscode(targetSyntax, failureReason, source.GetParsed(), - allowedSyntaxes, allowNewSopInstanceUid, lossyQuality)) + allowedSyntaxes, mode, lossyQuality)) { // Sanity check DicomTransferSyntax targetSyntax2; @@ -385,9 +397,13 @@ source.Clear(); #if !defined(NDEBUG) - // Only run the sanity check in debug mode - CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid, - allowedSyntaxes, allowNewSopInstanceUid); + { + // Only run the sanity check in debug mode + const bool allowNewSopInstanceUid = (mode == TranscodingSopInstanceUidMode_AllowNew || + mode == TranscodingSopInstanceUidMode_Preserve); + CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid, + allowedSyntaxes, allowNewSopInstanceUid); + } #endif return true;
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Tue Nov 25 15:52:40 2025 +0100 @@ -48,7 +48,7 @@ std::string& failureReason /* out */, DcmFileFormat& dicom, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality); public: @@ -63,12 +63,12 @@ virtual bool Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + TranscodingSopInstanceUidMode mode) ORTHANC_OVERRIDE; virtual bool Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) ORTHANC_OVERRIDE; }; }
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -471,7 +471,7 @@ if (previous == uidMap_.end()) { - if (identifierGenerator_ == NULL) + if (identifierGenerator_.get() == NULL) { mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); } @@ -541,9 +541,8 @@ keepSeriesInstanceUid_(false), keepSopInstanceUid_(false), updateReferencedRelationships_(true), - isAnonymization_(false), - //privateCreator_("PrivateCreator"), - identifierGenerator_(NULL) + isAnonymization_(false) + //privateCreator_("PrivateCreator") { } @@ -958,13 +957,18 @@ } } - void DicomModification::Apply(ParsedDicomFile& toModify) + void DicomModification::Apply(std::unique_ptr<ParsedDicomFile>& toModify) { // Check the request assert(ResourceType_Patient + 1 == ResourceType_Study && ResourceType_Study + 1 == ResourceType_Series && ResourceType_Series + 1 == ResourceType_Instance); + if (toModify.get() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + if (IsRemoved(DICOM_TAG_PATIENT_ID) || IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || @@ -1015,12 +1019,19 @@ } } + // (0.1) New in Orthanc 1.12.10: Apply custom modifications (e.g. pixels masking) + // This is done before modifying any tags because the dicomModifier_ might have filters on the Orthanc ids -> + // the DICOM UID tags must not be modified before. + if (dicomModifier_ != NULL) + { + dicomModifier_->Apply(toModify); + } - // (0) Create a summary of the source file, if a custom generator + // (0.2) Create a summary of the source file, if a custom generator // is provided if (identifierGenerator_ != NULL) { - toModify.ExtractDicomSummary(currentSource_, ORTHANC_MAXIMUM_TAG_LENGTH); + toModify->ExtractDicomSummary(currentSource_, ORTHANC_MAXIMUM_TAG_LENGTH); } // (1) Make sure the relationships are updated with the ids that we force too @@ -1031,7 +1042,7 @@ { std::string original; std::string replacement = GetReplacementAsString(DICOM_TAG_STUDY_INSTANCE_UID); - const_cast<const ParsedDicomFile&>(toModify).GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID); + const_cast<const ParsedDicomFile&>(*toModify).GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID); RegisterMappedDicomIdentifier(original, replacement, ResourceType_Study); } @@ -1039,7 +1050,7 @@ { std::string original; std::string replacement = GetReplacementAsString(DICOM_TAG_SERIES_INSTANCE_UID); - const_cast<const ParsedDicomFile&>(toModify).GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID); + const_cast<const ParsedDicomFile&>(*toModify).GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID); RegisterMappedDicomIdentifier(original, replacement, ResourceType_Series); } @@ -1047,7 +1058,7 @@ { std::string original; std::string replacement = GetReplacementAsString(DICOM_TAG_SOP_INSTANCE_UID); - const_cast<const ParsedDicomFile&>(toModify).GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID); + const_cast<const ParsedDicomFile&>(*toModify).GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID); RegisterMappedDicomIdentifier(original, replacement, ResourceType_Instance); } } @@ -1056,21 +1067,21 @@ // (2) Remove the private tags, if need be if (removePrivateTags_) { - toModify.RemovePrivateTags(privateTagsToKeep_); + toModify->RemovePrivateTags(privateTagsToKeep_); } // (3) Clear the tags specified by the user for (SetOfTags::const_iterator it = clearings_.begin(); it != clearings_.end(); ++it) { - toModify.Clear(*it, true /* only clear if the tag exists in the original file */); + toModify->Clear(*it, true /* only clear if the tag exists in the original file */); } // (4) Remove the tags specified by the user for (SetOfTags::const_iterator it = removals_.begin(); it != removals_.end(); ++it) { - toModify.Remove(*it); + toModify->Remove(*it); } // (5) Replace the tags @@ -1078,7 +1089,7 @@ it != replacements_.end(); ++it) { assert(it->second != NULL); - toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, + toModify->Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent, privateCreator_); } @@ -1092,7 +1103,7 @@ } else { - MapDicomTags(toModify, ResourceType_Study); + MapDicomTags(*toModify, ResourceType_Study); } } @@ -1105,7 +1116,7 @@ } else { - MapDicomTags(toModify, ResourceType_Series); + MapDicomTags(*toModify, ResourceType_Series); } } @@ -1118,7 +1129,7 @@ } else { - MapDicomTags(toModify, ResourceType_Instance); + MapDicomTags(*toModify, ResourceType_Instance); } } @@ -1129,11 +1140,11 @@ if (updateReferencedRelationships_) { - const_cast<const ParsedDicomFile&>(toModify).Apply(visitor); + const_cast<const ParsedDicomFile&>(*toModify).Apply(visitor); } else { - visitor.RemoveRelationships(toModify); + visitor.RemoveRelationships(*toModify); } } @@ -1142,7 +1153,7 @@ it != removeSequences_.end(); ++it) { assert(it->GetPrefixLength() > 0); - toModify.RemovePath(*it); + toModify->RemovePath(*it); } for (SequenceReplacements::const_iterator it = sequenceReplacements_.begin(); @@ -1150,9 +1161,10 @@ { assert(*it != NULL); assert((*it)->GetPath().GetPrefixLength() > 0); - toModify.ReplacePath((*it)->GetPath(), (*it)->GetValue(), true /* decode data URI scheme */, - DicomReplaceMode_InsertIfAbsent, privateCreator_); + toModify->ReplacePath((*it)->GetPath(), (*it)->GetValue(), true /* decode data URI scheme */, + DicomReplaceMode_InsertIfAbsent, privateCreator_); } + } void DicomModification::SetAllowManualIdentifiers(bool check) @@ -1387,7 +1399,7 @@ { if (!request.isObject()) { - throw OrthancException(ErrorCode_BadFileFormat); + throw OrthancException(ErrorCode_BadFileFormat, "The payload should be a JSON object."); } bool force = GetBooleanValue("Force", request, false); @@ -1401,7 +1413,7 @@ { if (request["DicomVersion"].type() != Json::stringValue) { - throw OrthancException(ErrorCode_BadFileFormat); + throw OrthancException(ErrorCode_BadFileFormat, "DicomVersion should be a string"); } else { @@ -1445,9 +1457,16 @@ } } - void DicomModification::SetDicomIdentifierGenerator(DicomModification::IDicomIdentifierGenerator &generator) + void DicomModification::SetDicomIdentifierGenerator(DicomModification::IDicomIdentifierGenerator* generator) { - identifierGenerator_ = &generator; + if (generator == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + identifierGenerator_.reset(generator); + } } @@ -1627,8 +1646,7 @@ } - DicomModification::DicomModification(const Json::Value& serialized) : - identifierGenerator_(NULL) + DicomModification::DicomModification(const Json::Value& serialized) { removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); @@ -1904,4 +1922,16 @@ target.insert(it->first); } } + + void DicomModification::SetDicomModifier(IDicomModifier* modifier) + { + if (modifier == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + dicomModifier_.reset(modifier); + } + } }
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h Tue Nov 25 15:52:40 2025 +0100 @@ -31,6 +31,7 @@ namespace Orthanc { + class ORTHANC_PUBLIC DicomModification : public boost::noncopyable { /** @@ -54,6 +55,18 @@ const DicomMap& sourceDicom) = 0; }; + class ORTHANC_PUBLIC IDicomModifier : public boost::noncopyable + { + public: + virtual ~IDicomModifier() + { + } + + // It is allowed for the implementation to replace "dicom" by a + // brand new ParsedDicomFile instance + virtual void Apply(std::unique_ptr<ParsedDicomFile>& dicom) = 0; + }; + private: class RelationshipsVisitor; @@ -145,7 +158,7 @@ DicomMap currentSource_; std::string privateCreator_; - IDicomIdentifierGenerator* identifierGenerator_; + std::unique_ptr<IDicomIdentifierGenerator> identifierGenerator_; // New in Orthanc 1.9.4 SetOfTags uids_; @@ -154,6 +167,9 @@ ListOfPaths removeSequences_; // Must *never* be a path whose prefix is empty SequenceReplacements sequenceReplacements_; // Must *never* be a path whose prefix is empty + // New in Orthanc 1.12.10 + std::unique_ptr<IDicomModifier> dicomModifier_; + std::string MapDicomIdentifier(const std::string& original, ResourceType level); @@ -235,7 +251,8 @@ void SetupAnonymization(DicomVersion version); - void Apply(ParsedDicomFile& toModify); + // The "toModify" might be replaced by a new object + void Apply(std::unique_ptr<ParsedDicomFile>& toModify); void SetAllowManualIdentifiers(bool check); @@ -248,7 +265,7 @@ void ParseAnonymizationRequest(bool& patientNameOverridden /* out */, const Json::Value& request); - void SetDicomIdentifierGenerator(IDicomIdentifierGenerator& generator); + void SetDicomIdentifierGenerator(IDicomIdentifierGenerator* generator /* takes ownership */); void Serialize(Json::Value& value) const; @@ -268,5 +285,12 @@ bool safeForAnonymization); bool IsAlteredTag(const DicomTag& tag) const; + + bool IsAnonymization() const + { + return isAnonymization_; + } + + void SetDicomModifier(IDicomModifier* modifier); }; }
--- a/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Tue Nov 25 15:52:40 2025 +0100 @@ -56,8 +56,6 @@ void Serialize(); - DcmFileFormat* ReleaseParsed(); - public: DicomImage(); @@ -81,6 +79,8 @@ DcmFileFormat& GetParsed(); + DcmFileFormat* ReleaseParsed(); + ParsedDicomFile* ReleaseAsParsedDicomFile(); const void* GetBufferData(); @@ -114,12 +114,12 @@ virtual bool Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; + TranscodingSopInstanceUidMode mode) = 0; virtual bool Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) = 0; static std::string GetSopInstanceUid(DcmFileFormat& dicom);
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -226,6 +226,28 @@ fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); } } + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) ORTHANC_OVERRIDE + { + if (index >= startFragment_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (countFragments_[index] != 1) + { + throw OrthancException(ErrorCode_NotImplemented, "GetRawFrameBuffer is currently not implemented if there are more fragments than frames."); + } + + DcmPixelItem* fragment = startFragment_[index]; + uint8_t* content = NULL; + if (!fragment->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + return content; + } }; @@ -277,6 +299,11 @@ memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); } } + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) ORTHANC_OVERRIDE + { + return pixelData_ + index * frameSize_; + } }; @@ -308,6 +335,11 @@ memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); } } + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); + } }; @@ -415,4 +447,20 @@ throw OrthancException(ErrorCode_BadFileFormat, "Cannot access a raw frame"); } } + + uint8_t* DicomFrameIndex::GetRawFrameBuffer(unsigned int index) + { + if (index >= countFrames_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (index_.get() != NULL) + { + return index_->GetRawFrameBuffer(index); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot access a raw frame"); + } + } }
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.h Tue Nov 25 15:52:40 2025 +0100 @@ -48,6 +48,8 @@ virtual void GetRawFrame(std::string& frame, unsigned int index) const = 0; + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) = 0; }; class FragmentIndex; @@ -69,5 +71,7 @@ unsigned int index) const; static unsigned int GetFramesCount(DcmDataset& dicom); + + uint8_t* GetRawFrameBuffer(unsigned int index); }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomParsing/Internals/SopInstanceUidFixer.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -0,0 +1,85 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeaders.h" +#include "SopInstanceUidFixer.h" + +#include "../../OrthancException.h" + +#include <dcmtk/dcmdata/dcdeftag.h> + + +namespace Orthanc +{ + namespace Internals + { + SopInstanceUidFixer::SopInstanceUidFixer(TranscodingSopInstanceUidMode mode, + IDicomTranscoder::DicomImage& source) : + fix_(mode == TranscodingSopInstanceUidMode_Preserve) + { + if (fix_) + { + sopInstanceUid_ = IDicomTranscoder::GetSopInstanceUid(source.GetParsed()); + } + } + + + SopInstanceUidFixer::SopInstanceUidFixer(TranscodingSopInstanceUidMode mode, + DcmFileFormat& source) : + fix_(mode == TranscodingSopInstanceUidMode_Preserve) + { + if (fix_) + { + sopInstanceUid_ = IDicomTranscoder::GetSopInstanceUid(source); + } + } + + + void SopInstanceUidFixer::Apply(DcmFileFormat& target) const + { + if (fix_) + { + if (target.getDataset() == NULL || + !target.getDataset()->putAndInsertString( + DCM_SOPInstanceUID, sopInstanceUid_.c_str(), OFTrue /* replace */).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void SopInstanceUidFixer::Apply(IDicomTranscoder::DicomImage& target) const + { + if (fix_) + { + std::unique_ptr<DcmFileFormat> dicom(target.ReleaseParsed()); + Apply(*dicom); + + target.Clear(); + target.AcquireParsed(dicom.release()); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomParsing/Internals/SopInstanceUidFixer.h Tue Nov 25 15:52:40 2025 +0100 @@ -0,0 +1,60 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Enumerations.h" +#include "../IDicomTranscoder.h" + +#if ORTHANC_ENABLE_DCMTK != 1 +# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 +#endif + +#include <boost/noncopyable.hpp> +#include <dcmtk/dcmdata/dcfilefo.h> + + +namespace Orthanc +{ + namespace Internals + { + class SopInstanceUidFixer : public boost::noncopyable + { + private: + bool fix_; + std::string sopInstanceUid_; + + public: + SopInstanceUidFixer(TranscodingSopInstanceUidMode mode, + IDicomTranscoder::DicomImage& source); + + SopInstanceUidFixer(TranscodingSopInstanceUidMode mode, + DcmFileFormat& source); + + void Apply(IDicomTranscoder::DicomImage& target) const; + + void Apply(DcmFileFormat& target) const; + }; + } +}
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -28,6 +28,7 @@ #include "../Logging.h" #include "../OrthancException.h" #include "FromDcmtkBridge.h" +#include "Internals/SopInstanceUidFixer.h" #if !defined(NDEBUG) // For debugging # include "ParsedDicomFile.h" @@ -59,16 +60,16 @@ bool MemoryBufferTranscoder::Transcode(DicomImage& target, DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQualityNotUsed) { - return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid); + return Transcode(target, source, allowedSyntaxes, mode); } bool MemoryBufferTranscoder::Transcode(DicomImage& target, DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) + TranscodingSopInstanceUidMode mode) { target.Clear(); @@ -84,6 +85,10 @@ const std::string sourceSopInstanceUid = GetSopInstanceUid(source.GetParsed()); #endif + Internals::SopInstanceUidFixer fixer(mode, source); + const bool allowNewSopInstanceUid = (mode == TranscodingSopInstanceUidMode_AllowNew || + mode == TranscodingSopInstanceUidMode_Preserve); + std::string buffer; if (TranscodeBuffer(buffer, source.GetBufferData(), source.GetBufferSize(), allowedSyntaxes, allowNewSopInstanceUid)) @@ -98,6 +103,7 @@ allowedSyntaxes, allowNewSopInstanceUid); #endif + fixer.Apply(target); return true; } else
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Tue Nov 25 15:52:40 2025 +0100 @@ -42,12 +42,12 @@ virtual bool Transcode(DicomImage& target /* out */, DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + TranscodingSopInstanceUidMode mode) ORTHANC_OVERRIDE; virtual bool Transcode(DicomImage& target /* out */, DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQualityNotUsed) ORTHANC_OVERRIDE; }; }
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -1824,6 +1824,67 @@ } } + ImageAccessor* ParsedDicomFile::GetRawFrameForInplaceModification(unsigned int frame) + { + E_TransferSyntax transferSyntax = GetDcmtkObjectConst().getDataset()->getCurrentXfer(); + + bool ok = false; + switch (Toolbox::DetectEndianness()) + { + case Endianness_Little: + if (transferSyntax == EXS_LittleEndianImplicit || + transferSyntax == EXS_LittleEndianExplicit) + { + ok = true; + } + break; + + case Endianness_Big: + if (transferSyntax == EXS_BigEndianImplicit || + transferSyntax == EXS_BigEndianExplicit) + { + ok = true; + } + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (!ok) + { + throw OrthancException(ErrorCode_NotImplemented, "ParsedDicomFile::GetRawFrameForInplaceModification() only works with uncompressed transfer syntaxes and matching host endianness"); + } + + if (pimpl_->frameIndex_.get() == NULL) + { + assert(pimpl_->file_ != NULL && + GetDcmtkObjectConst().getDataset() != NULL); + pimpl_->frameIndex_.reset(new DicomFrameIndex(*GetDcmtkObjectConst().getDataset())); + } + + DicomMap m; + std::set<DicomTag> ignoreTagLength; + FromDcmtkBridge::ExtractDicomSummary(m, *GetDcmtkObjectConst().getDataset(), DicomImageInformation::GetUsefulTagLength(), ignoreTagLength); + + DicomImageInformation info(m); + PixelFormat format; + + if (!info.ExtractPixelFormat(format, false)) + { + LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() + << "bpp, " << info.GetChannelCount() << " channels, " + << (info.IsSigned() ? "signed" : "unsigned") + << (info.IsPlanar() ? ", planar, " : ", non-planar, ") + << EnumerationToString(info.GetPhotometricInterpretation()) + << " photometric interpretation"; + throw OrthancException(ErrorCode_NotImplemented); + } + + std::unique_ptr<ImageAccessor> img(new ImageAccessor()); + img->AssignWritable(format, info.GetWidth(), info.GetHeight(), info.GetWidth() * GetBytesPerPixel(format), pimpl_->frameIndex_->GetRawFrameBuffer(frame)); + return img.release(); + } static bool HasGenericGroupLength(const DicomPath& path) {
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h Tue Nov 25 15:52:40 2025 +0100 @@ -316,6 +316,10 @@ ImageAccessor* DecodeAllOverlays(int& originX, int& originY) const; + // Returns an image accessor to the raw frame only if the DicomFile is in an uncompressed TS. + // This enables modification of pixels data in place. + ImageAccessor* GetRawFrameForInplaceModification(unsigned int frame); + void InjectEmptyPixelData(ValueRepresentation vr); // Remove all the tags after pixel data
--- a/OrthancFramework/Sources/Enumerations.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/Enumerations.h Tue Nov 25 15:52:40 2025 +0100 @@ -809,6 +809,13 @@ ErrorPayloadType_RetrieveJob = 3, }; + enum TranscodingSopInstanceUidMode + { + TranscodingSopInstanceUidMode_NoChange, // Never change the SOP Instance UID (only allows transcoding to lossless) + TranscodingSopInstanceUidMode_AllowNew, // Allow transcoding to lossless and lossy (if lossy, a new SOP Instance UID is generated) + TranscodingSopInstanceUidMode_Preserve // Allow transcoding to lossless and lossy (if lossy, preserve the original SOP Instance UID) + }; + ORTHANC_PUBLIC const char* EnumerationToString(ErrorCode code);
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -2789,6 +2789,19 @@ } break; + case PixelFormat_Grayscale16: + if (useRound) + { + SeparableConvolutionFloat<uint16_t, 1u, true> + (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization); + } + else + { + SeparableConvolutionFloat<uint16_t, 1u, false> + (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization); + } + break; + case PixelFormat_RGB24: if (useRound) { @@ -2822,6 +2835,14 @@ } + void ImageProcessing::MeanFilter(ImageAccessor& image, size_t horizontalKernelWidth, size_t verticalKernelWidth) + { + std::vector<float> hKernel(horizontalKernelWidth, 1.0f); + std::vector<float> vKernel(verticalKernelWidth, 1.0f); + + SeparableConvolution(image, hKernel, horizontalKernelWidth / 2, vKernel, verticalKernelWidth / 2, false); + } + void ImageProcessing::FitSize(ImageAccessor& target, const ImageAccessor& source) {
--- a/OrthancFramework/Sources/Images/ImageProcessing.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/Sources/Images/ImageProcessing.h Tue Nov 25 15:52:40 2025 +0100 @@ -201,6 +201,8 @@ static void SmoothGaussian5x5(ImageAccessor& image, bool useRound /* this is expensive */); + static void MeanFilter(ImageAccessor& image, size_t horizontalAverageWidth, size_t verticalAverageWidth); + static void FitSize(ImageAccessor& target, const ImageAccessor& source);
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -116,7 +116,7 @@ std::unique_ptr<ParsedDicomFile> f(o.Clone(false)); if (i > 4) o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou"); - m.Apply(*f); + m.Apply(f); f->SaveToFile(b); } } @@ -135,26 +135,26 @@ ASSERT_EQ(0x1020, privateTag2.GetElement()); std::string s; - ParsedDicomFile o(true); - o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); - ASSERT_FALSE(o.GetTagValue(s, privateTag)); - o.Insert(privateTag, "private tag", false, "OrthancCreator"); - ASSERT_TRUE(o.GetTagValue(s, privateTag)); + std::unique_ptr<ParsedDicomFile> o(new ParsedDicomFile(true)); + o->ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou"); + ASSERT_FALSE(o->GetTagValue(s, privateTag)); + o->Insert(privateTag, "private tag", false, "OrthancCreator"); + ASSERT_TRUE(o->GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); - ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException); - ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator"); - ASSERT_FALSE(o.GetTagValue(s, privateTag2)); - o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); - ASSERT_TRUE(o.GetTagValue(s, privateTag2)); + ASSERT_FALSE(o->GetTagValue(s, privateTag2)); + ASSERT_THROW(o->Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException); + ASSERT_FALSE(o->GetTagValue(s, privateTag2)); + o->Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator"); + ASSERT_FALSE(o->GetTagValue(s, privateTag2)); + o->Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); + ASSERT_TRUE(o->GetTagValue(s, privateTag2)); ASSERT_STREQ("hello", s.c_str()); - o.Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); - ASSERT_TRUE(o.GetTagValue(s, privateTag2)); + o->Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator"); + ASSERT_TRUE(o->GetTagValue(s, privateTag2)); ASSERT_STREQ("hello world", s.c_str()); - ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(o->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); ASSERT_FALSE(Toolbox::IsUuid(s)); DicomModification m; @@ -163,14 +163,14 @@ m.Apply(o); - ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_TRUE(o->GetTagValue(s, DICOM_TAG_PATIENT_NAME)); ASSERT_TRUE(Toolbox::IsUuid(s)); - ASSERT_TRUE(o.GetTagValue(s, privateTag)); + ASSERT_TRUE(o->GetTagValue(s, privateTag)); ASSERT_STREQ("private tag", s.c_str()); m.SetupAnonymization(DicomVersion_2008); m.Apply(o); - ASSERT_FALSE(o.GetTagValue(s, privateTag)); + ASSERT_FALSE(o->GetTagValue(s, privateTag)); } @@ -2772,7 +2772,7 @@ modif.Replace(DicomPath(DICOM_TAG_PATIENT_NAME), "Hello1", false); modif.Replace(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"), "Hello2", false); modif.Replace(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"), "Hello3", false); - modif.Apply(*dicom); + modif.Apply(dicom); } Json::Value vv; @@ -2794,7 +2794,7 @@ modif.Remove(DicomPath(DICOM_TAG_PATIENT_NAME)); modif.Remove(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID")); modif.Remove(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence")); - modif.Apply(*dicom); + modif.Apply(dicom); } Json::Value vv; @@ -2815,8 +2815,8 @@ { DicomModification modif; modif.SetupAnonymization(DicomVersion_2023b); - modif.Apply(*dicom1); - modif.Apply(*dicom2); + modif.Apply(dicom1); + modif.Apply(dicom2); } // Same anonymization context and same input DICOM => hence, same output DICOM @@ -2844,7 +2844,7 @@ modif.SetupAnonymization(DicomVersion_2023b); modif.Keep(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPInstanceUID")); modif.Keep(DicomPath::Parse("RelatedSeriesSequence")); - modif.Apply(*dicom); + modif.Apply(dicom); } Json::Value vv; @@ -3681,7 +3681,7 @@ IDicomTranscoder::DicomImage source, target; source.AcquireParsed(dynamic_cast<DcmFileFormat*>(toto->clone())); - if (!transcoder.Transcode(target, source, s, true)) + if (!transcoder.Transcode(target, source, s, TranscodingSopInstanceUidMode_AllowNew)) { printf("**************** CANNOT: [%s] => [%s]\n", GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -1084,7 +1084,7 @@ modification.Remove(DICOM_TAG_SERIES_DESCRIPTION); modification.Replace(DICOM_TAG_PATIENT_NAME, "Test 4", true); - modification.Apply(*modified); + modification.Apply(modified); s = 42; modification.Serialize(s); @@ -1095,7 +1095,7 @@ ASSERT_EQ(ResourceType_Series, modification.GetLevel()); std::unique_ptr<ParsedDicomFile> second(source.Clone(true)); - modification.Apply(*second); + modification.Apply(second); std::string t; ASSERT_TRUE(second->GetTagValue(t, DICOM_TAG_STUDY_DESCRIPTION));
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -6015,11 +6015,10 @@ IDicomTranscoder::DicomImage transcoded; bool success; - + { PImpl::ServerContextReference lock(*pimpl_); - success = lock.GetContext().Transcode( - transcoded, source, syntaxes, true /* allow new sop */); + success = lock.GetContext().Transcode(transcoded, source, syntaxes, TranscodingSopInstanceUidMode_AllowNew); } if (success)
--- a/OrthancServer/Plugins/Samples/CppSkeleton/CMakeLists.txt Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Plugins/Samples/CppSkeleton/CMakeLists.txt Tue Nov 25 15:52:40 2025 +0100 @@ -19,7 +19,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8...4.0) project(OrthancSkeleton)
--- a/OrthancServer/Plugins/Samples/CppSkeleton/Resources/SyncOrthancFolder.py Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Plugins/Samples/CppSkeleton/Resources/SyncOrthancFolder.py Tue Nov 25 15:52:40 2025 +0100 @@ -40,7 +40,7 @@ ('orthanc', 'OrthancFramework/Resources/CMake/Compiler.cmake', 'CMake'), ('orthanc', 'OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake', 'CMake'), ('orthanc', 'OrthancFramework/Resources/CMake/DownloadPackage.cmake', 'CMake'), - ('orthanc', 'OrthancFramework/Resources/EmbedResources.py', 'CMake'), + ('orthanc', 'OrthancFramework/Resources/CMake/EmbedResources.py', 'CMake'), ('orthanc', 'OrthancFramework/Resources/Toolchains/LinuxStandardBaseToolchain.cmake', 'Toolchains'), ('orthanc', 'OrthancFramework/Resources/Toolchains/MinGW-W64-Toolchain32.cmake', 'Toolchains'),
--- a/OrthancServer/Resources/RunCppCheck-2.1.sh Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Resources/RunCppCheck-2.1.sh Tue Nov 25 15:52:40 2025 +0100 @@ -13,7 +13,7 @@ constParameter:../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp knownArgument:../../OrthancFramework/UnitTestsSources/ImageTests.cpp knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp -nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322 +nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:321 stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1535 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74
--- a/OrthancServer/Resources/RunCppCheck-2.17.1.sh Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Resources/RunCppCheck-2.17.1.sh Tue Nov 25 15:52:40 2025 +0100 @@ -17,7 +17,7 @@ constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp:112 constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp:113 constParameterCallback:../../OrthancFramework/Sources/Pkcs11.cpp:125 -constParameterCallback:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp:3489 +constParameterCallback:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp:3493 constParameterCallback:../../OrthancServer/Sources/OrthancGetRequestHandler.cpp:47 constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:447 constParameterPointer:../../OrthancFramework/Sources/Logging.cpp:451 @@ -39,7 +39,7 @@ knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2298 knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2299 knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp:2300 -nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322 +nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:321 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74 syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53
--- a/OrthancServer/Sources/OrthancGetRequestHandler.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/OrthancGetRequestHandler.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -325,7 +325,7 @@ std::set<DicomTransferSyntax> ts; ts.insert(selectedSyntax); - if (context_.Transcode(transcoded, source, ts, true)) + if (context_.Transcode(transcoded, source, ts, TranscodingSopInstanceUidMode_AllowNew)) { // Transcoding has succeeded DcmDataset *stDetailTmp = NULL;
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -220,7 +220,7 @@ modified.reset(locker.GetDicom().Clone(true)); } - modification.Apply(*modified); + modification.Apply(modified); if (transcode) { @@ -232,7 +232,7 @@ std::set<DicomTransferSyntax> s; s.insert(targetSyntax); - if (context.Transcode(transcoded, source, s, true, lossyQuality)) + if (context.Transcode(transcoded, source, s, TranscodingSopInstanceUidMode_AllowNew, lossyQuality)) { call.GetOutput().AnswerBuffer(transcoded.GetBufferData(), transcoded.GetBufferSize(), MimeType_Dicom);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -432,7 +432,7 @@ std::set<DicomTransferSyntax> allowedSyntaxes; allowedSyntaxes.insert(GetTransferSyntax(call.GetArgument(GET_TRANSCODE, ""))); - if (context.Transcode(targetImage, sourceImage, allowedSyntaxes, true, lossyQuality)) + if (context.Transcode(targetImage, sourceImage, allowedSyntaxes, TranscodingSopInstanceUidMode_AllowNew, lossyQuality)) { call.GetOutput().SetContentFilename(filename.c_str()); call.GetOutput().AnswerBuffer(targetImage.GetBufferData(), targetImage.GetBufferSize(), MimeType_Dicom);
--- a/OrthancServer/Sources/ServerContext.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -50,6 +50,7 @@ #include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmnet/dimse.h> +#include <dcmtk/dcmdata/dcdeftag.h> #include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ #include <boost/regex.hpp> @@ -1017,7 +1018,7 @@ IDicomTranscoder::DicomImage transcoded; - if (Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */)) + if (Transcode(transcoded, source, syntaxes, TranscodingSopInstanceUidMode_AllowNew /* allow new SOP instance UID */)) { std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile()); @@ -1941,7 +1942,7 @@ source.SetExternalBuffer(buffer, size); allowedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); - if (Transcode(explicitTemporaryImage, source, allowedSyntaxes, true)) + if (Transcode(explicitTemporaryImage, source, allowedSyntaxes, TranscodingSopInstanceUidMode_AllowNew)) { std::unique_ptr<ParsedDicomFile> file(explicitTemporaryImage.ReleaseAsParsedDicomFile()); return file->DecodeFrame(frameIndex); @@ -2015,7 +2016,7 @@ std::set<DicomTransferSyntax> syntaxes; syntaxes.insert(targetSyntax); - if (Transcode(targetDicom, sourceDicom, syntaxes, true)) + if (Transcode(targetDicom, sourceDicom, syntaxes, TranscodingSopInstanceUidMode_AllowNew)) { cacheAccessor.AddTranscodedInstance(attachmentId, targetSyntax, reinterpret_cast<const char*>(targetDicom.GetBufferData()), targetDicom.GetBufferSize()); target = std::string(reinterpret_cast<const char*>(targetDicom.GetBufferData()), targetDicom.GetBufferSize()); @@ -2031,7 +2032,7 @@ bool ServerContext::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) + TranscodingSopInstanceUidMode mode) { unsigned int lossyQuality; @@ -2040,19 +2041,19 @@ lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); } - return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); + return Transcode(target, source, allowedSyntaxes, mode, lossyQuality); } bool ServerContext::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) { if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { - if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality)) + if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, mode, lossyQuality)) { return true; } @@ -2062,7 +2063,7 @@ if (HasPlugins() && GetPlugins().HasCustomTranscoder()) { - if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) // TODO: pass lossyQuality to plugins -> needs a new plugin interface + if (GetPlugins().Transcode(target, source, allowedSyntaxes, mode)) // TODO: pass lossyQuality to plugins -> needs a new plugin interface { return true; } @@ -2076,7 +2077,7 @@ if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { - return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); + return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, mode, lossyQuality); } else { @@ -2084,6 +2085,7 @@ } } + const std::string& ServerContext::GetDeidentifiedContent(const DicomElement &element) const { static const std::string redactedContent = "*** POTENTIAL PHI ***";
--- a/OrthancServer/Sources/ServerContext.h Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Tue Nov 25 15:52:40 2025 +0100 @@ -589,12 +589,12 @@ virtual bool Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; - + TranscodingSopInstanceUidMode mode) ORTHANC_OVERRIDE; + virtual bool Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid, + TranscodingSopInstanceUidMode mode, unsigned int lossyQuality) ORTHANC_OVERRIDE; virtual bool TranscodeWithCache(std::string& target,
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -116,7 +116,7 @@ IDicomTranscoder::DicomImage source, transcoded; source.SetExternalBuffer(sourceBuffer); - if (context_.Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */, lossyQuality_)) + if (context_.Transcode(transcoded, source, syntaxes, TranscodingSopInstanceUidMode_AllowNew, lossyQuality_)) { transcodedBuffer.assign(reinterpret_cast<const char*>(transcoded.GetBufferData()), transcoded.GetBufferSize()); return true;
--- a/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -92,7 +92,7 @@ try { - modification_->Apply(*modified); + modification_->Apply(modified); std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*modified)); assert(origin_ == RequestOrigin_Lua);
--- a/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -69,7 +69,7 @@ IDicomTranscoder::DicomImage source, transcoded; source.SetExternalBuffer(dicom); - if (context_.Transcode(transcoded, source, syntaxes, true)) + if (context_.Transcode(transcoded, source, syntaxes, TranscodingSopInstanceUidMode_AllowNew)) { body.assign(reinterpret_cast<const char*>(transcoded.GetBufferData()), transcoded.GetBufferSize());
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Tue Nov 25 15:48:26 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Tue Nov 25 15:52:40 2025 +0100 @@ -238,7 +238,7 @@ { boost::recursive_mutex::scoped_lock lock(mutex_); // DicomModification object is not thread safe, we must protect it from here - modification_->Apply(*modified); + modification_->Apply(modified); if (modification_->AreLabelsKept()) { @@ -261,7 +261,7 @@ source.AcquireParsed(*modified); // "modified" is invalid below this point IDicomTranscoder::DicomImage transcoded; - if (GetContext().Transcode(transcoded, source, syntaxes, true)) + if (GetContext().Transcode(transcoded, source, syntaxes, TranscodingSopInstanceUidMode_AllowNew)) { modified.reset(transcoded.ReleaseAsParsedDicomFile());
