Mercurial > hg > orthanc
view OrthancFramework/Sources/DicomParsing/DcmtkTranscoder.cpp @ 5873:c8788f8f5322
todo
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Mon, 18 Nov 2024 15:16:16 +0100 |
parents | f7adfb22e20e |
children |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2023 Osimis S.A., Belgium * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/>. **/ #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 "../Logging.h" #include "../OrthancException.h" #include "../Toolbox.h" #include <dcmtk/dcmdata/dcdeftag.h> #include <dcmtk/dcmjpeg/djrploss.h> // for DJ_RPLossy #include <dcmtk/dcmjpeg/djrplol.h> // for DJ_RPLossless #include <dcmtk/dcmjpls/djrparam.h> // for DJLSRepresentationParameter #include <boost/lexical_cast.hpp> namespace Orthanc { DcmtkTranscoder::DcmtkTranscoder() : lossyQuality_(90) { } static bool GetBitsStored(uint16_t& bitsStored, DcmDataset& dataset) { return dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good(); } void DcmtkTranscoder::SetLossyQuality(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: " + boost::lexical_cast<std::string>(quality)); } else { LOG(INFO) << "Quality for lossy transcoding using DCMTK is set to: " << quality; lossyQuality_ = quality; } } unsigned int DcmtkTranscoder::GetLossyQuality() const { return lossyQuality_; } bool TryTranscode(std::vector<std::string>& failureReasons, /* out */ DicomTransferSyntax& selectedSyntax, /* out*/ DcmFileFormat& dicom, /* in/out */ const std::set<DicomTransferSyntax>& allowedSyntaxes, DicomTransferSyntax trySyntax) { if (allowedSyntaxes.find(trySyntax) != allowedSyntaxes.end()) { if (FromDcmtkBridge::Transcode(dicom, trySyntax, NULL)) { selectedSyntax = trySyntax; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(trySyntax)); } return false; } bool DcmtkTranscoder::InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */, std::string& failureReason /* out */, DcmFileFormat& dicom, /* in/out */ const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) { std::vector<std::string> failureReasons; if (dicom.getDataset() == NULL) { throw OrthancException(ErrorCode_InternalError); } DicomTransferSyntax syntax; if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom)) { throw OrthancException(ErrorCode_BadFileFormat, "Cannot determine the transfer syntax"); } uint16_t bitsStored; bool hasBitsStored = GetBitsStored(bitsStored, *dicom.getDataset()); if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end()) { // No transcoding is needed return true; } if (TryTranscode(failureReasons, selectedSyntax, dicom, allowedSyntaxes, DicomTransferSyntax_LittleEndianImplicit)) { return true; } if (TryTranscode(failureReasons, selectedSyntax, dicom, allowedSyntaxes, DicomTransferSyntax_LittleEndianExplicit)) { return true; } if (TryTranscode(failureReasons, selectedSyntax, dicom, allowedSyntaxes, DicomTransferSyntax_BigEndianExplicit)) { return true; } if (TryTranscode(failureReasons, selectedSyntax, dicom, allowedSyntaxes, DicomTransferSyntax_DeflatedLittleEndianExplicit)) { return true; } #if ORTHANC_ENABLE_DCMTK_JPEG == 1 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end()) { if (!allowNewSopInstanceUid) { failureReasons.push_back(std::string("Can not transcode to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess1) + " without generating new SOPInstanceUID"); } else if (hasBitsStored && bitsStored != 8) { failureReasons.push_back(std::string("Can not transcode to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess1) + " if BitsStored != 8"); } else { // Check out "dcmjpeg/apps/dcmcjpeg.cc" DJ_RPLossy parameters(lossyQuality_); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) { selectedSyntax = DicomTransferSyntax_JPEGProcess1; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess1)); } } #endif #if ORTHANC_ENABLE_DCMTK_JPEG == 1 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end()) { if (!allowNewSopInstanceUid) { failureReasons.push_back(std::string("Can not transcode to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess2_4) + " without generating new SOPInstanceUID"); } else if (hasBitsStored && bitsStored > 12) { failureReasons.push_back(std::string("Can not transcode to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess2_4) + " if BitsStored != 8"); } else { // Check out "dcmjpeg/apps/dcmcjpeg.cc" DJ_RPLossy parameters(lossyQuality_); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) { selectedSyntax = DicomTransferSyntax_JPEGProcess2_4; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess2_4)); } } #endif #if ORTHANC_ENABLE_DCMTK_JPEG == 1 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14) != allowedSyntaxes.end()) { // Check out "dcmjpeg/apps/dcmcjpeg.cc" DJ_RPLossless parameters(6 /* opt_selection_value */, 0 /* opt_point_transform */); if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14, ¶meters)) { selectedSyntax = DicomTransferSyntax_JPEGProcess14; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess14)); } #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)) { selectedSyntax = DicomTransferSyntax_JPEGProcess14SV1; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGProcess14SV1)); } #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)) { selectedSyntax = DicomTransferSyntax_JPEGLSLossless; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGLSLossless)); } #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)) { selectedSyntax = DicomTransferSyntax_JPEGLSLossy; return true; } failureReasons.push_back(std::string("Internal error while transcoding to ") + GetTransferSyntaxUid(DicomTransferSyntax_JPEGLSLossy)); } #endif Orthanc::Toolbox::JoinStrings(failureReason, failureReasons, ", "); return false; } bool DcmtkTranscoder::IsSupported(DicomTransferSyntax syntax) { if (syntax == DicomTransferSyntax_LittleEndianImplicit || syntax == DicomTransferSyntax_LittleEndianExplicit || syntax == DicomTransferSyntax_BigEndianExplicit || syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit) { return true; } #if ORTHANC_ENABLE_DCMTK_JPEG == 1 if (syntax == DicomTransferSyntax_JPEGProcess1 || syntax == DicomTransferSyntax_JPEGProcess2_4 || syntax == DicomTransferSyntax_JPEGProcess14 || syntax == DicomTransferSyntax_JPEGProcess14SV1) { return true; } #endif #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 if (syntax == DicomTransferSyntax_JPEGLSLossless || syntax == DicomTransferSyntax_JPEGLSLossy) { return true; } #endif return false; } bool DcmtkTranscoder::Transcode(DicomImage& target, DicomImage& source /* in, "GetParsed()" possibly modified */, const std::set<DicomTransferSyntax>& allowedSyntaxes, bool allowNewSopInstanceUid) { target.Clear(); DicomTransferSyntax sourceSyntax; if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, source.GetParsed())) { LOG(ERROR) << "Unsupport transfer syntax for transcoding"; return false; } std::string failureReason; std::string s; for (std::set<DicomTransferSyntax>::const_iterator it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it) { if (!s.empty()) { s += ", "; } s += GetTransferSyntaxUid(*it); } if (s.empty()) { s = "<none>"; } LOG(INFO) << "DCMTK transcoding from " << GetTransferSyntaxUid(sourceSyntax) << " to one of: " << s; #if !defined(NDEBUG) const std::string sourceSopInstanceUid = GetSopInstanceUid(source.GetParsed()); #endif DicomTransferSyntax targetSyntax; if (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end()) { // No transcoding is needed target.AcquireParsed(source); target.AcquireBuffer(source); return true; } else if (InplaceTranscode(targetSyntax, failureReason, source.GetParsed(), allowedSyntaxes, allowNewSopInstanceUid)) { // Sanity check DicomTransferSyntax targetSyntax2; if (FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax2, source.GetParsed()) && targetSyntax == targetSyntax2 && allowedSyntaxes.find(targetSyntax2) != allowedSyntaxes.end()) { target.AcquireParsed(source); source.Clear(); #if !defined(NDEBUG) // Only run the sanity check in debug mode CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid, allowedSyntaxes, allowNewSopInstanceUid); #endif return true; } else { throw OrthancException(ErrorCode_InternalError); } } else { // Cannot transcode LOG(WARNING) << "DCMTK was unable to transcode from " << GetTransferSyntaxUid(sourceSyntax) << " to one of: " << s << " " << failureReason; return false; } } }