comparison UnitTestsSources/FromDcmtkTests.cpp @ 3882:904575738462 transcoding

implemented IDicomTranscoder::Store()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 05 May 2020 12:29:33 +0200
parents cdd0cb5ec4e4
children 795c9ca5eb91
comparison
equal deleted inserted replaced
3881:f23ab7829a8d 3882:904575738462
2250 } 2250 }
2251 }; 2251 };
2252 2252
2253 2253
2254 2254
2255 class IDicomTranscoder : public boost::noncopyable 2255 class IDicomTranscoder1 : public boost::noncopyable
2256 { 2256 {
2257 public: 2257 public:
2258 virtual ~IDicomTranscoder() 2258 virtual ~IDicomTranscoder1()
2259 { 2259 {
2260 } 2260 }
2261 2261
2262 virtual DcmFileFormat& GetDicom() = 0; 2262 virtual DcmFileFormat& GetDicom() = 0;
2263 2263
2282 2282
2283 virtual void WriteToMemoryBuffer(std::string& target) = 0; 2283 virtual void WriteToMemoryBuffer(std::string& target) = 0;
2284 }; 2284 };
2285 2285
2286 2286
2287 class DcmtkTranscoder : public IDicomTranscoder 2287 class DcmtkTranscoder2 : public IDicomTranscoder1
2288 { 2288 {
2289 private: 2289 private:
2290 std::unique_ptr<DcmFileFormat> dicom_; 2290 std::unique_ptr<DcmFileFormat> dicom_;
2291 std::unique_ptr<DicomFrameIndex> index_; 2291 std::unique_ptr<DicomFrameIndex> index_;
2292 DicomTransferSyntax transferSyntax_; 2292 DicomTransferSyntax transferSyntax_;
2355 sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID); 2355 sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID);
2356 sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID); 2356 sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID);
2357 } 2357 }
2358 2358
2359 public: 2359 public:
2360 DcmtkTranscoder(DcmFileFormat* dicom) // Takes ownership 2360 DcmtkTranscoder2(DcmFileFormat* dicom) // Takes ownership
2361 { 2361 {
2362 Setup(dicom); 2362 Setup(dicom);
2363 } 2363 }
2364 2364
2365 DcmtkTranscoder(const void* dicom, 2365 DcmtkTranscoder2(const void* dicom,
2366 size_t size) 2366 size_t size)
2367 { 2367 {
2368 Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); 2368 Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
2369 } 2369 }
2370 2370
2527 } 2527 }
2528 2528
2529 2529
2530 2530
2531 2531
2532 static bool Transcode(std::string& buffer,
2533 DcmDataset& dataSet,
2534 E_TransferSyntax xfer)
2535 {
2536 // Determine the transfer syntax which shall be used to write the
2537 // information to the file. We always switch to the Little Endian
2538 // syntax, with explicit length.
2539
2540 // http://support.dcmtk.org/docs/dcxfer_8h-source.html
2541
2542
2543 /**
2544 * Note that up to Orthanc 0.7.1 (inclusive), the
2545 * "EXS_LittleEndianExplicit" was always used to save the DICOM
2546 * dataset into memory. We now keep the original transfer syntax
2547 * (if available).
2548 **/
2549 //E_TransferSyntax xfer = dataSet.getOriginalXfer();
2550 if (xfer == EXS_Unknown)
2551 {
2552 // No information about the original transfer syntax: This is
2553 // most probably a DICOM dataset that was read from memory.
2554 xfer = EXS_LittleEndianExplicit;
2555 }
2556
2557 E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
2558
2559 // Create the meta-header information
2560 DcmFileFormat ff(&dataSet);
2561 ff.validateMetaInfo(xfer);
2562 ff.removeInvalidGroups();
2563
2564 // Create a memory buffer with the proper size
2565 {
2566 const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*)
2567 buffer.resize(estimatedSize);
2568 }
2569
2570 DcmOutputBufferStream ob(&buffer[0], buffer.size());
2571
2572 // Fill the memory buffer with the meta-header and the dataset
2573 ff.transferInit();
2574 OFCondition c = ff.write(ob, xfer, encodingType, NULL,
2575 /*opt_groupLength*/ EGL_recalcGL,
2576 /*opt_paddingType*/ EPD_withoutPadding);
2577 ff.transferEnd();
2578
2579 if (c.good())
2580 {
2581 // The DICOM file is successfully written, truncate the target
2582 // buffer if its size was overestimated by (*)
2583 ob.flush();
2584
2585 size_t effectiveSize = static_cast<size_t>(ob.tell());
2586 if (effectiveSize < buffer.size())
2587 {
2588 buffer.resize(effectiveSize);
2589 }
2590
2591 return true;
2592 }
2593 else
2594 {
2595 // Error
2596 buffer.clear();
2597 return false;
2598 }
2599 }
2600
2601
2602 #include <boost/filesystem.hpp> 2532 #include <boost/filesystem.hpp>
2603 2533
2604 2534
2605 static void TestFile(const std::string& path) 2535 static void TestFile(const std::string& path)
2606 { 2536 {
2611 printf("** %s\n", path.c_str()); 2541 printf("** %s\n", path.c_str());
2612 2542
2613 std::string s; 2543 std::string s;
2614 SystemToolbox::ReadFile(s, path); 2544 SystemToolbox::ReadFile(s, path);
2615 2545
2616 Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); 2546 Orthanc::DcmtkTranscoder2 transcoder(s.c_str(), s.size());
2617 2547
2618 /*if (transcoder.GetBitsStored() != 8) // TODO 2548 /*if (transcoder.GetBitsStored() != 8) // TODO
2619 return; */ 2549 return; */
2620 2550
2621 { 2551 {
2645 2575
2646 { 2576 {
2647 std::string t; 2577 std::string t;
2648 transcoder.WriteToMemoryBuffer(t); 2578 transcoder.WriteToMemoryBuffer(t);
2649 2579
2650 Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); 2580 Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
2651 printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size()); 2581 printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size());
2652 } 2582 }
2653 2583
2654 { 2584 {
2655 std::string a = transcoder.GetSopInstanceUid(); 2585 std::string a = transcoder.GetSopInstanceUid();
2673 sprintf(buf, "/tmp/transcoded-%06d.dcm", count); 2603 sprintf(buf, "/tmp/transcoded-%06d.dcm", count);
2674 printf(">> %s\n", buf); 2604 printf(">> %s\n", buf);
2675 Orthanc::SystemToolbox::WriteFile(t, buf); 2605 Orthanc::SystemToolbox::WriteFile(t, buf);
2676 } 2606 }
2677 2607
2678 Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); 2608 Orthanc::DcmtkTranscoder2 transcoder2(t.c_str(), t.size());
2679 printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); 2609 printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size());
2680 } 2610 }
2681 } 2611 }
2682 2612
2683 printf("\n"); 2613 printf("\n");
2684 } 2614 }
2685 2615
2686 TEST(Toto, DISABLED_Transcode) 2616 TEST(Toto, DISABLED_Transcode)
2687 { 2617 {
2688 //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); 2618 //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
2689
2690 if (0)
2691 {
2692 std::string s;
2693 //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm");
2694 //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm");
2695 //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm");
2696 SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm");
2697
2698 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size()));
2699
2700 // less /home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcxfer.h
2701 printf(">> %d\n", dicom->getDataset()->getOriginalXfer()); // => 4 == EXS_JPEGProcess1
2702
2703 const DcmRepresentationParameter *p;
2704
2705 #if 0
2706 E_TransferSyntax target = EXS_LittleEndianExplicit;
2707 p = NULL;
2708 #elif 0
2709 E_TransferSyntax target = EXS_JPEGProcess14SV1;
2710 DJ_RPLossless rp_lossless(6, 0);
2711 p = &rp_lossless;
2712 #else
2713 E_TransferSyntax target = EXS_JPEGProcess1;
2714 DJ_RPLossy rp_lossy(90); // quality
2715 p = &rp_lossy;
2716 #endif
2717
2718 ASSERT_TRUE(dicom->getDataset()->chooseRepresentation(target, p).good());
2719 ASSERT_TRUE(dicom->getDataset()->canWriteXfer(target));
2720
2721 std::string t;
2722 ASSERT_TRUE(Transcode(t, *dicom->getDataset(), target));
2723
2724 SystemToolbox::WriteFile(s, "source.dcm");
2725 SystemToolbox::WriteFile(t, "target.dcm");
2726 }
2727 2619
2728 if (1) 2620 if (1)
2729 { 2621 {
2730 const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes"; 2622 const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";
2731 2623
2875 const std::string& sopClassUid, 2767 const std::string& sopClassUid,
2876 DicomTransferSyntax transferSyntax) 2768 DicomTransferSyntax transferSyntax)
2877 { 2769 {
2878 std::set<DicomTransferSyntax> accepted; 2770 std::set<DicomTransferSyntax> accepted;
2879 2771
2880 if (!scu.LookupTranscoding(accepted, sopClassUid, transferSyntax)) 2772 scu.LookupTranscoding(accepted, sopClassUid, transferSyntax);
2773 if (accepted.empty())
2881 { 2774 {
2882 throw OrthancException(ErrorCode_NetworkProtocol, 2775 throw OrthancException(ErrorCode_NetworkProtocol,
2883 "The SOP class is not supported by the remote modality"); 2776 "The SOP class is not supported by the remote modality");
2884 } 2777 }
2885 2778
2964 std::string c, i; 2857 std::string c, i;
2965 assoc.Store(c, i, s.c_str(), s.size()); 2858 assoc.Store(c, i, s.c_str(), s.size());
2966 printf("[%s] [%s]\n", c.c_str(), i.c_str()); 2859 printf("[%s] [%s]\n", c.c_str(), i.c_str());
2967 } 2860 }
2968 2861
2862
2863 namespace Orthanc
2864 {
2865 class IDicomTranscoder : public boost::noncopyable
2866 {
2867 public:
2868 virtual ~IDicomTranscoder()
2869 {
2870 }
2871
2872 virtual DcmFileFormat* Transcode(const void* buffer,
2873 size_t size,
2874 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2875 bool allowNewSopInstanceUid) = 0;
2876
2877 // In-place transcoding. This method can return "false" if not supported,
2878 // in which case the "Transcode()" method should be used.
2879 virtual bool InplaceTranscode(DcmFileFormat& dicom,
2880 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2881 bool allowNewSopInstanceUid) = 0;
2882
2883 /**
2884 * Important: Transcoding over the DICOM protocol is only
2885 * implemented towards uncompressed transfer syntaxes.
2886 **/
2887 static void Store(std::string& sopClassUid /* out */,
2888 std::string& sopInstanceUid /* out */,
2889 DicomStoreUserConnection& connection,
2890 IDicomTranscoder& transcoder,
2891 const void* buffer,
2892 size_t size,
2893 const std::string& moveOriginatorAET,
2894 uint16_t moveOriginatorID)
2895 {
2896 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
2897 if (dicom.get() == NULL ||
2898 dicom->getDataset() == NULL)
2899 {
2900 throw OrthancException(ErrorCode_NullPointer);
2901 }
2902
2903 DicomTransferSyntax inputSyntax;
2904 connection.LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
2905
2906 std::set<DicomTransferSyntax> accepted;
2907 connection.LookupTranscoding(accepted, sopClassUid, inputSyntax);
2908
2909 if (accepted.find(inputSyntax) != accepted.end())
2910 {
2911 // No need for transcoding
2912 connection.Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID);
2913 }
2914 else
2915 {
2916 // Transcoding is needed
2917 std::set<DicomTransferSyntax> uncompressedSyntaxes;
2918
2919 if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
2920 {
2921 uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
2922 }
2923
2924 if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
2925 {
2926 uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
2927 }
2928
2929 if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
2930 {
2931 uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
2932 }
2933
2934 std::unique_ptr<DcmFileFormat> transcoded;
2935
2936 if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false))
2937 {
2938 // In-place transcoding is supported
2939 transcoded.reset(dicom.release());
2940 }
2941 else
2942 {
2943 transcoded.reset(transcoder.Transcode(buffer, size, uncompressedSyntaxes, false));
2944 }
2945
2946 // The "dicom" variable must not be used below this point
2947
2948 if (transcoded == NULL ||
2949 transcoded->getDataset() == NULL)
2950 {
2951 throw OrthancException(
2952 ErrorCode_NotImplemented,
2953 "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) +
2954 "\" to an uncompressed syntax for modality: " +
2955 connection.GetParameters().GetRemoteModality().GetApplicationEntityTitle());
2956 }
2957 else
2958 {
2959 DicomTransferSyntax transcodedSyntax;
2960
2961 // Sanity check
2962 if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) ||
2963 accepted.find(transcodedSyntax) == accepted.end())
2964 {
2965 throw OrthancException(ErrorCode_InternalError);
2966 }
2967 else
2968 {
2969 connection.Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID);
2970 }
2971 }
2972 }
2973 }
2974 };
2975
2976
2977 class DcmtkTranscoder : public IDicomTranscoder
2978 {
2979 private:
2980 unsigned int lossyQuality_;
2981
2982 static uint16_t GetBitsStored(DcmDataset& dataset)
2983 {
2984 uint16_t bitsStored;
2985 if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good())
2986 {
2987 return bitsStored;
2988 }
2989 else
2990 {
2991 throw OrthancException(ErrorCode_BadFileFormat,
2992 "Missing \"Bits Stored\" tag in DICOM instance");
2993 }
2994 }
2995
2996 public:
2997 DcmtkTranscoder() :
2998 lossyQuality_(90)
2999 {
3000 }
3001
3002 void SetLossyQuality(unsigned int quality)
3003 {
3004 if (quality <= 0 ||
3005 quality > 100)
3006 {
3007 throw OrthancException(ErrorCode_ParameterOutOfRange);
3008 }
3009 else
3010 {
3011 lossyQuality_ = quality;
3012 }
3013 }
3014
3015 unsigned int GetLossyQuality() const
3016 {
3017 return lossyQuality_;
3018 }
3019
3020 virtual DcmFileFormat* Transcode(const void* buffer,
3021 size_t size,
3022 const std::set<DicomTransferSyntax>& allowedSyntaxes,
3023 bool allowNewSopInstanceUid)
3024 {
3025 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
3026
3027 if (dicom.get() == NULL)
3028 {
3029 throw OrthancException(ErrorCode_InternalError);
3030 }
3031
3032 if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid))
3033 {
3034 return dicom.release();
3035 }
3036 else
3037 {
3038 return NULL;
3039 }
3040 }
3041
3042 virtual bool InplaceTranscode(DcmFileFormat& dicom,
3043 const std::set<DicomTransferSyntax>& allowedSyntaxes,
3044 bool allowNewSopInstanceUid)
3045 {
3046 if (dicom.getDataset() == NULL)
3047 {
3048 throw OrthancException(ErrorCode_InternalError);
3049 }
3050
3051 const uint16_t bitsStored = GetBitsStored(*dicom.getDataset());
3052
3053 #if 0
3054
3055 if (syntax == DetectTransferSyntax(*dicom))
3056 {
3057 // No transcoding is needed
3058 return new Image(dicom.release(), syntax);
3059 }
3060
3061 if (syntax == DicomTransferSyntax_LittleEndianImplicit &&
3062 FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
3063 {
3064 return new Image(dicom.release(), syntax);
3065 }
3066
3067 if (syntax == DicomTransferSyntax_LittleEndianExplicit &&
3068 FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
3069 {
3070 return new Image(dicom.release(), syntax);
3071 }
3072
3073 if (syntax == DicomTransferSyntax_BigEndianExplicit &&
3074 FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
3075 {
3076 return new Image(dicom.release(), syntax);
3077 }
3078
3079 if (syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit &&
3080 FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
3081 {
3082 return new Image(dicom.release(), syntax);
3083 }
3084
3085 #if ORTHANC_ENABLE_JPEG == 1
3086 if (syntax == DicomTransferSyntax_JPEGProcess1 &&
3087 allowNewSopInstanceUid &&
3088 bitsStored == 8)
3089 {
3090 DJ_RPLossy rpLossy(lossyQuality_);
3091
3092 if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess1, &rpLossy))
3093 {
3094 return new Image(dicom.release(), syntax);
3095 }
3096 }
2969 #endif 3097 #endif
3098
3099 #if ORTHANC_ENABLE_JPEG == 1
3100 if (syntax == DicomTransferSyntax_JPEGProcess2_4 &&
3101 allowNewSopInstanceUid &&
3102 bitsStored <= 12)
3103 {
3104 DJ_RPLossy rpLossy(lossyQuality_);
3105 if (FromDcmtkBridge::Transcode(*dicom, DicomTransferSyntax_JPEGProcess2_4, &rpLossy))
3106 {
3107 return new Image(dicom.release(), syntax);
3108 }
3109 }
3110 #endif
3111
3112 #endif
3113
3114 return false;
3115 }
3116 };
3117 }
3118
3119
3120 #endif