#include "gdcmFileChangeTransferSyntax.h"
#include "gdcmImageCodec.h"
#include "gdcmReader.h"
#include "gdcmWriter.h"
#include "gdcmImageRegionReader.h"
#include "gdcmJPEGCodec.h"
#include "gdcmJPEGLSCodec.h"
#include "gdcmJPEG2000Codec.h"
#include "gdcmRLECodec.h"
#include "gdcmImageHelper.h"
#include "gdcmEvent.h"
#include "gdcmProgressEvent.h"
#include "gdcmUIDGenerator.h"
#include "gdcmAttribute.h"
#include "gdcmFileDerivation.h"
#include "gdcmFileAnonymizer.h"
namespace gdcm
{
class FileChangeTransferSyntaxInternals
{
public:
FileChangeTransferSyntaxInternals()
= default;
~FileChangeTransferSyntaxInternals()
{
delete IC;
}
ImageCodec *IC{nullptr};
bool InitializeCopy{false};
std::streampos PixelDataPos;
std::string InFilename;
std::string OutFilename;
TransferSyntax TS;
std::vector<unsigned int> Dims;
PixelFormat PF;
PhotometricInterpretation PI;
unsigned int PC;
bool Needbyteswap;
double Progress;
};
FileChangeTransferSyntax::FileChangeTransferSyntax()
{
Internals = new FileChangeTransferSyntaxInternals;
}
FileChangeTransferSyntax::~FileChangeTransferSyntax()
{
delete Internals;
}
bool FileChangeTransferSyntax::Change()
{
this->InvokeEvent( StartEvent() );
if( !InitializeCopy() )
{
gdcmDebugMacro( "Could not InitializeCopy" );
return false;
}
const char *filename = this->Internals->InFilename.c_str();
std::ifstream is( filename, std::ios::binary );
is.seekg( Internals->PixelDataPos, std::ios::beg );
const char *outfilename = this->Internals->OutFilename.c_str();
std::fstream os( outfilename, std::ofstream::in | std::ofstream::out | std::ios::binary );
os.seekp( 0, std::ios::end );
const std::vector<unsigned int> & dims = Internals->Dims;
const PixelFormat &pf = Internals->PF;
const PhotometricInterpretation &pi = Internals->PI;
unsigned int pc = Internals->PC;
const int pixsize = pf.GetPixelSize();
const bool needbyteswap = Internals->Needbyteswap;
ImageCodec *codec = Internals->IC;
codec->SetDimensions( dims );
codec->SetNumberOfDimensions( 2 );
codec->SetPlanarConfiguration( pc );
codec->SetPhotometricInterpretation( pi );
codec->SetNeedByteSwap( needbyteswap );
codec->SetNeedOverlayCleanup( pf.GetBitsAllocated() != pf.GetBitsStored() );
codec->SetPixelFormat( pf );
VL vl;
vl.SetToUndefined();
DataElement de( Tag(0x7fe0,0x0010), vl, VR::OB );
switch ( pf.GetBitsAllocated() )
{
case 8:
de.SetVR( VR::OB );
break;
case 16:
case 32:
de.SetVR( VR::OW );
break;
default:
return false;
}
de.GetTag().Write<SwapperNoOp>( os );
de.GetVR().Write( os );
de.GetVL().Write<SwapperNoOp>( os );
Fragment frag;
frag.Write<SwapperNoOp>( os );
Internals->Progress = 0;
bool b = Internals->IC->StartEncode(os);
gdcm_assert(b);
size_t len = 0; if( Internals->IC->IsRowEncoder() )
{
std::vector<char> vbuffer;
vbuffer.resize( dims[0] * pixsize );
char *data = vbuffer.data();
const size_t datalen = vbuffer.size();
const size_t nscanlines = dims[2] * dims[1];
const double progresstick = 1. / (double)nscanlines;
for( unsigned int z = 0; z < dims[2]; ++z )
{
frag.Write<SwapperNoOp>( os );
std::streampos start = os.tellp();
for( unsigned int y = 0; y < dims[1]; ++y )
{
is.read( data, datalen );
gdcm_assert( is.good() );
b = Internals->IC->CleanupUnusedBits(data, datalen);
if( !b ) return false;
b = Internals->IC->AppendRowEncode(os, data, datalen);
if( !b ) return false;
Internals->Progress += progresstick;
ProgressEvent pe;
pe.SetProgress( Internals->Progress );
this->InvokeEvent( pe );
}
const std::streampos end = os.tellp();
const VL jpegvl = (uint32_t)(end - start);
len += jpegvl;
start -= 4;
if( jpegvl.IsOdd() )
{
os.put( 0 );
}
os.seekp( start, std::ios::beg );
jpegvl.Write<SwapperNoOp>( os );
os.seekp( 0, std::ios::end );
}
}
else if( Internals->IC->IsFrameEncoder() )
{
std::vector<char> vbuffer;
vbuffer.resize( dims[0] * dims[1] * pixsize );
char *data = vbuffer.data();
const size_t datalen = vbuffer.size();
const size_t nscanlines = dims[2];
const double progresstick = 1. / (double)nscanlines;
for( unsigned int z = 0; z < dims[2]; ++z )
{
frag.Write<SwapperNoOp>( os );
std::streampos start = os.tellp();
{
is.read( data, datalen );
gdcm_assert( is.good() );
b = Internals->IC->CleanupUnusedBits(data, datalen);
if( !b ) return false;
b = Internals->IC->AppendFrameEncode(os, data, datalen);
if( !b ) return false;
Internals->Progress += progresstick;
ProgressEvent pe;
pe.SetProgress( Internals->Progress );
this->InvokeEvent( pe );
}
const std::streampos end = os.tellp();
const VL jpegvl = (uint32_t)(end - start);
len += jpegvl;
start -= 4;
if( jpegvl.IsOdd() )
{
os.put( 0 );
}
os.seekp( start, std::ios::beg );
jpegvl.Write<SwapperNoOp>( os );
os.seekp( 0, std::ios::end );
}
}
else
{
return false;
}
b = Internals->IC->StopEncode(os);
gdcm_assert(b);
const Tag seqDelItem(0xfffe,0xe0dd);
seqDelItem.Write<SwapperNoOp>(os);
VL zero = 0;
zero.Write<SwapperNoOp>(os);
is.close();
os.close();
size_t reflen = dims[0] * dims[1] * pixsize;
double level = (double)reflen / (double)len;
if( !UpdateCompressionLevel(level) )
{
gdcmDebugMacro( "Could not UpdateCompressionLevel" );
return false;
}
this->InvokeEvent( EndEvent() );
return true;
}
bool FileChangeTransferSyntax::UpdateCompressionLevel(double level)
{
if( !Internals->IC->GetLossyFlag() ) return true;
double ratio = level;
Attribute<0x0028,0x2112> at2;
at2.SetValues( &ratio, 1);
DataElement de = at2.GetAsDataElement();
const char * fn = Internals->OutFilename.c_str();
FileAnonymizer fa;
fa.SetInputFileName(fn);
fa.SetOutputFileName(fn);
const ByteValue * bv = de.GetByteValue();
fa.Replace( de.GetTag(), bv->GetPointer(), 16 );
if( !fa.Write() )
{
gdcmDebugMacro( "FileAnonymizer failure" );
return false;
}
return true;
}
void FileChangeTransferSyntax::SetTransferSyntax( TransferSyntax const & ts )
{
Internals->TS = ts;
delete Internals->IC;
JPEGCodec jpeg;
JPEGLSCodec jpegls;
JPEG2000Codec jpeg2000;
RLECodec rle;
ImageCodec *codecs[] = {
&jpeg,
&jpegls,
&jpeg2000,
&rle
};
const int n = sizeof( codecs ) / sizeof( codecs[0] );
for( int i = 0; i < n; ++i )
{
if( codecs[i]->CanCode( ts ) )
{
Internals->IC = codecs[i]->Clone();
}
}
gdcm_assert( Internals->TS );
}
ImageCodec * FileChangeTransferSyntax::GetCodec()
{
return Internals->IC;
}
void FileChangeTransferSyntax::SetInputFileName(const char *filename_native)
{
if( filename_native )
Internals->InFilename = filename_native;
}
void FileChangeTransferSyntax::SetOutputFileName(const char *filename_native)
{
if( filename_native )
Internals->OutFilename = filename_native;
}
bool FileChangeTransferSyntax::InitializeCopy()
{
if( !Internals->IC )
{
return false;
}
if( !this->Internals->InitializeCopy )
{
const char *filename = this->Internals->InFilename.c_str();
const char *outfilename = this->Internals->OutFilename.c_str();
{
std::ifstream is( filename, std::ios::binary );
ImageRegionReader reader;
reader.SetStream( is );
if( !reader.ReadInformation() )
{
gdcmDebugMacro( "ImageRegionReader::ReadInformation failed" );
return false;
}
is.clear(); Internals->PixelDataPos = is.tellg();
File & file = reader.GetFile();
DataSet & ds = file.GetDataSet();
if( ds.FindDataElement( Tag(0x7fe0,0x0010) ) )
{
const DataElement & de = ds.GetDataElement( Tag(0x7fe0,0x0010) );
gdcmAssertAlwaysMacro( "Impossible happen"); (void)de;
return false;
}
FileMetaInformation & fmi = file.GetHeader();
const TransferSyntax &ts = fmi.GetDataSetTransferSyntax();
if( ts.IsEncapsulated() )
{
gdcmDebugMacro( "Don't know how to handle encapsulated TS: " << ts );
return false;
}
if( ts == TransferSyntax::ImplicitVRBigEndianPrivateGE
|| ts == TransferSyntax::ExplicitVRBigEndian )
{
gdcmDebugMacro( "Don't know how to handle TS: " << ts );
return false;
}
Internals->Needbyteswap = (ts == TransferSyntax::ImplicitVRBigEndianPrivateGE
|| ts == TransferSyntax::ExplicitVRBigEndian );
Internals->Dims = ImageHelper::GetDimensionsValue(file);
Internals->PF = ImageHelper::GetPixelFormatValue(file);
Internals->PI = ImageHelper::GetPhotometricInterpretationValue(file);
if( Internals->PI == PhotometricInterpretation::YBR_FULL_422 &&
( ts == TransferSyntax::ImplicitVRLittleEndian
|| ts == TransferSyntax::ExplicitVRLittleEndian ) )
{
gdcmDebugMacro( "Don't know how to handle YBR_FULL_422/raw" );
return false;
}
Internals->PC = ImageHelper::GetPlanarConfigurationValue(file);
if( Internals->PC )
{
gdcmDebugMacro( "Don't know how to handle Planar Configuration" );
return false;
}
is.close();
if( Internals->IC->GetLossyFlag() )
{
if( !ds.FindDataElement( Tag(0x0008,0x0016) )
|| ds.GetDataElement( Tag(0x0008,0x0016) ).IsEmpty() )
{
gdcmErrorMacro( "Missing Tag" );
return false;
}
if( !ds.FindDataElement( Tag(0x0008,0x0018) )
|| ds.GetDataElement( Tag(0x0008,0x0018) ).IsEmpty() )
{
gdcmErrorMacro( "Missing Tag" );
return false;
}
const DataElement &sopclassuid = ds.GetDataElement( Tag(0x0008,0x0016) );
const DataElement &sopinstanceuid = ds.GetDataElement( Tag(0x0008,0x0018) );
std::string sopclassuid_str( sopclassuid.GetByteValue()->GetPointer(), sopclassuid.GetByteValue()->GetLength() );
std::string sopinstanceuid_str( sopinstanceuid.GetByteValue()->GetPointer(), sopinstanceuid.GetByteValue()->GetLength() );
UIDGenerator uid;
{
const char *sop = uid.Generate();
DataElement de( Tag(0x0008,0x0018) );
VL::Type strlenSOP = (VL::Type) strlen(sop);
de.SetByteValue( sop, strlenSOP );
de.SetVR( Attribute<0x0008, 0x0018>::GetVR() );
ds.Replace( de );
}
FileDerivation fd;
fd.SetFile( file );
fd.AddReference( sopclassuid_str.c_str(), sopinstanceuid_str.c_str() );
fd.SetPurposeOfReferenceCodeSequenceCodeValue( 121320 );
fd.SetDerivationCodeSequenceCodeValue( 113040 );
fd.SetDerivationDescription( "lossy conversion" );
if( !fd.Derive() )
{
gdcmErrorMacro( "could not derive using input info" );
return false;
}
Attribute<0x0028,0x2110> at1;
at1.SetValue( "01" );
ds.Replace( at1.GetAsDataElement() );
#if 0#else
char buf[16+1];
buf[16] = 0;
memset( buf, ' ', 16 );
DataElement de1( Tag(0x0028,0x2112) );
de1.SetByteValue( buf, (uint32_t)strlen( buf ) );
ds.Replace( de1 );
#endif
Attribute<0x0028,0x2114> at3;
const TransferSyntax ts_orig = Internals->TS;
if( ts_orig == TransferSyntax::JPEG2000 )
{
static const CSComp newvalues2[] = {"ISO_15444_1"};
at3.SetValues( newvalues2, 1 );
}
else if( ts_orig == TransferSyntax::JPEGLSNearLossless )
{
static const CSComp newvalues2[] = {"ISO_14495_1"};
at3.SetValues( newvalues2, 1 );
}
else if (
ts_orig == TransferSyntax::HTJ2KLossless ||
ts_orig == TransferSyntax::HTJ2KRPCLLossless ||
ts_orig == TransferSyntax::HTJ2K )
{
static const CSComp newvalues2[] = {"ISO_15444_15"};
at3.SetValues( newvalues2, 1 );
}
else if (
ts_orig == TransferSyntax::JPEGBaselineProcess1 ||
ts_orig == TransferSyntax::JPEGExtendedProcess2_4 ||
ts_orig == TransferSyntax::JPEGExtendedProcess3_5 ||
ts_orig == TransferSyntax::JPEGSpectralSelectionProcess6_8 ||
ts_orig == TransferSyntax::JPEGFullProgressionProcess10_12 )
{
static const CSComp newvalues2[] = {"ISO_10918_1"};
at3.SetValues( newvalues2, 1 );
}
else
{
gdcmErrorMacro(
"Pixel Data is lossy but I cannot find the original transfer syntax" );
return false;
}
ds.Replace( at3.GetAsDataElement() );
}
Writer writer;
fmi.Clear();
fmi.SetDataSetTransferSyntax( Internals->TS );
writer.SetFileName( outfilename );
writer.SetFile( file );
if( !writer.Write() ) return false;
}
this->Internals->InitializeCopy = true;
}
return true;
}
}