Mercurial > hg > orthanc
changeset 6016:295f51db7976
merge
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 24 Feb 2025 08:03:02 +0100 |
parents | 78b1934b4ce3 (current diff) 97cfdcdf47e3 (diff) |
children | 90ecf7d849cd |
files | NEWS |
diffstat | 18 files changed, 281 insertions(+), 49 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Mon Feb 24 08:01:43 2025 +0100 +++ b/NEWS Mon Feb 24 08:03:02 2025 +0100 @@ -8,6 +8,9 @@ * GET /studies/../archive and sibbling routes now all accept a 'filename' GET argument. * POST /studies/../archive and sibbling routes now all accept a 'Filename' query argument. * GET /instances/../file and sibbling ../attachments/../data routes now all accept a 'filename' GET argument. +* All routes accepting a "transcode" url argument or a "Transcode" field in the payload now also + accepts a "lossy-quality" url argument or a "LossyQuality" field to define the compression quality factor. + If not specified, the "DicomLossyTranscodingQuality" configuration is taken into account. Maintenance
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -51,7 +51,7 @@ namespace Orthanc { DcmtkTranscoder::DcmtkTranscoder(unsigned int maxConcurrentExecutions) : - lossyQuality_(90), + defaultLossyQuality_(90), maxConcurrentExecutionsSemaphore_(maxConcurrentExecutions) { } @@ -64,26 +64,26 @@ } - void DcmtkTranscoder::SetLossyQuality(unsigned int quality) + void DcmtkTranscoder::SetDefaultLossyQuality(unsigned int quality) { if (quality == 0 || quality > 100) { throw OrthancException( ErrorCode_ParameterOutOfRange, - "The quality for lossy transcoding must be an integer between 1 and 100, received: " + + "The default quality for lossy transcoding must be an integer between 1 and 100, received: " + boost::lexical_cast<std::string>(quality)); } else { - LOG(INFO) << "Quality for lossy transcoding using DCMTK is set to: " << quality; - lossyQuality_ = quality; + LOG(INFO) << "Default quality for lossy transcoding using DCMTK is set to: " << quality; + defaultLossyQuality_ = quality; } } - unsigned int DcmtkTranscoder::GetLossyQuality() const + unsigned int DcmtkTranscoder::GetDefaultLossyQuality() const { - return lossyQuality_; + return defaultLossyQuality_; } bool TryTranscode(std::vector<std::string>& failureReasons, /* out */ @@ -109,7 +109,8 @@ std::string& failureReason /* out */, DcmFileFormat& dicom, /* in/out */ const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) + bool allowNewSopInstanceUid, + unsigned int lossyQuality) { std::vector<std::string> failureReasons; @@ -169,7 +170,7 @@ else { // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); + DJ_RPLossy parameters(lossyQuality); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) { @@ -195,7 +196,7 @@ else { // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); + DJ_RPLossy parameters(lossyQuality); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) { selectedSyntax = DicomTransferSyntax_JPEGProcess2_4; @@ -312,11 +313,20 @@ return false; } + bool DcmtkTranscoder::Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, defaultLossyQuality_); + } + bool DcmtkTranscoder::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid) + bool allowNewSopInstanceUid, + unsigned int lossyQuality) { Semaphore::Locker lock(maxConcurrentExecutionsSemaphore_); // limit the number of concurrent executions @@ -363,7 +373,7 @@ return true; } else if (InplaceTranscode(targetSyntax, failureReason, source.GetParsed(), - allowedSyntaxes, allowNewSopInstanceUid)) + allowedSyntaxes, allowNewSopInstanceUid, lossyQuality)) { // Sanity check DicomTransferSyntax targetSyntax2;
--- a/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.h Mon Feb 24 08:03:02 2025 +0100 @@ -41,21 +41,22 @@ class ORTHANC_PUBLIC DcmtkTranscoder : public IDicomTranscoder { private: - unsigned int lossyQuality_; + unsigned int defaultLossyQuality_; Semaphore maxConcurrentExecutionsSemaphore_; bool InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */, std::string& failureReason /* out */, DcmFileFormat& dicom, const std::set<DicomTransferSyntax>& allowedSyntaxes, - bool allowNewSopInstanceUid); + bool allowNewSopInstanceUid, + unsigned int lossyQuality); public: explicit DcmtkTranscoder(unsigned int maxConcurrentExecutions); - void SetLossyQuality(unsigned int quality); + void SetDefaultLossyQuality(unsigned int quality); - unsigned int GetLossyQuality() const; + unsigned int GetDefaultLossyQuality() const; static bool IsSupported(DicomTransferSyntax syntax); @@ -63,5 +64,11 @@ DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) ORTHANC_OVERRIDE; }; }
--- a/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h Mon Feb 24 08:03:02 2025 +0100 @@ -116,6 +116,12 @@ const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) = 0; + virtual bool Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) = 0; + static std::string GetSopInstanceUid(DcmFileFormat& dicom); }; }
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -56,6 +56,14 @@ #endif } + bool MemoryBufferTranscoder::Transcode(DicomImage& target, + DicomImage& source, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQualityNotUsed) + { + return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid); + } bool MemoryBufferTranscoder::Transcode(DicomImage& target, DicomImage& source,
--- a/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/MemoryBufferTranscoder.h Mon Feb 24 08:03:02 2025 +0100 @@ -43,5 +43,11 @@ DicomImage& source, const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool Transcode(DicomImage& target /* out */, + DicomImage& source, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQualityNotUsed) ORTHANC_OVERRIDE; }; }
--- a/OrthancFramework/Sources/RestApi/RestApiGetCall.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiGetCall.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -69,4 +69,31 @@ name + "\", found: " + found->second); } } + + uint32_t RestApiGetCall::GetUnsignedInteger32Argument(const std::string& name, + uint32_t defaultValue) const + { + HttpToolbox::Arguments::const_iterator found = getArguments_.find(name); + + uint32_t value; + + if (found == getArguments_.end()) + { + return defaultValue; + } + else if (found->second.empty()) + { + return true; + } + else if (SerializationToolbox::ParseUnsignedInteger32(value, found->second)) + { + return value; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Expected a Unsigned Int for GET argument \"" + + name + "\", found: " + found->second); + } + } + }
--- a/OrthancFramework/Sources/RestApi/RestApiGetCall.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiGetCall.h Mon Feb 24 08:03:02 2025 +0100 @@ -65,7 +65,10 @@ bool GetBooleanArgument(const std::string& name, bool defaultValue) const; - + + uint32_t GetUnsignedInteger32Argument(const std::string& name, + uint32_t defaultValue) const; + virtual bool ParseJsonRequest(Json::Value& result) const ORTHANC_OVERRIDE; }; }
--- a/OrthancServer/Resources/Configuration.json Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Resources/Configuration.json Mon Feb 24 08:03:02 2025 +0100 @@ -920,8 +920,10 @@ // that have a compressed transfer syntax (new in Orthanc 1.8.2). "IngestTranscodingOfCompressed" : true, - // The compression level that is used when transcoding to one of the - // lossy/JPEG transfer syntaxes (integer between 1 and 100). + // The default compression level that is used when transcoding to one + // of the lossy/JPEG transfer syntaxes (integer between 1 and 100). + // This value is currently only used by the default built-in DCMTK + // transcoder and is not provided to transcoding plugins. "DicomLossyTranscodingQuality" : 90, // Whether "fsync()" is called after each write to the storage area
--- a/OrthancServer/Sources/OrthancConfiguration.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/OrthancConfiguration.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -46,6 +46,7 @@ static const char* const DATABASE_SERVER_IDENTIFIER = "DatabaseServerIdentifier"; static const char* const WARNINGS = "Warnings"; static const char* const JOBS_ENGINE_THREADS_COUNT = "JobsEngineThreadsCount"; +static const char* const DICOM_LOSSY_TRANSCODING_QUALITY = "DicomLossyTranscodingQuality"; namespace Orthanc { @@ -700,6 +701,11 @@ } } + unsigned int OrthancConfiguration::GetDicomLossyTranscodingQuality() const + { + return GetUnsignedIntegerParameter(DICOM_LOSSY_TRANSCODING_QUALITY, 90); + } + bool OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const {
--- a/OrthancServer/Sources/OrthancConfiguration.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/OrthancConfiguration.h Mon Feb 24 08:03:02 2025 +0100 @@ -197,6 +197,8 @@ void GetListOfOrthancPeers(std::set<std::string>& target) const; + unsigned int GetDicomLossyTranscodingQuality() const; + // Returns "true" iff. at least one user is registered bool SetupRegisteredUsers(HttpServer& httpServer) const;
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -58,6 +58,8 @@ static const char* const SERIES = "Series"; static const char* const TAGS = "Tags"; static const char* const TRANSCODE = "Transcode"; +static const char* const LOSSY_QUALITY = "LossyQuality"; + namespace Orthanc @@ -88,6 +90,10 @@ .SetRequestField(TRANSCODE, RestApiCallDocumentation::Type_String, "Transcode the DICOM instances to the provided DICOM transfer syntax: " "https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetRequestField(LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, "Allow the modification of tags related to DICOM identifiers, at the risk of " "breaking the DICOM model of the real world", false) @@ -116,6 +122,10 @@ .SetRequestField(TRANSCODE, RestApiCallDocumentation::Type_String, "Transcode the DICOM instances to the provided DICOM transfer syntax: " "https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetRequestField(LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetRequestField(FORCE, RestApiCallDocumentation::Type_Boolean, "Allow the modification of tags related to DICOM identifiers, at the risk of " "breaking the DICOM model of the real world", false) @@ -196,7 +206,8 @@ static void AnonymizeOrModifyInstance(DicomModification& modification, RestApiPostCall& call, bool transcode, - DicomTransferSyntax targetSyntax) + DicomTransferSyntax targetSyntax, + unsigned int lossyQuality) { ServerContext& context = OrthancRestApi::GetContext(call); std::string id = call.GetUriComponent("id", ""); @@ -220,7 +231,7 @@ std::set<DicomTransferSyntax> s; s.insert(targetSyntax); - if (context.Transcode(transcoded, source, s, true)) + if (context.Transcode(transcoded, source, s, true, lossyQuality)) { call.GetOutput().AnswerBuffer(transcoded.GetBufferData(), transcoded.GetBufferSize(), MimeType_Dicom); @@ -259,6 +270,20 @@ } } + static unsigned int GetLossyQuality(const Json::Value& request) + { + unsigned int lossyQuality; + { + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + + if (request.isMember(LOSSY_QUALITY)) + { + lossyQuality = SerializationToolbox::ReadUnsignedInteger(request, LOSSY_QUALITY); + } + return lossyQuality; +} static void ModifyInstance(RestApiPostCall& call) { @@ -286,11 +311,11 @@ if (request.isMember(TRANSCODE)) { std::string s = SerializationToolbox::ReadString(request, TRANSCODE); - + DicomTransferSyntax syntax; if (LookupTransferSyntax(syntax, s)) { - AnonymizeOrModifyInstance(modification, call, true, syntax); + AnonymizeOrModifyInstance(modification, call, true, syntax, GetLossyQuality(request)); } else { @@ -300,7 +325,7 @@ else { AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, - DicomTransferSyntax_LittleEndianImplicit /* unused */); + DicomTransferSyntax_LittleEndianImplicit /* unused */, 0 /* unused */); } } @@ -326,8 +351,25 @@ Json::Value request; ParseAnonymizationRequest(request, modification, call); - AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, - DicomTransferSyntax_LittleEndianImplicit /* unused */); + if (request.isMember(TRANSCODE)) + { + std::string s = SerializationToolbox::ReadString(request, TRANSCODE); + + DicomTransferSyntax syntax; + if (LookupTransferSyntax(syntax, s)) + { + AnonymizeOrModifyInstance(modification, call, true, syntax, GetLossyQuality(request)); + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Unknown transfer syntax: " + s); + } + } + else + { + AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, + DicomTransferSyntax_LittleEndianImplicit /* unused */, 0 /* unused */); + } }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestArchive.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -42,9 +42,11 @@ static const char* const KEY_RESOURCES = "Resources"; static const char* const KEY_EXTENDED = "Extended"; static const char* const KEY_TRANSCODE = "Transcode"; + static const char* const KEY_LOSSY_QUALITY = "LossyQuality"; static const char* const KEY_FILENAME = "Filename"; static const char* const GET_TRANSCODE = "transcode"; + static const char* const GET_LOSSY_QUALITY = "lossy-quality"; static const char* const GET_FILENAME = "filename"; static const char* const GET_RESOURCES = "resources"; @@ -118,6 +120,7 @@ bool& extended, /* out */ bool& transcode, /* out */ DicomTransferSyntax& syntax, /* out */ + unsigned int& lossyQuality, /* out */ int& priority, /* out */ unsigned int& loaderThreads, /* out */ std::string& filename, /* out */ @@ -145,6 +148,16 @@ { transcode = true; syntax = Orthanc::GetTransferSyntax(SerializationToolbox::ReadString(body, KEY_TRANSCODE)); + + { + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + + if (body.isMember(KEY_LOSSY_QUALITY)) + { + lossyQuality = SerializationToolbox::ReadUnsignedInteger(body, KEY_LOSSY_QUALITY); + } } else { @@ -526,6 +539,10 @@ .SetRequestField(KEY_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM files in the archive will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetRequestField(KEY_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetRequestField(KEY_FILENAME, RestApiCallDocumentation::Type_String, "Filename to set in the \"Content-Disposition\" HTTP header " "(including file extension)", false) @@ -578,7 +595,9 @@ int priority; unsigned int loaderThreads; std::string filename; - GetJobParameters(synchronous, extended, transcode, transferSyntax, + unsigned int lossyQuality; + + GetJobParameters(synchronous, extended, transcode, transferSyntax, lossyQuality, priority, loaderThreads, filename, body, DEFAULT_IS_EXTENDED, "Archive.zip"); std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended, ResourceType_Patient)); @@ -587,6 +606,7 @@ if (transcode) { job->SetTranscode(transferSyntax); + job->SetLossyQuality(lossyQuality); } job->SetLoaderThreads(loaderThreads); @@ -599,7 +619,18 @@ "Expected a list of resources to archive in the body"); } } - + + static unsigned int GetLossyQuality(RestApiGetCall& call) + { + unsigned int lossyQuality; + + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + lossyQuality = call.GetUnsignedInteger32Argument(GET_LOSSY_QUALITY, lossyQuality); + + return lossyQuality; + } + template <bool IS_MEDIA, bool DEFAULT_IS_EXTENDED /* only makes sense for media (i.e. not ZIP archives) */ > @@ -619,6 +650,10 @@ .SetHttpGetArgument(GET_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM files will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetHttpGetArgument(GET_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetHttpGetArgument(GET_RESOURCES, RestApiCallDocumentation::Type_String, "A comma separated list of Orthanc resource identifiers to include in the " + m + ".", true); return; @@ -627,11 +662,13 @@ ServerContext& context = OrthancRestApi::GetContext(call); bool transcode = false; DicomTransferSyntax transferSyntax = DicomTransferSyntax_LittleEndianImplicit; // Initialize variable to avoid warnings + unsigned int lossyQuality; if (call.HasArgument(GET_TRANSCODE)) { transcode = true; transferSyntax = GetTransferSyntax(call.GetArgument(GET_TRANSCODE, "")); + lossyQuality = GetLossyQuality(call); } if (!call.HasArgument(GET_RESOURCES)) @@ -645,6 +682,7 @@ if (transcode) { job->SetTranscode(transferSyntax); + job->SetLossyQuality(lossyQuality); } const std::string filename = call.GetArgument(GET_FILENAME, "Archive.zip"); // New in Orthanc 1.12.7 @@ -677,6 +715,10 @@ .SetHttpGetArgument(GET_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM files in the archive will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetHttpGetArgument(GET_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .AddAnswerType(MimeType_Zip, "ZIP file containing the archive"); if (IS_MEDIA) { @@ -708,6 +750,7 @@ if (call.HasArgument(GET_TRANSCODE)) { job->SetTranscode(GetTransferSyntax(call.GetArgument(GET_TRANSCODE, ""))); + job->SetLossyQuality(GetLossyQuality(call)); } { @@ -752,7 +795,8 @@ int priority; unsigned int loaderThreads; std::string filename; - GetJobParameters(synchronous, extended, transcode, transferSyntax, + unsigned int lossyQuality; + GetJobParameters(synchronous, extended, transcode, transferSyntax, lossyQuality, priority, loaderThreads, filename, body, false /* by default, not extented */, id + ".zip"); std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended, LEVEL)); @@ -761,6 +805,7 @@ if (transcode) { job->SetTranscode(transferSyntax); + job->SetLossyQuality(lossyQuality); } job->SetLoaderThreads(loaderThreads);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -338,6 +338,7 @@ static void GetInstanceFile(RestApiGetCall& call) { static const char* const GET_TRANSCODE = "transcode"; + static const char* const GET_LOSSY_QUALITY = "lossy-quality"; static const char* const GET_FILENAME = "filename"; if (call.IsDocumentation()) @@ -351,6 +352,10 @@ .SetHttpGetArgument(GET_TRANSCODE, RestApiCallDocumentation::Type_String, "If present, the DICOM file will be transcoded to the provided " "transfer syntax: https://orthanc.uclouvain.be/book/faq/transcoding.html", false) + .SetHttpGetArgument(GET_LOSSY_QUALITY, RestApiCallDocumentation::Type_Number, + "If transcoding to a lossy transfer syntax, this entry defines the quality " + "as an integer between 1 and 100. If not provided, the value is defined " + "by the \"DicomLossyTranscodingQuality\" configuration. (new in v1.12.7)", false) .SetHttpGetArgument(GET_FILENAME, RestApiCallDocumentation::Type_String, "Filename to set in the \"Content-Disposition\" HTTP header " "(including file extension)", false) @@ -406,12 +411,34 @@ if (call.HasArgument(GET_TRANSCODE)) { + unsigned int lossyQuality; + unsigned int defaultLossyQuality; + { + OrthancConfiguration::ReaderLock lock; + defaultLossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + lossyQuality = call.GetUnsignedInteger32Argument(GET_LOSSY_QUALITY, defaultLossyQuality); + std::string source; std::string attachmentId; std::string transcoded; context.ReadDicom(source, attachmentId, publicId); - if (context.TranscodeWithCache(transcoded, source, publicId, attachmentId, GetTransferSyntax(call.GetArgument(GET_TRANSCODE, "")))) + if (lossyQuality != defaultLossyQuality) // we can't use the cache if the lossy quality is not the default one + { + IDicomTranscoder::DicomImage targetImage; + IDicomTranscoder::DicomImage sourceImage; + sourceImage.SetExternalBuffer(source); + std::set<DicomTransferSyntax> allowedSyntaxes; + allowedSyntaxes.insert(GetTransferSyntax(call.GetArgument(GET_TRANSCODE, ""))); + + if (context.Transcode(targetImage, sourceImage, allowedSyntaxes, true, lossyQuality)) + { + call.GetOutput().SetContentFilename(filename.c_str()); + call.GetOutput().AnswerBuffer(targetImage.GetBufferData(), targetImage.GetBufferSize(), MimeType_Dicom); + } + } + else if (context.TranscodeWithCache(transcoded, source, publicId, attachmentId, GetTransferSyntax(call.GetArgument(GET_TRANSCODE, "")))) { call.GetOutput().SetContentFilename(filename.c_str()); call.GetOutput().AnswerBuffer(transcoded, MimeType_Dicom);
--- a/OrthancServer/Sources/ServerContext.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -393,8 +393,6 @@ { try { - unsigned int lossyQuality; - { OrthancConfiguration::ReaderLock lock; @@ -425,7 +423,6 @@ // New options in Orthanc 1.7.0 transcodeDicomProtocol_ = lock.GetConfiguration().GetBooleanParameter("TranscodeDicomProtocol", true); builtinDecoderTranscoderOrder_ = StringToBuiltinDecoderTranscoderOrder(lock.GetConfiguration().GetStringParameter("BuiltinDecoderTranscoderOrder", "After")); - lossyQuality = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomLossyTranscodingQuality", 90); std::string s; if (lock.GetConfiguration().LookupStringParameter(s, "IngestTranscoding")) @@ -502,6 +499,8 @@ SetAcceptedSopClasses(acceptedSopClasses, rejectedSopClasses); defaultDicomRetrieveMethod_ = StringToRetrieveMethod(lock.GetConfiguration().GetStringParameter("DicomDefaultRetrieveMethod", "C-MOVE")); + + dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetDefaultLossyQuality(lock.GetConfiguration().GetDicomLossyTranscodingQuality()); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); @@ -516,8 +515,6 @@ #else LOG(INFO) << "Your platform does not support malloc_trim(), not starting the memory trimming thread"; #endif - - dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetLossyQuality(lossyQuality); } catch (OrthancException&) { @@ -1959,15 +1956,31 @@ return true; } - bool ServerContext::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) { + unsigned int lossyQuality; + + { + OrthancConfiguration::ReaderLock lock; + lossyQuality = lock.GetConfiguration().GetDicomLossyTranscodingQuality(); + } + + return Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); + } + + + bool ServerContext::Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) + { if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) { - if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) + if (dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality)) { return true; } @@ -1977,7 +1990,7 @@ if (HasPlugins() && GetPlugins().HasCustomTranscoder()) { - if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) + if (GetPlugins().Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid)) // TODO: pass lossyQuality to plugins -> needs a new plugin interface { return true; } @@ -1991,7 +2004,7 @@ if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) { - return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid); + return dcmtkTranscoder_->Transcode(target, source, allowedSyntaxes, allowNewSopInstanceUid, lossyQuality); } else {
--- a/OrthancServer/Sources/ServerContext.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Mon Feb 24 08:03:02 2025 +0100 @@ -577,6 +577,12 @@ DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool Transcode(DicomImage& target, + DicomImage& source /* in, "GetParsed()" possibly modified */, + const std::set<DicomTransferSyntax>& allowedSyntaxes, + bool allowNewSopInstanceUid, + unsigned int lossyQuality) ORTHANC_OVERRIDE; virtual bool TranscodeWithCache(std::string& target, const std::string& source,
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp Mon Feb 24 08:03:02 2025 +0100 @@ -88,11 +88,13 @@ ServerContext& context_; bool transcode_; DicomTransferSyntax transferSyntax_; + unsigned int lossyQuality_; public: - explicit InstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax) + explicit InstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax, unsigned int lossyQuality) : context_(context), transcode_(transcode), - transferSyntax_(transferSyntax) + transferSyntax_(transferSyntax), + lossyQuality_(lossyQuality) { } @@ -114,7 +116,7 @@ IDicomTranscoder::DicomImage source, transcoded; source.SetExternalBuffer(sourceBuffer); - if (context_.Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */)) + if (context_.Transcode(transcoded, source, syntaxes, true /* allow new SOP instance UID */, lossyQuality_)) { transcodedBuffer.assign(reinterpret_cast<const char*>(transcoded.GetBufferData()), transcoded.GetBufferSize()); return true; @@ -139,8 +141,8 @@ class ArchiveJob::SynchronousInstanceLoader : public ArchiveJob::InstanceLoader { public: - explicit SynchronousInstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax) - : InstanceLoader(context, transcode, transferSyntax) + explicit SynchronousInstanceLoader(ServerContext& context, bool transcode, DicomTransferSyntax transferSyntax, unsigned int lossyQuality) + : InstanceLoader(context, transcode, transferSyntax, lossyQuality) { } @@ -192,8 +194,8 @@ public: - ThreadedInstanceLoader(ServerContext& context, size_t threadCount, bool transcode, DicomTransferSyntax transferSyntax) - : InstanceLoader(context, transcode, transferSyntax), + ThreadedInstanceLoader(ServerContext& context, size_t threadCount, bool transcode, DicomTransferSyntax transferSyntax, unsigned int lossyQuality) + : InstanceLoader(context, transcode, transferSyntax, lossyQuality), availableInstancesSemaphore_(0), bufferedInstancesSemaphore_(3*threadCount) { @@ -1250,6 +1252,7 @@ archiveSize_(0), transcode_(false), transferSyntax_(DicomTransferSyntax_LittleEndianImplicit), + lossyQuality_(100), loaderThreads_(0) { } @@ -1349,7 +1352,20 @@ } } - + + void ArchiveJob::SetLossyQuality(unsigned int lossyQuality) + { + if (writer_.get() != NULL) // Already started + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + lossyQuality_ = lossyQuality; + } + } + + void ArchiveJob::SetLoaderThreads(unsigned int loaderThreads) { if (writer_.get() != NULL) // Already started @@ -1375,11 +1391,11 @@ if (loaderThreads_ == 0) { // default behaviour before loaderThreads was introducted in 1.10.0 - instanceLoader_.reset(new SynchronousInstanceLoader(context_, transcode_, transferSyntax_)); + instanceLoader_.reset(new SynchronousInstanceLoader(context_, transcode_, transferSyntax_, lossyQuality_)); } else { - instanceLoader_.reset(new ThreadedInstanceLoader(context_, loaderThreads_, transcode_, transferSyntax_)); + instanceLoader_.reset(new ThreadedInstanceLoader(context_, loaderThreads_, transcode_, transferSyntax_, lossyQuality_)); } if (writer_.get() != NULL)
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h Mon Feb 24 08:01:43 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h Mon Feb 24 08:03:02 2025 +0100 @@ -69,6 +69,7 @@ // New in Orthanc 1.7.0 bool transcode_; DicomTransferSyntax transferSyntax_; + unsigned int lossyQuality_; // New in Orthanc 1.10.0 unsigned int loaderThreads_; @@ -105,6 +106,8 @@ void SetTranscode(DicomTransferSyntax transferSyntax); + void SetLossyQuality(unsigned int lossyQuality); + void SetLoaderThreads(unsigned int loaderThreads); virtual void Reset() ORTHANC_OVERRIDE;