# HG changeset patch # User Sebastien Jodogne # Date 1584615512 -3600 # Node ID b07fc9ed0dc5bde69b10ce37915a53981772700f # Parent d912b0b1628d53b397035d6f1f98ccb3cd8eac4d# Parent 7f4a75093a6afebb357431182f42a173efe354d0 integration mainline->transcoding diff -r 7f4a75093a6a -r b07fc9ed0dc5 CMakeLists.txt --- a/CMakeLists.txt Thu Mar 19 10:23:29 2020 +0100 +++ b/CMakeLists.txt Thu Mar 19 11:58:32 2020 +0100 @@ -26,7 +26,7 @@ set(ENABLE_ZLIB ON) # To test transcoding -#set(ENABLE_DCMTK_TRANSCODING ON) +set(ENABLE_DCMTK_TRANSCODING ON) set(HAS_EMBEDDED_RESOURCES ON) diff -r 7f4a75093a6a -r b07fc9ed0dc5 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Thu Mar 19 10:23:29 2020 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Thu Mar 19 11:58:32 2020 +0100 @@ -121,6 +121,13 @@ #endif +#include +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include +# include // include to support color images +#endif + + namespace Orthanc { static bool IsBinaryTag(const DcmTag& key) @@ -1198,6 +1205,54 @@ } } + + + static bool SaveToMemoryBufferInternal(std::string& buffer, + DcmFileFormat& dicom, + E_TransferSyntax xfer) + { + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + + // Create a memory buffer with the proper size + { + const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType); // (*) + buffer.resize(estimatedSize); + } + + DcmOutputBufferStream ob(&buffer[0], buffer.size()); + + // Fill the memory buffer with the meta-header and the dataset + dicom.transferInit(); + OFCondition c = dicom.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_noChange, + /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0, + EWM_updateMeta /* creates new SOP instance UID on lossy */); + dicom.transferEnd(); + + if (c.good()) + { + // The DICOM file is successfully written, truncate the target + // buffer if its size was overestimated by (*) + ob.flush(); + + size_t effectiveSize = static_cast(ob.tell()); + if (effectiveSize < buffer.size()) + { + buffer.resize(effectiveSize); + } + + return true; + } + else + { + // Error + buffer.clear(); + return false; + } + } + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet) { @@ -1222,47 +1277,51 @@ xfer = EXS_LittleEndianExplicit; } - E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; - // Create the meta-header information DcmFileFormat ff(&dataSet); ff.validateMetaInfo(xfer); ff.removeInvalidGroups(); - // Create a memory buffer with the proper size + return SaveToMemoryBufferInternal(buffer, ff, xfer); + } + + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom) + { + E_TransferSyntax xfer = dicom.getDataset()->getOriginalXfer(); + if (xfer == EXS_Unknown) { - const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) - buffer.resize(estimatedSize); + throw OrthancException(ErrorCode_InternalError, + "Cannot write a DICOM instance with unknown transfer syntax"); } - - DcmOutputBufferStream ob(&buffer[0], buffer.size()); - - // Fill the memory buffer with the meta-header and the dataset - ff.transferInit(); - OFCondition c = ff.write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding); - ff.transferEnd(); - - if (c.good()) + else if (!dicom.validateMetaInfo(xfer).good()) { - // The DICOM file is successfully written, truncate the target - // buffer if its size was overestimated by (*) - ob.flush(); - - size_t effectiveSize = static_cast(ob.tell()); - if (effectiveSize < buffer.size()) - { - buffer.resize(effectiveSize); - } - - return true; + throw OrthancException(ErrorCode_InternalError, + "Cannot setup the transfer syntax to write a DICOM instance"); } else { - // Error - buffer.clear(); - return false; + return SaveToMemoryBufferInternal(buffer, dicom, xfer); + } + } + + + bool FromDcmtkBridge::Transcode(std::string& buffer, + DcmFileFormat& dicom, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation) + { + E_TransferSyntax xfer; + if (!LookupDcmtkTransferSyntax(xfer, syntax)) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + return (dicom.getDataset()->chooseRepresentation(xfer, representation).good() && + dicom.getDataset()->canWriteXfer(xfer) && + SaveToMemoryBufferInternal(buffer, dicom, xfer)); } } @@ -2073,6 +2132,12 @@ DJEncoderRegistration::registerCodecs(); # endif #endif + + LOG(INFO) << "Registering RLE codecs in DCMTK"; + DcmRLEDecoderRegistration::registerCodecs(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::registerCodecs(); +#endif } @@ -2093,6 +2158,11 @@ DJEncoderRegistration::cleanup(); # endif #endif + + DcmRLEDecoderRegistration::cleanup(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::cleanup(); +#endif } diff -r 7f4a75093a6a -r b07fc9ed0dc5 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Thu Mar 19 10:23:29 2020 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.h Thu Mar 19 11:58:32 2020 +0100 @@ -205,6 +205,14 @@ static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet); + static bool SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom); + + static bool Transcode(std::string& buffer, + DcmFileFormat& dicom, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation); + static ValueRepresentation Convert(DcmEVR vr); static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); diff -r 7f4a75093a6a -r b07fc9ed0dc5 Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Thu Mar 19 10:23:29 2020 +0100 +++ b/Resources/CMake/DcmtkConfiguration.cmake Thu Mar 19 11:58:32 2020 +0100 @@ -36,6 +36,14 @@ ) endif() + if (ENABLE_DCMTK_TRANSCODING) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimgle/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimage/libsrc DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmimage/include + ) + endif() + if (ENABLE_DCMTK_JPEG) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES) @@ -56,17 +64,21 @@ ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8/jaricom.c ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12/jaricom.c ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg24/jaricom.c + ) - # Disable support for encoding JPEG (modification in Orthanc 1.0.1) - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc - ) + if (NOT ENABLE_DCMTK_TRANSCODING) + list(REMOVE_ITEM DCMTK_SOURCES + # Disable support for encoding JPEG (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc + ) + endif() endif() @@ -78,15 +90,18 @@ ${DCMTK_SOURCES_DIR}/dcmjpls/include ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls ) - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc - - # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) - ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc - ) list(APPEND DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc ) + + if (NOT ENABLE_DCMTK_TRANSCODING) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc + + # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc + ) + endif() endif() diff -r 7f4a75093a6a -r b07fc9ed0dc5 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Thu Mar 19 10:23:29 2020 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Thu Mar 19 11:58:32 2020 +0100 @@ -1922,6 +1922,8 @@ #include #include #include +#include // for DJ_RPLossy +#include // for DJ_RPLossless namespace Orthanc @@ -1946,8 +1948,11 @@ virtual void GetCompressedFrame(std::string& target, unsigned int frame) = 0; - virtual IDicomTranscoder* Transcode(std::set syntaxes, - bool allowNewSopInstanceUid) = 0; + virtual bool Transcode(std::string& target, + std::set syntaxes, + bool allowNewSopInstanceUid) = 0; + + virtual void WriteToMemoryBuffer(std::string& target) = 0; }; @@ -1959,9 +1964,13 @@ DicomTransferSyntax transferSyntax_; std::string sopClassUid_; std::string sopInstanceUid_; + uint16_t bitsStored_; + unsigned int lossyQuality_; void Setup(DcmFileFormat* dicom) { + lossyQuality_ = 90; + dicom_.reset(dicom); if (dicom == NULL || @@ -1992,6 +2001,12 @@ "Unsupported transfer syntax: " + boost::lexical_cast(xfer)); } + if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good()) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing \"Bits Stored\" tag in DICOM instance"); + } + const char* a = NULL; const char* b = NULL; @@ -2020,6 +2035,29 @@ Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); } + void SetLossyQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + lossyQuality_ = quality; + } + } + + unsigned int GetLossyQuality() const + { + return lossyQuality_; + } + + unsigned int GetBitsStored() const + { + return bitsStored_; + } + virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE { return transferSyntax_; @@ -2040,6 +2078,15 @@ return index_->GetFramesCount(); } + virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE + { + if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_)) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot write the DICOM instance to a memory buffer"); + } + } + virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE { assert(dicom_->getDataset() != NULL); @@ -2049,48 +2096,51 @@ virtual void GetCompressedFrame(std::string& target, unsigned int frame) ORTHANC_OVERRIDE { -#if 1 index_->GetRawFrame(target, frame); - printf("%d: %d\n", frame, target.size()); -#endif - -#if 1 - assert(dicom_->getDataset() != NULL); - DcmDataset& dataset = *dicom_->getDataset(); - - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); - - if (pixelSequence != NULL && - frame == 0 && - pixelSequence->card() != GetFramesCount() + 1) + } + + virtual bool Transcode(std::string& target, + std::set syntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE + { + if (syntaxes.find(GetTransferSyntax()) != syntaxes.end()) { - printf("COMPRESSED\n"); - - // Check out "djcodecd.cc" - - printf("%d fragments\n", pixelSequence->card()); + printf("NO TRANSCODING\n"); - // Skip the first fragment, that is the offset table - for (unsigned long i = 1; ;i++) - { - DcmPixelItem *fragment = NULL; - if (pixelSequence->getItem(fragment, i).good()) - { - printf("fragment %d %d\n", i, fragment->getLength()); - } - else - { - break; - } - } + // No change in the transfer syntax => simply serialize the current dataset + WriteToMemoryBuffer(target); + return true; + } + + printf(">> %d\n", bitsStored_); + + DJ_RPLossy rpLossy(lossyQuality_); + + if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != syntaxes.end() && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianImplicit, NULL)) + { + return true; } -#endif - } - - virtual IDicomTranscoder* Transcode(std::set syntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - throw OrthancException(ErrorCode_NotImplemented); + else if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != syntaxes.end() && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianExplicit, NULL)) + { + return true; + } + else if (syntaxes.find(DicomTransferSyntax_BigEndianExplicit) != syntaxes.end() && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_BigEndianExplicit, NULL)) + { + return true; + } + else if (syntaxes.find(DicomTransferSyntax_JPEGProcess1) != syntaxes.end() && + allowNewSopInstanceUid && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess1, &rpLossy)) + { + return true; + } + else + { + return false; + } } }; } @@ -2167,14 +2217,16 @@ } } -#include "dcmtk/dcmjpeg/djrploss.h" /* for DJ_RPLossy */ -#include "dcmtk/dcmjpeg/djrplol.h" /* for DJ_RPLossless */ #include static void TestFile(const std::string& path) { + static unsigned int count = 0; + count++; + + printf("** %s\n", path.c_str()); std::string s; @@ -2182,9 +2234,19 @@ Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); - printf("[%s] [%s] [%s] %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), + if (transcoder.GetBitsStored() != 8) // TODO + return; + + { + char buf[1024]; + sprintf(buf, "/tmp/source-%06d.dcm", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(s, buf); + } + + printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(), - transcoder.GetFramesCount()); + transcoder.GetFramesCount(), transcoder.GetTransferSyntax()); for (size_t i = 0; i < transcoder.GetFramesCount(); i++) { @@ -2193,27 +2255,57 @@ if (i == 0) { - static unsigned int i = 0; char buf[1024]; - sprintf(buf, "/tmp/frame-%06d.dcm", i++); + sprintf(buf, "/tmp/frame-%06d.raw", count); printf(">> %s\n", buf); Orthanc::SystemToolbox::WriteFile(f, buf); } } + { + std::string t; + transcoder.WriteToMemoryBuffer(t); + + Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size()); + } + + { + std::set syntaxes; + syntaxes.insert(DicomTransferSyntax_JPEGProcess1); + + std::string t; + bool ok = transcoder.Transcode(t, syntaxes, false); + printf("Transcoding: %d\n", ok); + + if (ok) + { + { + char buf[1024]; + sprintf(buf, "/tmp/transcoded-%06d.dcm", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(t, buf); + } + + Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); + } + } + printf("\n"); } -TEST(Toto, Transcode) +TEST(Toto, DISABLED_Transcode) { + //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); + if (0) { - OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); - std::string s; //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm"); //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm"); - SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm"); + //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm"); + SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size())); @@ -2225,7 +2317,7 @@ #if 0 E_TransferSyntax target = EXS_LittleEndianExplicit; p = NULL; -#elif 1 +#elif 0 E_TransferSyntax target = EXS_JPEGProcess14SV1; DJ_RPLossless rp_lossless(6, 0); p = &rp_lossless; @@ -2245,7 +2337,7 @@ SystemToolbox::WriteFile(t, "target.dcm"); } - if (1) + if (0) { const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes"; @@ -2257,10 +2349,18 @@ TestFile(it->path().string()); } } - + } + + if (0) + { TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm"); TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm"); } + + if (1) + { + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); + } } #endif