Mercurial > hg > orthanc
changeset 6485:f44c708caf25
integration pixel-anon->mainline
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Tue, 25 Nov 2025 14:24:48 +0100 |
| parents | 360953cb921b (current diff) 46462739f6d2 (diff) |
| children | d7bbe6dc90ba |
| files | |
| diffstat | 35 files changed, 325 insertions(+), 109 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Mon Nov 24 15:37:02 2025 +0100 +++ b/.hgignore Tue Nov 25 14:24:48 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/OrthancFrameworkConfiguration.cmake Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Tue Nov 25 14:24:48 2025 +0100 @@ -599,6 +599,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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/CheckDcmtkTransferSyntaxes.py Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/GenerateErrorCodes.py Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxes.py Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp Tue Nov 25 14:24:48 2025 +0100 @@ -226,6 +226,28 @@ fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); } } + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) + { + 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,12 @@ memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); } } + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) + { + return pixelData_ + index * frameSize_; + } + }; @@ -308,6 +336,12 @@ memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); } } + + virtual uint8_t* GetRawFrameBuffer(unsigned int index) + { + throw OrthancException(ErrorCode_NotImplemented); + } + }; @@ -415,4 +449,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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.h Tue Nov 25 14:24:48 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); }; }
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp Tue Nov 25 14:24:48 2025 +0100 @@ -1824,6 +1824,46 @@ } } + ImageAccessor* ParsedDicomFile::GetRawFrame(unsigned int frame) + { + E_TransferSyntax transferSyntax = GetDcmtkObjectConst().getDataset()->getCurrentXfer(); + if (transferSyntax != EXS_LittleEndianImplicit && + transferSyntax != EXS_BigEndianImplicit && + transferSyntax != EXS_LittleEndianExplicit && + transferSyntax != EXS_BigEndianExplicit) + { + throw OrthancException(ErrorCode_NotImplemented, "ParseDicomFile::GetRawFrame only works with uncompressed transfer syntaxes"); + } + + 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h Tue Nov 25 14:24:48 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* GetRawFrame(unsigned int frame); + void InjectEmptyPixelData(ValueRepresentation vr); // Remove all the tags after pixel data
--- a/OrthancFramework/Sources/Enumerations.h Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/Enumerations.h Tue Nov 25 14:24:48 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); @@ -966,6 +973,12 @@ DicomTransferSyntax GetTransferSyntax(const std::string& uid); ORTHANC_PUBLIC + bool IsRawTransferSyntax(DicomTransferSyntax syntax); + + ORTHANC_PUBLIC + bool IsLossyTransferSyntax(DicomTransferSyntax syntax); + + ORTHANC_PUBLIC const char* GetResourceTypeText(ResourceType type, bool isPlural, bool isUpperCase);
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/Sources/Images/ImageProcessing.h Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Nov 25 14:24:48 2025 +0100 @@ -5792,11 +5792,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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Plugins/Samples/CppSkeleton/CMakeLists.txt Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Plugins/Samples/CppSkeleton/Resources/SyncOrthancFolder.py Tue Nov 25 14:24:48 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/Sources/OrthancGetRequestHandler.cpp Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/OrthancGetRequestHandler.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Tue Nov 25 14:24:48 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()); @@ -1942,7 +1943,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); @@ -2016,7 +2017,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()); @@ -2032,7 +2033,7 @@ bool ServerContext::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) + TranscodingSopInstanceUidMode mode) { unsigned int lossyQuality; @@ -2041,19 +2042,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; } @@ -2063,7 +2064,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; } @@ -2077,7 +2078,7 @@ if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { - return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); + return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, mode, lossyQuality); } else { @@ -2085,6 +2086,7 @@ } } + const std::string& ServerContext::GetDeidentifiedContent(const DicomElement &element) const { static const std::string redactedContent = "*** POTENTIAL PHI ***";
--- a/OrthancServer/Sources/ServerContext.h Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Tue Nov 25 14:24:48 2025 +0100 @@ -590,12 +590,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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp Tue Nov 25 14:24:48 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 Mon Nov 24 15:37:02 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp Tue Nov 25 14:24:48 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());
