comparison UnitTestsSources/FromDcmtkTests.cpp @ 3893:7a5fa8f307e9 transcoding

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 06 May 2020 12:48:28 +0200
parents 56ce23ba93b7
children 8f7ad4989fec
comparison
equal deleted inserted replaced
3891:5571082a9df6 3893:7a5fa8f307e9
1923 1923
1924 1924
1925 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 1925 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
1926 1926
1927 #include "../Core/DicomNetworking/DicomStoreUserConnection.h" 1927 #include "../Core/DicomNetworking/DicomStoreUserConnection.h"
1928 1928 #include "../Core/DicomParsing/DcmtkTranscoder.h"
1929 #include <dcmtk/dcmjpeg/djrploss.h> // for DJ_RPLossy
1930 #include <dcmtk/dcmjpeg/djrplol.h> // for DJ_RPLossless
1931 #include <dcmtk/dcmjpls/djrparam.h> // for DJLSRepresentationParameter
1932
1933
1934 #if !defined(ORTHANC_ENABLE_DCMTK_JPEG)
1935 # error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined
1936 #endif
1937
1938 #if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
1939 # error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined
1940 #endif
1941
1942
1943
1944 namespace Orthanc
1945 {
1946 /**
1947 * WARNING: This class might be called from several threads at
1948 * once. Make sure to implement proper locking.
1949 **/
1950
1951 class IDicomTranscoder : public boost::noncopyable
1952 {
1953 public:
1954 virtual ~IDicomTranscoder()
1955 {
1956 }
1957
1958 virtual bool TranscodeToBuffer(std::string& target,
1959 const void* buffer,
1960 size_t size,
1961 const std::set<DicomTransferSyntax>& allowedSyntaxes,
1962 bool allowNewSopInstanceUid) = 0;
1963
1964 /**
1965 * Transcoding flavor that creates a new parsed DICOM file. A
1966 * "std::set<>" is used to give the possible plugin the
1967 * possibility to do a single parsing for all the possible
1968 * transfer syntaxes.
1969 **/
1970 virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
1971 size_t size,
1972 const std::set<DicomTransferSyntax>& allowedSyntaxes,
1973 bool allowNewSopInstanceUid) = 0;
1974
1975 virtual bool HasInplaceTranscode() const = 0;
1976
1977 /**
1978 * In-place transcoding. This method is preferred for C-STORE.
1979 **/
1980 virtual bool InplaceTranscode(DcmFileFormat& dicom,
1981 const std::set<DicomTransferSyntax>& allowedSyntaxes,
1982 bool allowNewSopInstanceUid) = 0;
1983
1984 /**
1985 * Important: Transcoding over the DICOM protocol is only
1986 * implemented towards uncompressed transfer syntaxes.
1987 **/
1988 static void Store(std::string& sopClassUid /* out */,
1989 std::string& sopInstanceUid /* out */,
1990 DicomStoreUserConnection& connection,
1991 IDicomTranscoder& transcoder,
1992 const void* buffer,
1993 size_t size,
1994 const std::string& moveOriginatorAET,
1995 uint16_t moveOriginatorID)
1996 {
1997 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
1998 if (dicom.get() == NULL ||
1999 dicom->getDataset() == NULL)
2000 {
2001 throw OrthancException(ErrorCode_NullPointer);
2002 }
2003
2004 DicomTransferSyntax inputSyntax;
2005 connection.LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom);
2006
2007 std::set<DicomTransferSyntax> accepted;
2008 connection.LookupTranscoding(accepted, sopClassUid, inputSyntax);
2009
2010 if (accepted.find(inputSyntax) != accepted.end())
2011 {
2012 // No need for transcoding
2013 connection.Store(sopClassUid, sopInstanceUid, *dicom, moveOriginatorAET, moveOriginatorID);
2014 }
2015 else
2016 {
2017 // Transcoding is needed
2018 std::set<DicomTransferSyntax> uncompressedSyntaxes;
2019
2020 if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
2021 {
2022 uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
2023 }
2024
2025 if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
2026 {
2027 uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
2028 }
2029
2030 if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
2031 {
2032 uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
2033 }
2034
2035 std::unique_ptr<DcmFileFormat> transcoded;
2036
2037 if (transcoder.HasInplaceTranscode())
2038 {
2039 if (transcoder.InplaceTranscode(*dicom, uncompressedSyntaxes, false))
2040 {
2041 // In-place transcoding is supported and has succeeded
2042 transcoded.reset(dicom.release());
2043 }
2044 }
2045 else
2046 {
2047 transcoded.reset(transcoder.TranscodeToParsed(buffer, size, uncompressedSyntaxes, false));
2048 }
2049
2050 // WARNING: The "dicom" variable must not be used below this
2051 // point. The "sopInstanceUid" might also have changed (if
2052 // using lossy compression).
2053
2054 if (transcoded == NULL ||
2055 transcoded->getDataset() == NULL)
2056 {
2057 throw OrthancException(
2058 ErrorCode_NotImplemented,
2059 "Cannot transcode from \"" + std::string(GetTransferSyntaxUid(inputSyntax)) +
2060 "\" to an uncompressed syntax for modality: " +
2061 connection.GetParameters().GetRemoteModality().GetApplicationEntityTitle());
2062 }
2063 else
2064 {
2065 DicomTransferSyntax transcodedSyntax;
2066
2067 // Sanity check
2068 if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, *transcoded) ||
2069 accepted.find(transcodedSyntax) == accepted.end())
2070 {
2071 throw OrthancException(ErrorCode_InternalError);
2072 }
2073 else
2074 {
2075 connection.Store(sopClassUid, sopInstanceUid, *transcoded, moveOriginatorAET, moveOriginatorID);
2076 }
2077 }
2078 }
2079 }
2080
2081 static void Store(std::string& sopClassUid /* out */,
2082 std::string& sopInstanceUid /* out */,
2083 DicomStoreUserConnection& connection,
2084 IDicomTranscoder& transcoder,
2085 const void* buffer,
2086 size_t size)
2087 {
2088 Store(sopClassUid, sopInstanceUid, connection, transcoder,
2089 buffer, size, "", 0 /* Not a C-MOVE */);
2090 }
2091 };
2092
2093
2094 class DcmtkTranscoder : public IDicomTranscoder
2095 {
2096 private:
2097 unsigned int lossyQuality_;
2098
2099 static uint16_t GetBitsStored(DcmDataset& dataset)
2100 {
2101 uint16_t bitsStored;
2102 if (dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good())
2103 {
2104 return bitsStored;
2105 }
2106 else
2107 {
2108 throw OrthancException(ErrorCode_BadFileFormat,
2109 "Missing \"Bits Stored\" tag in DICOM instance");
2110 }
2111 }
2112
2113 static std::string GetSopInstanceUid(DcmDataset& dataset)
2114 {
2115 const char* v = NULL;
2116
2117 if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() &&
2118 v != NULL)
2119 {
2120 return std::string(v);
2121 }
2122 else
2123 {
2124 throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID");
2125 }
2126 }
2127
2128 static void CheckSopInstanceUid(DcmFileFormat& dicom,
2129 const std::string& sopInstanceUid,
2130 bool mustEqual)
2131 {
2132 if (dicom.getDataset() == NULL)
2133 {
2134 throw OrthancException(ErrorCode_InternalError);
2135 }
2136
2137 bool ok;
2138
2139 if (mustEqual)
2140 {
2141 ok = (GetSopInstanceUid(*dicom.getDataset()) == sopInstanceUid);
2142 }
2143 else
2144 {
2145 ok = (GetSopInstanceUid(*dicom.getDataset()) != sopInstanceUid);
2146 }
2147
2148 if (!ok)
2149 {
2150 throw OrthancException(ErrorCode_InternalError,
2151 mustEqual ? "The SOP instance UID has changed unexpectedly during transcoding" :
2152 "The SOP instance UID has not changed as expected during transcoding");
2153 }
2154 }
2155
2156 public:
2157 DcmtkTranscoder() :
2158 lossyQuality_(90)
2159 {
2160 }
2161
2162 void SetLossyQuality(unsigned int quality)
2163 {
2164 if (quality <= 0 ||
2165 quality > 100)
2166 {
2167 throw OrthancException(ErrorCode_ParameterOutOfRange);
2168 }
2169 else
2170 {
2171 lossyQuality_ = quality;
2172 }
2173 }
2174
2175 unsigned int GetLossyQuality() const
2176 {
2177 return lossyQuality_;
2178 }
2179
2180 virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
2181 size_t size,
2182 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2183 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
2184 {
2185 std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
2186
2187 if (dicom.get() == NULL)
2188 {
2189 throw OrthancException(ErrorCode_InternalError);
2190 }
2191
2192 if (InplaceTranscode(*dicom, allowedSyntaxes, allowNewSopInstanceUid))
2193 {
2194 return dicom.release();
2195 }
2196 else
2197 {
2198 return NULL;
2199 }
2200 }
2201
2202 virtual bool HasInplaceTranscode() const
2203 {
2204 return true;
2205 }
2206
2207 virtual bool InplaceTranscode(DcmFileFormat& dicom,
2208 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2209 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
2210 {
2211 if (dicom.getDataset() == NULL)
2212 {
2213 throw OrthancException(ErrorCode_InternalError);
2214 }
2215
2216 DicomTransferSyntax syntax;
2217 if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom))
2218 {
2219 throw OrthancException(ErrorCode_BadFileFormat,
2220 "Cannot determine the transfer syntax");
2221 }
2222
2223 const uint16_t bitsStored = GetBitsStored(*dicom.getDataset());
2224 std::string sourceSopInstanceUid = GetSopInstanceUid(*dicom.getDataset());
2225
2226 if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end())
2227 {
2228 // No transcoding is needed
2229 return true;
2230 }
2231
2232 if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() &&
2233 FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL))
2234 {
2235 CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
2236 return true;
2237 }
2238
2239 if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() &&
2240 FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL))
2241 {
2242 CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
2243 return true;
2244 }
2245
2246 if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() &&
2247 FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL))
2248 {
2249 CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
2250 return true;
2251 }
2252
2253 if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() &&
2254 FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL))
2255 {
2256 CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
2257 return true;
2258 }
2259
2260 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
2261 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() &&
2262 allowNewSopInstanceUid &&
2263 bitsStored == 8)
2264 {
2265 // Check out "dcmjpeg/apps/dcmcjpeg.cc"
2266 DJ_RPLossy parameters(lossyQuality_);
2267
2268 if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, &parameters))
2269 {
2270 CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
2271 return true;
2272 }
2273 }
2274 #endif
2275
2276 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
2277 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() &&
2278 allowNewSopInstanceUid &&
2279 bitsStored <= 12)
2280 {
2281 // Check out "dcmjpeg/apps/dcmcjpeg.cc"
2282 DJ_RPLossy parameters(lossyQuality_);
2283 if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, &parameters))
2284 {
2285 CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
2286 return true;
2287 }
2288 }
2289 #endif
2290
2291 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
2292 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end())
2293 {
2294 // Check out "dcmjpeg/apps/dcmcjpeg.cc"
2295 DJ_RPLossless parameters(6 /* opt_selection_value */,
2296 0 /* opt_point_transform */);
2297 if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, &parameters))
2298 {
2299 CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
2300 return true;
2301 }
2302 }
2303 #endif
2304
2305 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
2306 if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end())
2307 {
2308 // Check out "dcmjpls/apps/dcmcjpls.cc"
2309 DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
2310 OFTrue /* opt_useLosslessProcess */);
2311
2312 /**
2313 * WARNING: This call results in a segmentation fault if using
2314 * the DCMTK package 3.6.2 from Ubuntu 18.04.
2315 **/
2316 if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, &parameters))
2317 {
2318 CheckSopInstanceUid(dicom, sourceSopInstanceUid, true);
2319 return true;
2320 }
2321 }
2322 #endif
2323
2324 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
2325 if (allowNewSopInstanceUid &&
2326 allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end())
2327 {
2328 // Check out "dcmjpls/apps/dcmcjpls.cc"
2329 DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */,
2330 OFFalse /* opt_useLosslessProcess */);
2331
2332 /**
2333 * WARNING: This call results in a segmentation fault if using
2334 * the DCMTK package 3.6.2 from Ubuntu 18.04.
2335 **/
2336 if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, &parameters))
2337 {
2338 CheckSopInstanceUid(dicom, sourceSopInstanceUid, false);
2339 return true;
2340 }
2341 }
2342 #endif
2343
2344 return false;
2345 }
2346
2347
2348 virtual bool TranscodeToBuffer(std::string& target,
2349 const void* buffer,
2350 size_t size,
2351 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2352 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
2353 {
2354 std::unique_ptr<DcmFileFormat> transcoded(
2355 TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid));
2356
2357 if (transcoded.get() == NULL)
2358 {
2359 return false;
2360 }
2361 else
2362 {
2363 if (transcoded->getDataset() == NULL)
2364 {
2365 throw OrthancException(ErrorCode_InternalError);
2366 }
2367
2368 FromDcmtkBridge::SaveToMemoryBuffer(target, *transcoded->getDataset());
2369 return true;
2370 }
2371 }
2372 };
2373
2374
2375
2376 class PluginDicomTranscoder: public IDicomTranscoder
2377 {
2378 private:
2379 bool tryDcmtk_;
2380 DcmtkTranscoder dcmtk_;
2381
2382 protected:
2383 virtual bool TranscodeInternal(std::string& target,
2384 const void* buffer,
2385 size_t size,
2386 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2387 bool allowNewSopInstanceUid) = 0;
2388
2389 public:
2390 PluginDicomTranscoder(bool tryDcmtk) :
2391 tryDcmtk_(tryDcmtk)
2392 {
2393 }
2394
2395 virtual bool TranscodeToBuffer(std::string& target,
2396 const void* buffer,
2397 size_t size,
2398 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2399 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
2400 {
2401 if (tryDcmtk_)
2402 {
2403 return dcmtk_.TranscodeToBuffer(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
2404 }
2405 else
2406 {
2407 return TranscodeInternal(target, buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
2408 }
2409 }
2410
2411 virtual DcmFileFormat* TranscodeToParsed(const void* buffer,
2412 size_t size,
2413 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2414 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
2415 {
2416 if (tryDcmtk_)
2417 {
2418 return dcmtk_.TranscodeToParsed(buffer, size, allowedSyntaxes, allowNewSopInstanceUid);
2419 }
2420 else
2421 {
2422 std::string transcoded;
2423 if (TranscodeInternal(transcoded, buffer, size, allowedSyntaxes, allowNewSopInstanceUid))
2424 {
2425 return FromDcmtkBridge::LoadFromMemoryBuffer(
2426 transcoded.empty() ? NULL : transcoded.c_str(), transcoded.size());
2427 }
2428 else
2429 {
2430 return NULL;
2431 }
2432 }
2433 }
2434
2435 virtual bool HasInplaceTranscode() const ORTHANC_OVERRIDE
2436 {
2437 return tryDcmtk_;
2438 }
2439
2440 virtual bool InplaceTranscode(DcmFileFormat& dicom,
2441 const std::set<DicomTransferSyntax>& allowedSyntaxes,
2442 bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
2443 {
2444 if (tryDcmtk_)
2445 {
2446 return dcmtk_.InplaceTranscode(dicom, allowedSyntaxes, allowNewSopInstanceUid);
2447 }
2448 else
2449 {
2450 // "HasInplaceTranscode()" should have been called
2451 throw OrthancException(ErrorCode_BadSequenceOfCalls);
2452 }
2453 }
2454 };
2455 }
2456
2457 1929
2458 TEST(Toto, DISABLED_Transcode3) 1930 TEST(Toto, DISABLED_Transcode3)
2459 { 1931 {
2460 DicomAssociationParameters p; 1932 DicomAssociationParameters p;
2461 p.SetRemotePort(2000); 1933 p.SetRemotePort(2000);
2481 Orthanc::SystemToolbox::ReadFile(source, path); 1953 Orthanc::SystemToolbox::ReadFile(source, path);
2482 1954
2483 std::string c, i; 1955 std::string c, i;
2484 try 1956 try
2485 { 1957 {
2486 IDicomTranscoder::Store(c, i, scu, transcoder, source.c_str(), source.size()); 1958 scu.Transcode(c, i, transcoder, source.c_str(), source.size());
2487 } 1959 }
2488 catch (OrthancException& e) 1960 catch (OrthancException& e)
2489 { 1961 {
2490 if (e.GetErrorCode() == ErrorCode_NotImplemented) 1962 if (e.GetErrorCode() == ErrorCode_NotImplemented)
2491 { 1963 {
2499 } 1971 }
2500 } 1972 }
2501 } 1973 }
2502 1974
2503 1975
2504
2505
2506 TEST(Toto, DISABLED_Transcode4) 1976 TEST(Toto, DISABLED_Transcode4)
2507 { 1977 {
2508 std::string source; 1978 std::string source;
2509 Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm"); 1979 Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
2510 1980
2532 printf("SIZE: %lu\n", t.size()); 2002 printf("SIZE: %lu\n", t.size());
2533 } 2003 }
2534 } 2004 }
2535 } 2005 }
2536 2006
2537
2538 #endif 2007 #endif