# HG changeset patch # User Sebastien Jodogne # Date 1588674573 -7200 # Node ID 9045757384624dccf21a18d7f573a287d5401471 # Parent f23ab7829a8dcc375f17d87e15187f115178ba79 implemented IDicomTranscoder::Store() diff -r f23ab7829a8d -r 904575738462 Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Tue May 05 10:35:39 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.cpp Tue May 05 12:29:33 2020 +0200 @@ -178,11 +178,17 @@ void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid, std::string& sopInstanceUid, - DcmDataset& dataset) + DicomTransferSyntax& transferSyntax, + DcmFileFormat& dicom) { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + OFString a, b; - if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() || - !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good()) + if (!dicom.getDataset()->findAndGetOFString(DCM_SOPClassUID, a).good() || + !dicom.getDataset()->findAndGetOFString(DCM_SOPInstanceUID, b).good()) { throw OrthancException(ErrorCode_NoSopClassOrInstance, "Unable to determine the SOP class/instance for C-STORE with AET " + @@ -191,6 +197,12 @@ sopClassUid.assign(a.c_str()); sopInstanceUid.assign(b.c_str()); + + if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom)) + { + throw OrthancException(ErrorCode_InternalError, + "Unknown transfer syntax from DCMTK"); + } } @@ -308,18 +320,12 @@ void DicomStoreUserConnection::Store(std::string& sopClassUid, std::string& sopInstanceUid, - DcmDataset& dataset, + DcmFileFormat& dicom, const std::string& moveOriginatorAET, uint16_t moveOriginatorID) { - LookupParameters(sopClassUid, sopInstanceUid, dataset); - DicomTransferSyntax transferSyntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dataset)) - { - throw OrthancException(ErrorCode_InternalError, - "Unknown transfer syntax from DCMTK"); - } + LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom); uint8_t presID; if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax)) @@ -351,12 +357,17 @@ request.opts |= O_STORE_MOVEORIGINATORID; } + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + // Finally conduct transmission of data T_DIMSE_C_StoreRSP response; DcmDataset* statusDetail = NULL; DicomAssociation::CheckCondition( DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request, - NULL, &dataset, /*progressCallback*/ NULL, NULL, + NULL, dicom.getDataset(), /*progressCallback*/ NULL, NULL, /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), /*opt_dimse_timeout*/ GetParameters().GetTimeout(), &response, &statusDetail, NULL), @@ -397,18 +408,16 @@ std::unique_ptr dicom( FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); - if (dicom.get() == NULL || - dicom->getDataset() == NULL) + if (dicom.get() == NULL) { throw OrthancException(ErrorCode_InternalError); } - Store(sopClassUid, sopInstanceUid, *dicom->getDataset(), - moveOriginatorAET, moveOriginatorID); + Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID); } - bool DicomStoreUserConnection::LookupTranscoding(std::set& acceptedSyntaxes, + void DicomStoreUserConnection::LookupTranscoding(std::set& acceptedSyntaxes, const std::string& sopClassUid, DicomTransferSyntax sourceSyntax) { @@ -428,12 +437,6 @@ { acceptedSyntaxes.insert(it->first); } - - return true; - } - else - { - return false; } } } diff -r f23ab7829a8d -r 904575738462 Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Tue May 05 10:35:39 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Tue May 05 12:29:33 2020 +0200 @@ -41,7 +41,7 @@ #include // For uint8_t -class DcmDataset; +class DcmFileFormat; namespace Orthanc { @@ -86,10 +86,6 @@ bool ProposeStorageClass(const std::string& sopClassUid, const std::set& syntaxes); - void LookupParameters(std::string& sopClassUid, - std::string& sopInstanceUid, - DcmDataset& dataset); - bool LookupPresentationContext(uint8_t& presentationContextId, const std::string& sopClassUid, DicomTransferSyntax transferSyntax); @@ -141,7 +137,7 @@ void Store(std::string& sopClassUid, std::string& sopInstanceUid, - DcmDataset& dataset, + DcmFileFormat& dicom, const std::string& moveOriginatorAET, uint16_t moveOriginatorID); @@ -154,9 +150,9 @@ void Store(std::string& sopClassUid, std::string& sopInstanceUid, - DcmDataset& dataset) + DcmFileFormat& dicom) { - Store(sopClassUid, sopInstanceUid, dataset, "", 0); // Not a C-Move + Store(sopClassUid, sopInstanceUid, dicom, "", 0); // Not a C-Move } void Store(std::string& sopClassUid, @@ -167,7 +163,12 @@ Store(sopClassUid, sopInstanceUid, buffer, size, "", 0); // Not a C-Move } - bool LookupTranscoding(std::set& acceptedSyntaxes, + void LookupParameters(std::string& sopClassUid, + std::string& sopInstanceUid, + DicomTransferSyntax& transferSyntax, + DcmFileFormat& dicom); + + void LookupTranscoding(std::set& acceptedSyntaxes, const std::string& sopClassUid, DicomTransferSyntax sourceSyntax); }; diff -r f23ab7829a8d -r 904575738462 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Tue May 05 10:35:39 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Tue May 05 12:29:33 2020 +0200 @@ -2657,6 +2657,33 @@ Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); } + + + + bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, + DcmFileFormat& dicom) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DcmDataset& dataset = *dicom.getDataset(); + + E_TransferSyntax xfer = dataset.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + dataset.updateOriginalXfer(); + xfer = dataset.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Cannot determine the transfer syntax of the DICOM instance"); + } + } + + return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer); + } } diff -r f23ab7829a8d -r 904575738462 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Tue May 05 10:35:39 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.h Tue May 05 12:29:33 2020 +0200 @@ -286,9 +286,6 @@ E_TransferSyntax source); static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target, - const DcmDataset& dataset) - { - return LookupOrthancTransferSyntax(target, dataset.getOriginalXfer()); - } + DcmFileFormat& dicom); }; } diff -r f23ab7829a8d -r 904575738462 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Tue May 05 10:35:39 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Tue May 05 12:29:33 2020 +0200 @@ -2252,10 +2252,10 @@ - class IDicomTranscoder : public boost::noncopyable + class IDicomTranscoder1 : public boost::noncopyable { public: - virtual ~IDicomTranscoder() + virtual ~IDicomTranscoder1() { } @@ -2284,7 +2284,7 @@ }; - class DcmtkTranscoder : public IDicomTranscoder + class DcmtkTranscoder2 : public IDicomTranscoder1 { private: std::unique_ptr dicom_; @@ -2357,12 +2357,12 @@ } public: - DcmtkTranscoder(DcmFileFormat* dicom) // Takes ownership + DcmtkTranscoder2(DcmFileFormat* dicom) // Takes ownership { Setup(dicom); } - DcmtkTranscoder(const void* dicom, + DcmtkTranscoder2(const void* dicom, size_t size) { Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); @@ -2529,76 +2529,6 @@ -static bool Transcode(std::string& buffer, - DcmDataset& dataSet, - E_TransferSyntax xfer) -{ - // Determine the transfer syntax which shall be used to write the - // information to the file. We always switch to the Little Endian - // syntax, with explicit length. - - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - - - /** - * Note that up to Orthanc 0.7.1 (inclusive), the - * "EXS_LittleEndianExplicit" was always used to save the DICOM - * dataset into memory. We now keep the original transfer syntax - * (if available). - **/ - //E_TransferSyntax xfer = dataSet.getOriginalXfer(); - if (xfer == EXS_Unknown) - { - // No information about the original transfer syntax: This is - // most probably a DICOM dataset that was read from memory. - 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 - { - const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) - buffer.resize(estimatedSize); - } - - 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()) - { - // 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; - } -} - - #include @@ -2613,7 +2543,7 @@ std::string s; SystemToolbox::ReadFile(s, path); - Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); + Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size()); /*if (transcoder.GetBitsStored() != 8) // TODO return; */ @@ -2647,7 +2577,7 @@ std::string t; transcoder.WriteToMemoryBuffer(t); - Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size()); printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size()); } @@ -2675,7 +2605,7 @@ Orthanc::SystemToolbox::WriteFile(t, buf); } - Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size()); printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); } } @@ -2687,44 +2617,6 @@ { //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); - if (0) - { - 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/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); - - std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size())); - - // less /home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcxfer.h - printf(">> %d\n", dicom->getDataset()->getOriginalXfer()); // => 4 == EXS_JPEGProcess1 - - const DcmRepresentationParameter *p; - -#if 0 - E_TransferSyntax target = EXS_LittleEndianExplicit; - p = NULL; -#elif 0 - E_TransferSyntax target = EXS_JPEGProcess14SV1; - DJ_RPLossless rp_lossless(6, 0); - p = &rp_lossless; -#else - E_TransferSyntax target = EXS_JPEGProcess1; - DJ_RPLossy rp_lossy(90); // quality - p = &rp_lossy; -#endif - - ASSERT_TRUE(dicom->getDataset()->chooseRepresentation(target, p).good()); - ASSERT_TRUE(dicom->getDataset()->canWriteXfer(target)); - - std::string t; - ASSERT_TRUE(Transcode(t, *dicom->getDataset(), target)); - - SystemToolbox::WriteFile(s, "source.dcm"); - SystemToolbox::WriteFile(t, "target.dcm"); - } - if (1) { const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes"; @@ -2877,7 +2769,8 @@ { std::set accepted; - if (!scu.LookupTranscoding(accepted, sopClassUid, transferSyntax)) + scu.LookupTranscoding(accepted, sopClassUid, transferSyntax); + if (accepted.empty()) { throw OrthancException(ErrorCode_NetworkProtocol, "The SOP class is not supported by the remote modality"); @@ -2966,4 +2859,262 @@ printf("[%s] [%s]\n", c.c_str(), i.c_str()); } + +namespace Orthanc +{ + class IDicomTranscoder : public boost::noncopyable + { + public: + virtual ~IDicomTranscoder() + { + } + + virtual DcmFileFormat* Transcode(const void* buffer, + size_t size, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) = 0; + + // In-place transcoding. This method can return "false" if not supported, + // in which case the "Transcode()" method should be used. + 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.InplaceTranscode(*dicom, uncompressedSyntaxes, false)) + { + // In-place transcoding is supported + transcoded.reset(dicom.release()); + } + else + { + transcoded.reset(transcoder.Transcode(buffer, size, uncompressedSyntaxes, false)); + } + + // The "dicom" variable must not be used below this point + + 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); + } + } + } + } + }; + + + 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"); + } + } + + 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* Transcode(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; + } + } + + virtual bool InplaceTranscode(DcmFileFormat& dicom, + const std::set& allowedSyntaxes, + bool allowNewSopInstanceUid) + { + if (dicom.getDataset() == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + const uint16_t bitsStored = GetBitsStored(*dicom.getDataset()); + +#if 0 + + if (syntax == DetectTransferSyntax(*dicom)) + { + // No transcoding is needed + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_LittleEndianImplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_LittleEndianExplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_BigEndianExplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + + if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit && + FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL)) + { + return new Image(dicom.release(), syntax); + } + +#if ORTHANC_ENABLE_JPEG == 1 + if (syntax == DicomTransferSyntax_JPEGProcess1 && + allowNewSopInstanceUid && + bitsStored == 8) + { + DJ_RPLossy rpLossy(lossyQuality_); + + if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy)) + { + return new Image(dicom.release(), syntax); + } + } #endif + +#if ORTHANC_ENABLE_JPEG == 1 + if (syntax == DicomTransferSyntax_JPEGProcess2_4 && + allowNewSopInstanceUid && + bitsStored <= 12) + { + DJ_RPLossy rpLossy(lossyQuality_); + if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy)) + { + return new Image(dicom.release(), syntax); + } + } +#endif + +#endif + + return false; + } + }; +} + + +#endif