# HG changeset patch # User Sebastien Jodogne # Date 1588762108 -7200 # Node ID 7a5fa8f307e95bbeff6a9bc30ec00e0ab17cfed6 # Parent 5571082a9df6e01cfac811e22e5db8a61a5b811c reorganization diff -r 5571082a9df6 -r 7a5fa8f307e9 CMakeLists.txt --- a/CMakeLists.txt Tue May 05 18:08:51 2020 +0200 +++ b/CMakeLists.txt Wed May 06 12:48:28 2020 +0200 @@ -13,6 +13,7 @@ set(ENABLE_CRYPTO_OPTIONS ON) set(ENABLE_DCMTK ON) set(ENABLE_DCMTK_NETWORKING ON) +set(ENABLE_DCMTK_TRANSCODING ON) set(ENABLE_GOOGLE_TEST ON) set(ENABLE_JPEG ON) set(ENABLE_LOCALE ON) @@ -25,9 +26,6 @@ set(ENABLE_WEB_SERVER ON) set(ENABLE_ZLIB ON) -# To test transcoding -set(ENABLE_DCMTK_TRANSCODING ON) - set(HAS_EMBEDDED_RESOURCES ON) diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Tue May 05 18:08:51 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp Wed May 06 12:48:28 2020 +0200 @@ -439,4 +439,97 @@ } } } + + + void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + IDicomTranscoder& transcoder, + const void* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); + if (dicom.get() == NULL || + dicom->getDataset() == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + DicomTransferSyntax inputSyntax; + LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom); + + std::set accepted; + LookupTranscoding(accepted, sopClassUid, inputSyntax); + + if (accepted.find(inputSyntax) != accepted.end()) + { + // No need for transcoding + Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID); + } + else + { + // Transcoding is needed + std::set uncompressedSyntaxes; + + if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end()) + { + uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); + } + + if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end()) + { + uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); + } + + if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end()) + { + uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); + } + + std::unique_ptr transcoded; + + if (transcoder.HasInplaceTranscode()) + { + if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false)) + { + // In-place transcoding is supported and has succeeded + transcoded.reset(dicom.release()); + } + } + else + { + transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false)); + } + + // WARNING: The "dicom" variable must not be used below this + // point. The "sopInstanceUid" might also have changed (if + // using lossy compression). + + if (transcoded == NULL || + transcoded->getDataset() == NULL) + { + throw OrthancException( + ErrorCode_NotImplemented, + "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) + + "\" to an uncompressed syntax for modality: " + + GetParameters().GetRemoteModality().GetApplicationEntityTitle()); + } + else + { + DicomTransferSyntax transcodedSyntax; + + // Sanity check + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) || + accepted.find(transcodedSyntax) == accepted.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID); + } + } + } + } } diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Tue May 05 18:08:51 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Wed May 06 12:48:28 2020 +0200 @@ -33,8 +33,16 @@ #pragma once +#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) +# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file +#endif + #include "DicomAssociationParameters.h" +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include "../DicomParsing/IDicomTranscoder.h" +#endif + #include #include #include @@ -94,6 +102,10 @@ const std::string& sopClassUid, DicomTransferSyntax transferSyntax); + void LookupTranscoding(std::set& acceptedSyntaxes, + const std::string& sopClassUid, + DicomTransferSyntax sourceSyntax); + public: DicomStoreUserConnection(const DicomAssociationParameters& params); @@ -168,8 +180,22 @@ DicomTransferSyntax& transferSyntax, DcmFileFormat& dicom); - void LookupTranscoding(std::set& acceptedSyntaxes, - const std::string& sopClassUid, - DicomTransferSyntax sourceSyntax); + void Transcode(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + IDicomTranscoder& transcoder, + const void* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Transcode(std::string& sopClassUid /* out */, + std::string& sopInstanceUid /* out */, + IDicomTranscoder& transcoder, + const void* buffer, + size_t size) + { + Transcode(sopClassUid, sopInstanceUid, transcoder, + buffer, size, "", 0); // Not a C-Move + } }; } diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomParsing/DcmtkTranscoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DcmtkTranscoder.cpp Wed May 06 12:48:28 2020 +0200 @@ -0,0 +1,320 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" +#include "DcmtkTranscoder.h" + + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) +# error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) +# error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined +#endif + + +#include "FromDcmtkBridge.h" +#include "../OrthancException.h" + +#include +#include // for DJ_RPLossy +#include // for DJ_RPLossless +#include // for DJLSRepresentationParameter + + +namespace Orthanc +{ + static uint16_t GetBitsStored(DcmDataset& dataset) + { + uint16_t bitsStored; + if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good()) + { + return bitsStored; + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing \"Bits Stored\" tag in DICOM instance"); + } + } + + + static std::string GetSopInstanceUid(DcmDataset& dataset) + { + const char* v = NULL; + + if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() && + v != NULL) + { + return std::string(v); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID"); + } + } + + + static void CheckSopInstanceUid(DcmFileFormat& dicom, + const std::string& sopInstanceUid, + bool mustEqual) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + bool ok; + + if (mustEqual) + { + ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid); + } + else + { + ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid); + } + + if (!ok) + { + throw OrthancException(ErrorCode_InternalError, + mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" : + "The SOP instance UID has not changed as expected during transcoding"); + } + } + + + void DcmtkTranscoder::SetLossyQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + lossyQuality_ = quality; + } + } + + + DcmFileFormat* DcmtkTranscoder::TranscodeToParsed(const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); + + if (dicom.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid)) + { + return dicom.release(); + } + else + { + return NULL; + } + } + + + bool DcmtkTranscoder::InplaceTranscode(DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomTransferSyntax syntax; + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom)) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax"); + } + + const uint16_t bitsStored = GetBitsStored(*dicom.getDataset()); + std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset()); + + if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end()) + { + // No transcoding is needed + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + + if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() && + FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() && + allowNewSopInstanceUid && + bitsStored == 8) + { + // Check out "dcmjpeg/apps/dcmcjpeg.cc" + DJ_RPLossy parameters(lossyQuality_); + + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() && + allowNewSopInstanceUid && + bitsStored <= 12) + { + // Check out "dcmjpeg/apps/dcmcjpeg.cc" + DJ_RPLossy parameters(lossyQuality_); + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end()) + { + // Check out "dcmjpeg/apps/dcmcjpeg.cc" + DJ_RPLossless parameters(6 /* opt_selection_value */, + 0 /* opt_point_transform */); + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end()) + { + // Check out "dcmjpls/apps/dcmcjpls.cc" + DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, + OFTrue /* opt_useLosslessProcess */); + + /** + * WARNING: This call results in a segmentation fault if using + * the DCMTK package 3.6.2 from Ubuntu 18.04. + **/ + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); + return true; + } + } +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + if (allowNewSopInstanceUid && + allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end()) + { + // Check out "dcmjpls/apps/dcmcjpls.cc" + DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, + OFFalse /* opt_useLosslessProcess */); + + /** + * WARNING: This call results in a segmentation fault if using + * the DCMTK package 3.6.2 from Ubuntu 18.04. + **/ + if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, ¶meters)) + { + CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); + return true; + } + } +#endif + + return false; + } + + + bool DcmtkTranscoder::TranscodeToBuffer(std::string& target, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + std::unique_ptr transcoded( + TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid)); + + if (transcoded.get() == NULL) + { + return false; + } + else + { + if (transcoded->getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset()); + return true; + } + } +} diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomParsing/DcmtkTranscoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DcmtkTranscoder.h Wed May 06 12:48:28 2020 +0200 @@ -0,0 +1,86 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) +# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file +#endif + +#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1 +# error Transcoding is disabled, cannot compile this file +#endif + +#include "IDicomTranscoder.h" + +namespace Orthanc +{ + class DcmtkTranscoder : public IDicomTranscoder + { + private: + unsigned int lossyQuality_; + + public: + DcmtkTranscoder() : + lossyQuality_(90) + { + } + + void SetLossyQuality(unsigned int quality); + + unsigned int GetLossyQuality() const + { + return lossyQuality_; + } + + virtual DcmFileFormat* TranscodeToParsed(const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool HasInplaceTranscode() const + { + return true; + } + + virtual bool InplaceTranscode(DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool TranscodeToBuffer(std::string& target, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + }; +} diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomParsing/IDicomTranscoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/IDicomTranscoder.h Wed May 06 12:48:28 2020 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include +#include + +class DcmFileFormat; + +namespace Orthanc +{ + /** + * WARNING: This class might be called from several threads at + * once. Make sure to implement proper locking. + **/ + + class IDicomTranscoder : public boost::noncopyable + { + public: + virtual ~IDicomTranscoder() + { + } + + virtual bool TranscodeToBuffer(std::string& target, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + + /** + * Transcoding flavor that creates a new parsed DICOM file. A + * "std::set<>" is used to give the possible plugin the + * possibility to do a single parsing for all the possible + * transfer syntaxes. + **/ + virtual DcmFileFormat* TranscodeToParsed(const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + + virtual bool HasInplaceTranscode() const = 0; + + /** + * In-place transcoding. This method is preferred for C-STORE. + **/ + virtual bool InplaceTranscode(DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + }; +} diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomParsing/MemoryBufferTranscoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/MemoryBufferTranscoder.cpp Wed May 06 12:48:28 2020 +0200 @@ -0,0 +1,116 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "../PrecompiledHeaders.h" +#include "MemoryBufferTranscoder.h" + +#include "../OrthancException.h" +#include "FromDcmtkBridge.h" + +namespace Orthanc +{ + MemoryBufferTranscoder::MemoryBufferTranscoder(bool tryDcmtk) : + tryDcmtk_(tryDcmtk) + { +#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1 + if (tryDcmtk) + { + throw OrthancException(ErrorCode_NotImplemented, + "Orthanc was built without support for DMCTK transcoding"); + } +#endif + } + + bool MemoryBufferTranscoder::TranscodeToBuffer(std::string& target, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + if (tryDcmtk_) + { + return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } + else +#endif + { + return Transcode(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } + } + + + DcmFileFormat* MemoryBufferTranscoder::TranscodeToParsed(const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + if (tryDcmtk_) + { + return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid); + } + else +#endif + { + std::string transcoded; + if (Transcode(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)) + { + return FromDcmtkBridge::LoadFromMemoryBuffer( + transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size()); + } + else + { + return NULL; + } + } + } + + + bool MemoryBufferTranscoder::InplaceTranscode(DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + if (tryDcmtk_) + { + return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid); + } + else +#endif + { + // "HasInplaceTranscode()" should have been called + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } +} diff -r 5571082a9df6 -r 7a5fa8f307e9 Core/DicomParsing/MemoryBufferTranscoder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/MemoryBufferTranscoder.h Wed May 06 12:48:28 2020 +0200 @@ -0,0 +1,90 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) +# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file +#endif + +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include "DcmtkTranscoder.h" +#endif + +namespace Orthanc +{ + // This is the basis class for transcoding plugins + class MemoryBufferTranscoder : public IDicomTranscoder + { + private: + bool tryDcmtk_; + +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmtkTranscoder dcmtk_; +#endif + + protected: + virtual bool Transcode(std::string& target, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + + public: + /** + * If "tryDcmtk" is "true", the transcoder will first try and call + * DCMTK, before calling its own "Transcode()" implementation. + **/ + MemoryBufferTranscoder(bool tryDcmtk); + + virtual bool TranscodeToBuffer(std::string& target, + const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual DcmFileFormat* TranscodeToParsed(const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + + virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE + { + return tryDcmtk_; + } + + virtual bool InplaceTranscode(DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; + }; +} diff -r 5571082a9df6 -r 7a5fa8f307e9 Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Tue May 05 18:08:51 2020 +0200 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed May 06 12:48:28 2020 +0200 @@ -501,6 +501,10 @@ # New in Orthanc 1.6.0 if (ENABLE_DCMTK_TRANSCODING) add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=1) + list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL + ${ORTHANC_ROOT}/Core/DicomParsing/DcmtkTranscoder.cpp + ${ORTHANC_ROOT}/Core/DicomParsing/MemoryBufferTranscoder.cpp + ) else() add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=0) endif() diff -r 5571082a9df6 -r 7a5fa8f307e9 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Tue May 05 18:08:51 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Wed May 06 12:48:28 2020 +0200 @@ -1925,535 +1925,7 @@ #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 #include "../Core/DicomNetworking/DicomStoreUserConnection.h" - -#include // for DJ_RPLossy -#include // for DJ_RPLossless -#include // for DJLSRepresentationParameter - - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) -# error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) -# error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined -#endif - - - -namespace Orthanc -{ - /** - * WARNING: This class might be called from several threads at - * once. Make sure to implement proper locking. - **/ - - class IDicomTranscoder : public boost::noncopyable - { - public: - virtual ~IDicomTranscoder() - { - } - - virtual bool TranscodeToBuffer(std::string& target, - const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; - - /** - * Transcoding flavor that creates a new parsed DICOM file. A - * "std::set<>" is used to give the possible plugin the - * possibility to do a single parsing for all the possible - * transfer syntaxes. - **/ - virtual DcmFileFormat* TranscodeToParsed(const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; - - virtual bool HasInplaceTranscode() const = 0; - - /** - * In-place transcoding. This method is preferred for C-STORE. - **/ - virtual bool InplaceTranscode(DcmFileFormat& dicom, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; - - /** - * Important: Transcoding over the DICOM protocol is only - * implemented towards uncompressed transfer syntaxes. - **/ - static void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - DicomStoreUserConnection& connection, - IDicomTranscoder& transcoder, - const void* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); - if (dicom.get() == NULL || - dicom->getDataset() == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - - DicomTransferSyntax inputSyntax; - connection.LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom); - - std::set accepted; - connection.LookupTranscoding(accepted, sopClassUid, inputSyntax); - - if (accepted.find(inputSyntax) != accepted.end()) - { - // No need for transcoding - connection.Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID); - } - else - { - // Transcoding is needed - std::set uncompressedSyntaxes; - - if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end()) - { - uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); - } - - if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end()) - { - uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); - } - - if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end()) - { - uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); - } - - std::unique_ptr transcoded; - - if (transcoder.HasInplaceTranscode()) - { - if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false)) - { - // In-place transcoding is supported and has succeeded - transcoded.reset(dicom.release()); - } - } - else - { - transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false)); - } - - // WARNING: The "dicom" variable must not be used below this - // point. The "sopInstanceUid" might also have changed (if - // using lossy compression). - - if (transcoded == NULL || - transcoded->getDataset() == NULL) - { - throw OrthancException( - ErrorCode_NotImplemented, - "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) + - "\" to an uncompressed syntax for modality: " + - connection.GetParameters().GetRemoteModality().GetApplicationEntityTitle()); - } - else - { - DicomTransferSyntax transcodedSyntax; - - // Sanity check - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) || - accepted.find(transcodedSyntax) == accepted.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - connection.Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID); - } - } - } - } - - static void Store(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - DicomStoreUserConnection& connection, - IDicomTranscoder& transcoder, - const void* buffer, - size_t size) - { - Store(sopClassUid, sopInstanceUid, connection, transcoder, - buffer, size, "", 0 /* Not a C-MOVE */); - } - }; - - - class DcmtkTranscoder : public IDicomTranscoder - { - private: - unsigned int lossyQuality_; - - static uint16_t GetBitsStored(DcmDataset& dataset) - { - uint16_t bitsStored; - if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good()) - { - return bitsStored; - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, - "Missing \"Bits Stored\" tag in DICOM instance"); - } - } - - static std::string GetSopInstanceUid(DcmDataset& dataset) - { - const char* v = NULL; - - if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() && - v != NULL) - { - return std::string(v); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID"); - } - } - - static void CheckSopInstanceUid(DcmFileFormat& dicom, - const std::string& sopInstanceUid, - bool mustEqual) - { - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - bool ok; - - if (mustEqual) - { - ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid); - } - else - { - ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid); - } - - if (!ok) - { - throw OrthancException(ErrorCode_InternalError, - mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" : - "The SOP instance UID has not changed as expected during transcoding"); - } - } - - public: - DcmtkTranscoder() : - lossyQuality_(90) - { - } - - void SetLossyQuality(unsigned int quality) - { - if (quality <= 0 || - quality > 100) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - lossyQuality_ = quality; - } - } - - unsigned int GetLossyQuality() const - { - return lossyQuality_; - } - - virtual DcmFileFormat* TranscodeToParsed(const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); - - if (dicom.get() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid)) - { - return dicom.release(); - } - else - { - return NULL; - } - } - - virtual bool HasInplaceTranscode() const - { - return true; - } - - virtual bool InplaceTranscode(DcmFileFormat& dicom, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - DicomTransferSyntax syntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom)) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot determine the transfer syntax"); - } - - const uint16_t bitsStored = GetBitsStored(*dicom.getDataset()); - std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset()); - - if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end()) - { - // No transcoding is needed - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); - return true; - } - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() && - allowNewSopInstanceUid && - bitsStored == 8) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); - - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() && - allowNewSopInstanceUid && - bitsStored <= 12) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end()) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossless parameters(6 /* opt_selection_value */, - 0 /* opt_point_transform */); - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, ¶meters)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end()) - { - // Check out "dcmjpls/apps/dcmcjpls.cc" - DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, - OFTrue /* opt_useLosslessProcess */); - - /** - * WARNING: This call results in a segmentation fault if using - * the DCMTK package 3.6.2 from Ubuntu 18.04. - **/ - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, ¶meters)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, true); - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - if (allowNewSopInstanceUid && - allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end()) - { - // Check out "dcmjpls/apps/dcmcjpls.cc" - DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, - OFFalse /* opt_useLosslessProcess */); - - /** - * WARNING: This call results in a segmentation fault if using - * the DCMTK package 3.6.2 from Ubuntu 18.04. - **/ - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, ¶meters)) - { - CheckSopInstanceUid(dicom, sourceSopInstanceUid, false); - return true; - } - } -#endif - - return false; - } - - - virtual bool TranscodeToBuffer(std::string& target, - const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - std::unique_ptr transcoded( - TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid)); - - if (transcoded.get() == NULL) - { - return false; - } - else - { - if (transcoded->getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset()); - return true; - } - } - }; - - - - class PluginDicomTranscoder: public IDicomTranscoder - { - private: - bool tryDcmtk_; - DcmtkTranscoder dcmtk_; - - protected: - virtual bool TranscodeInternal(std::string& target, - const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; - - public: - PluginDicomTranscoder(bool tryDcmtk) : - tryDcmtk_(tryDcmtk) - { - } - - virtual bool TranscodeToBuffer(std::string& target, - const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - if (tryDcmtk_) - { - return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); - } - else - { - return TranscodeInternal(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid); - } - } - - virtual DcmFileFormat* TranscodeToParsed(const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - if (tryDcmtk_) - { - return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid); - } - else - { - std::string transcoded; - if (TranscodeInternal(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid)) - { - return FromDcmtkBridge::LoadFromMemoryBuffer( - transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size()); - } - else - { - return NULL; - } - } - } - - virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE - { - return tryDcmtk_; - } - - virtual bool InplaceTranscode(DcmFileFormat& dicom, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - if (tryDcmtk_) - { - return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid); - } - else - { - // "HasInplaceTranscode()" should have been called - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - }; -} - +#include "../Core/DicomParsing/DcmtkTranscoder.h" TEST(Toto, DISABLED_Transcode3) { @@ -2483,7 +1955,7 @@ std::string c, i; try { - IDicomTranscoder::Store(c, i, scu, transcoder, source.c_str(), source.size()); + scu.Transcode(c, i, transcoder, source.c_str(), source.size()); } catch (OrthancException& e) { @@ -2501,8 +1973,6 @@ } - - TEST(Toto, DISABLED_Transcode4) { std::string source; @@ -2534,5 +2004,4 @@ } } - #endif