Mercurial > hg > orthanc
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 |