/*========================================================================= Program: GDCM (Grassroots DICOM). A DICOM library Copyright (c) 2006-2011 Mathieu Malaterre All rights reserved. See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "gdcmReader.h" #include "gdcmWriter.h" #include "gdcmImageReader.h" #include #include "gdcm_charls.h" /* * This small example should show how one can handle the famous JAI-JPEGLS bug * It will take in as invalid DICOM/JAI-JPEG-LS and write out as Explicit Little * Endian. One can use `gdcmconv --jpegls` to recompress properly * * References: * http://charls.codeplex.com/discussions/230307?ProjectName=charls * http://charls.codeplex.com/workitem/7297 * http://www.dcm4che.org/jira/browse/DCM-442 * http://www.dcm4che.org/jira/browse/DCMEE-1144 * http://java.net/jira/browse/JAI_IMAGEIO_CORE-183 * * Explanation of the issue: * * Seems, the error is in the calculation of the default values for thresholds T1, * T2, T3, in particular min(MAXVAL, 4095) is not applied in * * FACTOR = (min(MAXVAL, 4095) + 128)/256 * * as specified in http://www.itu.int/rec/T-REC-T.87-199806-I/en . * */ int main(int argc, char *argv[]) { if( argc < 3 ) { std::cerr << argv[0] << " input.dcm output.dcm" << std::endl; return 1; } const char *filename = argv[1]; const char *outfilename = argv[2]; gdcm::FileMetaInformation::SetSourceApplicationEntityTitle( "FixJAIBugJPEGLS" ); gdcm::ImageReader reader; reader.SetFileName( filename ); if( !reader.Read() ) { return 1; } gdcm::Image &image = reader.GetImage(); //unsigned long len = image.GetBufferLength(); const gdcm::DataElement & in = reader.GetFile().GetDataSet().GetDataElement( gdcm::Tag(0x7fe0,0x0010) ); const gdcm::SequenceOfFragments *sf = in.GetSequenceOfFragments(); if( !sf ) { std::cerr << "No pixel data (or not encapsulated)" << std::endl; return 1; } const unsigned int *dims = image.GetDimensions(); if ( sf->GetNumberOfFragments() != dims[2] ) { std::cerr << "Unsupported" << std::endl; return 1; } // unsigned long totalLen = sf->ComputeByteLength(); std::vector rgbyteOutall; for(unsigned int i = 0; i < sf->GetNumberOfFragments(); ++i) { const gdcm::Fragment &frag = sf->GetFragment(i); if( frag.IsEmpty() ) return 1; const gdcm::ByteValue *bv = frag.GetByteValue(); if( !bv ) return 1; unsigned long totalLen = bv->GetLength(); std::vector vbuffer; vbuffer.resize( totalLen ); char *buffer = &vbuffer[0]; bv->GetBuffer(buffer, totalLen); const unsigned char* pbyteCompressed0 = (const unsigned char*)buffer; while( totalLen > 0 && pbyteCompressed0[totalLen-1] != 0xd9 ) { totalLen--; } JlsParameters metadata; char errorMsg[256+1]={'\0'}; if (JpegLsReadHeader(buffer, totalLen, &metadata, errorMsg) != charls::ApiResult::OK) { std::cerr << "Cant parse jpegls: " << errorMsg << std::endl; return 1; } std::cout << metadata.width << std::endl; std::cout << metadata.height << std::endl; std::cout << metadata.bitsPerSample << std::endl; gdcm::PixelFormat const & pf = image.GetPixelFormat(); std::cout << pf << std::endl; // http://charls.codeplex.com/discussions/230307?ProjectName=charls unsigned char marker_lse_13[] = { 0xFF, 0xF8, 0x00, 0x0D, 0x01, 0x1F, 0xFF, 0x00, 0x22, // T1 = 34 0x00, 0x83, // T2 = 131 0x02, 0x24, // T3 = 548 0x00, 0x40 }; unsigned char marker_lse_14[] = { 0xFF, 0xF8, 0x00, 0x0D, 0x01, 0x3F, 0xFF, 0x00, 0x42, // T1 = 66 0x01, 0x03, // T2 = 259 0x04, 0x44, // T3 = 1092 0x00, 0x40 }; unsigned char marker_lse_15[] = { 0xFF, 0xF8, 0x00, 0x0D, 0x01, 0x7F, 0xFF, 0x00, 0x82, // T1 = 130 0x02, 0x03, // T2 = 515 0x08, 0x84, // T3 = 2180 0x00, 0x40 }; unsigned char marker_lse_16[] = { 0xFF, 0xF8, 0x00, 0x0D, 0x01, 0xFF, 0xFF, 0x01, 0x02, // T1 = 258 0x04, 0x03, // T2 = 1027 0x11, 0x04, // T3 = 4356 0x00, 0x40 }; const unsigned char *marker_lse = nullptr; switch( metadata.bitsPerSample ) { case 13: marker_lse = marker_lse_13; break; case 14: marker_lse = marker_lse_14; break; case 15: marker_lse = marker_lse_15; break; case 16: marker_lse = marker_lse_16; break; } if( !marker_lse ) { std::cerr << "Cant handle: " << metadata.bitsPerSample << std::endl; return 1; } // FIXME: One should recompute the value for 0x0F vbuffer.insert (vbuffer.begin() + 0x0F, marker_lse, marker_lse+15); #if 0 std::ofstream of( "/tmp/d.jls", std::ios::binary ); of.write( &vbuffer[0], vbuffer.size() ); of.close(); #endif const char *pbyteCompressed = &vbuffer[0]; size_t cbyteCompressed = vbuffer.size(); // updated legnth JlsParameters params; JpegLsReadHeader(pbyteCompressed, cbyteCompressed, ¶ms, nullptr); std::vector rgbyteOut; //rgbyteOut.resize( image.GetBufferLength() ); rgbyteOut.resize(params.height *params.width * ((params.bitsPerSample + 7) / 8) * params.components); CharlsApiResultType result = JpegLsDecode(&rgbyteOut[0], rgbyteOut.size(), pbyteCompressed, cbyteCompressed, ¶ms, errorMsg ); if (result != charls::ApiResult::OK) { std::cerr << "Could not patch JAI-JPEGLS: " << errorMsg << std::endl; return 1; } rgbyteOutall.insert( rgbyteOutall.end(), rgbyteOut.begin(), rgbyteOut.end() ); } gdcm::DataElement pixeldata( gdcm::Tag(0x7fe0,0x0010) ); pixeldata.SetVR( gdcm::VR::OW ); pixeldata.SetByteValue( (char*)&rgbyteOutall[0], (uint32_t)rgbyteOutall.size() ); // Add the pixel data element reader.GetFile().GetDataSet().Replace( pixeldata ); reader.GetFile().GetHeader().SetDataSetTransferSyntax( gdcm::TransferSyntax::ExplicitVRLittleEndian); gdcm::Writer writer; writer.SetFileName( outfilename ); writer.SetFile( reader.GetFile() ); writer.Write(); std::cout << "Success !" << std::endl; return 0; }