/*========================================================================= 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. =========================================================================*/ /* * TODO: Merging (burnin) of overlay would be nice (merge 0x60xx overlay into PixelData) * TODO: --add-thumbnail / --remove-thumbnail * convert: -thumbnail geometry create a thumbnail of the image * convert: -crop geometry cut out a rectangular region of the image * -floodfill geometry color * floodfill the image with color * 1. Create a DICOM file from a 'raw' input: * 2. Create a blob (jpeg,pgm/pnm,j2k,rle) from input * - binary blob(s) (grayscale / RGB) input * - jpeg(s) * - j2k(s) * * Mapping is: * * DICOM RAW <-> pnm/pgm * DICOM jpg <-> jpg * DICOM ljpg <-> ljpg * DICOM jls <-> jls * DICOM j2k <-> j2k * DICOM rle <-> Utah RLE ?? * * ??: * DICOM avi <-> avi * DICOM wav <-> wav * DICOM pdf <-> pdf * Todo: check compat API with jhead */ #include "gdcmFilename.h" #include "gdcmImageHelper.h" #include "gdcmDirectory.h" #include "gdcmMediaStorage.h" #include "gdcmSmartPointer.h" #include "gdcmUIDGenerator.h" #include "gdcmSequenceOfFragments.h" #include "gdcmSystem.h" #include "gdcmReader.h" #include "gdcmPixmapWriter.h" #include "gdcmPixmapReader.h" #include "gdcmFileMetaInformation.h" #include "gdcmDataSet.h" #include "gdcmAttribute.h" #include "gdcmPNMCodec.h" #include "gdcmPGXCodec.h" #include "gdcmJPEGCodec.h" #include "gdcmJPEGLSCodec.h" #include "gdcmJPEG2000Codec.h" #include "gdcmRLECodec.h" #include "gdcmRAWCodec.h" #include "gdcmVersion.h" #include #include #include /* for printf */ #include /* for exit */ #include #include static unsigned int readsize(const char *str, unsigned int * size) { int n = sscanf( str, "%i,%i,%i", size, size+1, size+2); return n == EOF ? 0 : (unsigned int)n; } static bool readgeometry(const char *geometry, unsigned int * region) { int n = sscanf( geometry, "%i,%i,%i,%i,%i,%i", region, region+1, region+2, region+3, region+4, region+5); if( n != 6 ) return false; return true; } template static void FillRegionWithColor(T *p, const unsigned int *dims, const unsigned int * region, unsigned int color, unsigned int nsamples) { unsigned int xmin = region[0]; unsigned int xmax = region[1]; unsigned int ymin = region[2]; unsigned int ymax = region[3]; unsigned int zmin = region[4]; unsigned int zmax = region[5]; for( unsigned int x = xmin; x <= xmax; ++x) { for( unsigned int y = ymin; y <= ymax; ++y) { for( unsigned int z = zmin; z <= zmax; ++z) { for( unsigned int sample = 0; sample < nsamples; ++sample) { p[x*nsamples+y*dims[0]*nsamples+z*dims[0]*dims[1]*nsamples+sample] = (T)color; } } } } } static void PrintVersion() { std::cout << "gdcmimg: gdcm " << gdcm::Version::GetVersion() << " "; const char date[] = "$Date$"; std::cout << date << std::endl; } static void PrintHelp() { PrintVersion(); std::cout << "Usage: gdcmimg [OPTION]... FILE..." << std::endl; std::cout << "Manipulate DICOM image file:" << std::endl; std::cout << " 1. Convert to and from other file format (jpg, jp2, pnm...)" << std::endl; std::cout << " 2. Anonymize burn-in annotation (rect region fill with pixel value)" << std::endl; std::cout << "Parameter (required):" << std::endl; std::cout << " -i --input Input filename" << std::endl; std::cout << " -o --output Output filename" << std::endl; std::cout << "Options:" << std::endl; std::cout << " --endian %s Endianness (LSB/MSB)." << std::endl; std::cout << " -d --depth %d Depth (Either 8/16/32 or BitsAllocated eg. 12 when known)." << std::endl; std::cout << " --sign %s Pixel sign (0/1)." << std::endl; std::cout << " --spp %d Sample Per Pixel (1/3/4)." << std::endl; std::cout << " --pc [01] Change planar configuration." << std::endl; std::cout << " --pi [str] Change photometric interpretation." << std::endl; std::cout << " --pf %d,%d,%d Change pixel format: (BA,BS,HB)." << std::endl; std::cout << " -s --size %d,%d,%d Size." << std::endl; std::cout << " --offset %ull Start Offset." << std::endl; std::cout << " -C --sop-class-uid SOP Class UID (name or value)." << std::endl; std::cout << " -T --study-uid Study UID." << std::endl; std::cout << " -S --series-uid Series UID." << std::endl; std::cout << " --template DICOM template." << std::endl; std::cout << " --keep-meta Keep meta info from template file (advanced users only)." << std::endl; std::cout << " --root-uid Root UID." << std::endl; std::cout << "Fill Options:" << std::endl; std::cout << " -R --region %d,%d Region." << std::endl; std::cout << " -F --fill %d Fill with pixel value specified." << std::endl; std::cout << "General Options:" << std::endl; std::cout << " -V --verbose more verbose (warning+error)." << std::endl; std::cout << " -W --warning print warning info." << std::endl; std::cout << " -D --debug print debug info." << std::endl; std::cout << " -E --error print error info." << std::endl; std::cout << " -h --help print help." << std::endl; std::cout << " -v --version print version." << std::endl; std::cout << "Env var:" << std::endl; std::cout << " GDCM_ROOT_UID Root UID" << std::endl; /* * Default behavior for root UID is: * By default the GDCM one is used * If GDCM_ROOT_UID is set, then use this one instead * If --root-uid is explicitly set on the command line, it will override any other defined behavior */ } static bool AddContentDateTime(gdcm::DataSet &ds, const char *filename ) { time_t studydatetime = gdcm::System::FileTime( filename ); char date[22]; gdcm::System::FormatDateTime(date, studydatetime); const size_t datelen = 8; { gdcm::DataElement de( gdcm::Tag(0x0008,0x0023) ); // Content Date // Do not copy the whole cstring: de.SetByteValue( date, datelen ); de.SetVR( gdcm::Attribute<0x0008,0x0023>::GetVR() ); ds.Insert( de ); } // StudyTime const size_t timelen = 6; // get rid of milliseconds { gdcm::DataElement de( gdcm::Tag(0x0008,0x0033) ); // Content Time // Do not copy the whole cstring: de.SetByteValue( date+datelen, timelen ); de.SetVR( gdcm::Attribute<0x0008,0x0033>::GetVR() ); ds.Insert( de ); } return true; } // Set Study Date/Time to the file time: static bool AddStudyDateTime(gdcm::DataSet &ds, const char *filename ) { // StudyDate char date[22]; const size_t datelen = 8; int res = gdcm::System::GetCurrentDateTime(date); if( !res ) return false; { gdcm::DataElement de( gdcm::Tag(0x0008,0x0020) ); // Do not copy the whole cstring: de.SetByteValue( date, datelen ); de.SetVR( gdcm::Attribute<0x0008,0x0020>::GetVR() ); ds.Insert( de ); } // StudyTime const size_t timelen = 6; // get rid of milliseconds { gdcm::DataElement de( gdcm::Tag(0x0008,0x0030) ); // Do not copy the whole cstring: de.SetByteValue( date+datelen, timelen ); de.SetVR( gdcm::Attribute<0x0008,0x0030>::GetVR() ); ds.Insert( de ); } return AddContentDateTime(ds, filename); } static bool AddUIDs(int sopclassuid, std::string const & sopclass, std::string const & study_uid, std::string const & series_uid, gdcm::PixmapWriter& writer) { gdcm::DataSet & ds = writer.GetFile().GetDataSet(); gdcm::MediaStorage ms = gdcm::MediaStorage::MS_END; gdcm::Pixmap &image = writer.GetPixmap(); if( sopclassuid ) { // Is it by value or by name ? if( gdcm::UIDGenerator::IsValid( sopclass.c_str() ) ) { ms = gdcm::MediaStorage::GetMSType( sopclass.c_str() ); } else { std::cerr << "not implemented" << std::endl; } } else { // guess a default ms = gdcm::ImageHelper::ComputeMediaStorageFromModality( "OT", image.GetNumberOfDimensions(), image.GetPixelFormat(), image.GetPhotometricInterpretation() ); } if( !gdcm::MediaStorage::IsImage(ms) ) { std::cerr << "invalid media storage (no pixel data): " << sopclass << std::endl; return false; } if( ms.GetModalityDimension() < image.GetNumberOfDimensions() ) { std::cerr << "Could not find Modality" << std::endl; return false; } const char* msstr = gdcm::MediaStorage::GetMSString(ms); if( !msstr ) { std::cerr << "problem with media storage: " << sopclass << std::endl; return false; } { gdcm::DataElement de( gdcm::Tag(0x0008, 0x0016 ) ); de.SetByteValue( msstr, (uint32_t)strlen(msstr) ); de.SetVR( gdcm::Attribute<0x0008, 0x0016>::GetVR() ); ds.Insert( de ); } { gdcm::DataElement de( gdcm::Tag(0x0020,0x000d) ); // Study de.SetByteValue( study_uid.c_str(), (uint32_t)study_uid.size() ); de.SetVR( gdcm::Attribute<0x0020, 0x000d>::GetVR() ); ds.Insert( de ); } { gdcm::DataElement de( gdcm::Tag(0x0020,0x000e) ); // Series de.SetByteValue( series_uid.c_str(), (uint32_t)series_uid.size() ); de.SetVR( gdcm::Attribute<0x0020, 0x000e>::GetVR() ); ds.Insert( de ); } return true; } // Append data to either sq or bv depending whether encapsulated or not static bool PopulateSingeFile( gdcm::PixmapWriter & writer, gdcm::SequenceOfFragments *sq , gdcm::ByteValue * bv, gdcm::ImageCodec & jpeg, const char *filename, std::streampos const pos = 0 ) { /* * FIXME: when JPEG contains JFIF marker, we should only read them * during header parsing but discard them when copying the JPG byte stream into * the encapsulated Pixel Data Element... */ std::ifstream is(filename, std::ios::binary); gdcm::TransferSyntax ts; bool b = jpeg.GetHeaderInfo( is, ts ); if( !b ) { std::cerr << "Could not read: " << filename << std::endl; return false; } gdcm::Pixmap &image = writer.GetPixmap(); image.SetDimensions( jpeg.GetDimensions() ); image.SetPixelFormat( jpeg.GetPixelFormat() ); image.SetPhotometricInterpretation( jpeg.GetPhotometricInterpretation() ); image.SetPlanarConfiguration( jpeg.GetPlanarConfiguration() ); image.SetTransferSyntax( ts ); AddStudyDateTime( writer.GetFile().GetDataSet(), filename ); size_t len = gdcm::System::FileSize(filename); if( ts.IsEncapsulated() ) { is.seekg(0, std::ios::beg );// rewind ! } else { len = image.GetBufferLength(); // do not rewind file should be just at right offset } char *buf = new char[len]; if( pos ) { is.seekg( pos, std::ios::beg ); } is.read(buf, len); gdcm::DataElement pixeldata( gdcm::Tag(0x7fe0,0x0010) ); if( ts.IsEncapsulated() ) { gdcm::Fragment frag; frag.SetByteValue( buf, (uint32_t)len ); sq->AddFragment( frag ); pixeldata.SetValue( *sq ); } else { gdcm::ByteValue frame( buf, (uint32_t)len ); bv->Append( frame ); pixeldata.SetValue( *bv ); } delete[] buf; image.SetDataElement( pixeldata ); return true; } static bool Populate( gdcm::PixmapWriter & writer, gdcm::ImageCodec & jpeg, gdcm::Directory::FilenamesType const & filenames, unsigned int ndim = 2, std::streampos const & pos = 0 ) { assert( !filenames.empty() ); std::vector::const_iterator it = filenames.begin(); bool b = true; gdcm::Pixmap &image = writer.GetPixmap(); image.SetNumberOfDimensions( ndim ); gdcm::SmartPointer sq = new gdcm::SequenceOfFragments; gdcm::SmartPointer bv = new gdcm::ByteValue; for(; it != filenames.end(); ++it) { b = b && PopulateSingeFile( writer, sq, bv, jpeg, it->c_str(), pos ); } if( filenames.size() > 1 ) { image.SetNumberOfDimensions( 3 ); image.SetDimension(2, (unsigned int)filenames.size() ); } return b; } static bool GetPixelFormat( gdcm::PixelFormat & pf, int depth, int bpp, int sign, int pixelsign, int spp = 0, int pixelspp = 1 ) { if( depth ) { if( bpp <= 8 ) { pf = gdcm::PixelFormat::UINT8; } else if( bpp > 8 && bpp <= 16 ) { pf = gdcm::PixelFormat::UINT16; } else if( bpp > 16 && bpp <= 32 ) { pf = gdcm::PixelFormat::UINT32; } else { std::cerr << "Invalid depth: << " << bpp << std::endl; return false; } pf.SetBitsStored( (short)bpp ); } if( sign ) { pf.SetPixelRepresentation( (unsigned short)pixelsign ); } if( spp ) { pf.SetSamplesPerPixel( (unsigned short)pixelspp ); } return true; } int main (int argc, char *argv[]) { int c; //int digit_optind = 0; std::string root; int rootuid = 0; gdcm::Filename filename; gdcm::Directory::FilenamesType filenames; gdcm::Filename outfilename; gdcm::Filename templatefilename; unsigned int region[6] = {}; // Rows & Columns are VR=US anyway... unsigned int color = 0; int bregion = 0; int fill = 0; int sign = 0; int spp = 0; int pconf = 0; // planar configuration int studyuid = 0; int seriesuid = 0; int templated = 0; int keepmeta = 0; unsigned int size[3] = {0,0,0}; unsigned int ndimension = 2; int depth = 0; int endian = 0; int bpp = 0; int pixelsign = 0; int pixelspp = 0; std::string sopclass; std::string lsb_msb; int sopclassuid = 0; int pinter = 0; std::string pinterstr; int pformat = 0; std::string pformatstr; int poffset = 0; size_t start_pos = 0; int verbose = 0; int warning = 0; int debug = 0; int error = 0; int help = 0; int version = 0; gdcm::UIDGenerator uid; // Too early for UID Generation std::string series_uid; // = uid.Generate(); std::string study_uid; // = uid.Generate(); while (true) { //int this_option_optind = optind ? optind : 1; int option_index = 0; static struct option long_options[] = { {"input", 1, nullptr, 0}, {"output", 1, nullptr, 0}, // provide convert-like command line args: {"depth", 1, &depth, 1}, {"size", 1, nullptr, 0}, {"region", 1, &bregion, 1}, {"fill", 1, &fill, 1}, {"study-uid", 1, &studyuid, 1}, {"series-uid", 1, &seriesuid, 1}, {"root-uid", 1, &rootuid, 1}, // specific Root (not GDCM) {"sop-class-uid", 1, &sopclassuid, 1}, // specific SOP Class UID {"endian", 1, &endian, 1}, // {"sign", 1, &sign, 1}, // {"spp", 1, &spp, 1}, // {"pc", 1, &pconf, 1}, // {"pi", 1, &pinter, 1}, // {"pf", 1, &pformat, 1}, // {"offset", 1, &poffset, 1}, // {"template", 1, &templated, 1}, {"keep-meta", 0, &keepmeta, 1}, // by default we do not want to keep // General options ! {"verbose", 0, &verbose, 1}, {"warning", 0, &warning, 1}, {"debug", 0, &debug, 1}, {"error", 0, &error, 1}, {"help", 0, &help, 1}, {"version", 0, &version, 1}, {nullptr, 0, nullptr, 0} }; // i -> input file // I -> input directory // o -> output file // O -> output directory c = getopt_long (argc, argv, "i:o:I:O:d:s:R:C:F:T:S:VWDEhv", long_options, &option_index); if (c == -1) { break; } switch (c) { case 0: { const char *s = long_options[option_index].name; (void)s; //printf ("option %s", s); if (optarg) { if( option_index == 0 ) /* input */ { assert( strcmp(s, "input") == 0 ); assert( filename.IsEmpty() ); filename = optarg; filenames.emplace_back(filename); } else if( option_index == 2 ) /* depth */ { assert( strcmp(s, "depth") == 0 ); bpp = atoi(optarg); } else if( option_index == 3 ) /* size */ { assert( strcmp(s, "size") == 0 ); ndimension = readsize(optarg, size); } else if( option_index == 4 ) /* region */ { assert( strcmp(s, "region") == 0 ); readgeometry(optarg, region); } else if( option_index == 5 ) /* fill */ { assert( strcmp(s, "fill") == 0 ); color = atoi(optarg); } else if( option_index == 6 ) /* study-uid */ { assert( strcmp(s, "study-uid") == 0 ); study_uid = optarg; } else if( option_index == 7 ) /* series-uid */ { assert( strcmp(s, "series-uid") == 0 ); series_uid = optarg; } else if( option_index == 8 ) /* root-uid */ { assert( strcmp(s, "root-uid") == 0 ); root = optarg; } else if( option_index == 9 ) /* sop-class-uid */ { assert( strcmp(s, "sop-class-uid") == 0 ); sopclass = optarg; } else if( option_index == 10 ) /* endian */ { assert( strcmp(s, "endian") == 0 ); lsb_msb = optarg; } else if( option_index == 11 ) /* sign */ { assert( strcmp(s, "sign") == 0 ); pixelsign = atoi(optarg); } else if( option_index == 12 ) /* spp */ { assert( strcmp(s, "spp") == 0 ); pixelspp = atoi(optarg); } else if( option_index == 13 ) /* pconf */ { assert( strcmp(s, "pc") == 0 ); pconf = atoi(optarg); } else if( option_index == 14 ) /* pinter */ { assert( strcmp(s, "pi") == 0 ); pinter = 1; pinterstr = optarg; } else if( option_index == 15 ) /* pformat */ { assert( strcmp(s, "pf") == 0 ); pformat = 1; pformatstr = optarg; } else if( option_index == 16 ) /* start_pos */ { assert( strcmp(s, "offset") == 0 ); poffset = 1; start_pos = (size_t)std::atoll(optarg); } else if( option_index == 17 ) /* template */ { assert( strcmp(s, "template") == 0 ); templated = 1; templatefilename = optarg; } //printf (" with arg %s", optarg); } //printf ("\n"); } break; case 'i': //printf ("option i with value '%s'\n", optarg); assert( filename.IsEmpty() ); filename = optarg; filenames.emplace_back(filename); break; case 'o': //printf ("option o with value '%s'\n", optarg); assert( outfilename.IsEmpty() ); outfilename = optarg; break; case 'd': // depth bpp = atoi(optarg); depth = 1; break; case 's': // size ndimension = readsize(optarg, size); break; case 'T': studyuid = 1; study_uid = optarg; break; case 'S': seriesuid = 1; series_uid = optarg; break; case 'C': sopclassuid = 1; sopclass = optarg; break; case 'R': // region //outfilename = optarg; readgeometry(optarg, region); break; case 'F': // fill color = atoi( optarg ); fill = 1; break; case 'V': verbose = 1; break; case 'W': warning = 1; break; case 'D': debug = 1; break; case 'E': error = 1; break; case 'h': help = 1; break; case 'v': version = 1; break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } // For now only support one input / one output if (optind < argc) { //printf ("non-option ARGV-elements: "); std::vector files; while (optind < argc) { //printf ("%s\n", argv[optind++]); files.emplace_back(argv[optind++] ); } //printf ("\n"); if( files.size() >= 2 && filename.IsEmpty() && outfilename.IsEmpty() ) { filename = files[0].c_str(); filenames = files; outfilename = files[ files.size() - 1 ].c_str(); filenames.pop_back(); } else { PrintHelp(); return 1; } } if( version ) { //std::cout << "version" << std::endl; PrintVersion(); return 0; } if( help ) { //std::cout << "help" << std::endl; PrintHelp(); return 0; } if( filenames.empty() && filename.IsEmpty() ) { //std::cerr << "Need input file (-i)\n"; PrintHelp(); return 1; } if( outfilename.IsEmpty() ) { //std::cerr << "Need output file (-o)\n"; PrintHelp(); return 1; } // Ok so we are about to write a DICOM file, do not forget to stamp it GDCM ! gdcm::FileMetaInformation::SetSourceApplicationEntityTitle( "gdcmimg" ); if( !rootuid ) { // only read the env var is no explicit cmd line option // maybe there is an env var defined... let's check const char *rootuid_env = getenv("GDCM_ROOT_UID"); if( rootuid_env ) { rootuid = 1; root = rootuid_env; } } if( rootuid ) { if( !gdcm::UIDGenerator::IsValid( root.c_str() ) ) { std::cerr << "specified Root UID is not valid: " << root << std::endl; return 1; } gdcm::UIDGenerator::SetRoot( root.c_str() ); } if( study_uid.empty() ) { study_uid = uid.Generate(); } if( !gdcm::UIDGenerator::IsValid( study_uid.c_str() ) ) { std::cerr << "Invalid UID for Study UID: " << study_uid << std::endl; return 1; } if( series_uid.empty() ) { series_uid = uid.Generate(); } if( !gdcm::UIDGenerator::IsValid( series_uid.c_str() ) ) { std::cerr << "Invalid UID for Series UID: " << series_uid << std::endl; return 1; } // Debug is a little too verbose gdcm::Trace::SetDebug( (debug > 0 ? true : false)); gdcm::Trace::SetWarning( (warning > 0 ? true : false)); gdcm::Trace::SetError( (error > 0 ? true : false)); // when verbose is true, make sure warning+error are turned on: if( verbose ) { gdcm::Trace::SetWarning( (verbose > 0 ? true : false) ); gdcm::Trace::SetError( (verbose > 0 ? true : false) ); } if( depth ) { if( bpp < 1 || bpp > 32 ) { std::cerr << "Invalid depth for pixel: " << bpp << std::endl; return 1; } } if( sign ) { if( pixelsign != 0 && pixelsign != 1 ) return 1; } if( spp ) { if( pixelspp != 1 && pixelspp != 3 && pixelspp != 4 ) return 1; } if( pconf != 0 && pconf != 1 ) return 1; if( pconf ) { if( pixelspp != 3 ) return 1; } gdcm::PixelFormat pfref = gdcm::PixelFormat::UINT8; if( pformat ) { int ba, bs, hb; int n = sscanf( pformatstr.c_str(), "%d,%d,%d", &ba, &bs, &hb ); if( n != 3 ) return 1; pfref.SetBitsAllocated( (unsigned short)ba ); pfref.SetBitsStored( (unsigned short)bs ); pfref.SetHighBit( (unsigned short)hb ); if( spp ) pfref.SetSamplesPerPixel( (unsigned short)pixelspp ); if( sign ) pfref.SetPixelRepresentation( (unsigned short)pixelsign ); } gdcm::PhotometricInterpretation::PIType refpi = gdcm::PhotometricInterpretation::MONOCHROME2; if( pinter ) { refpi = gdcm::PhotometricInterpretation::GetPIType( pinterstr.c_str() ); if( refpi == gdcm::PhotometricInterpretation::UNKNOWN || refpi == gdcm::PhotometricInterpretation::PI_END ) { std::cerr << "Invalid PI: " << pinterstr << std::endl; return 1; } } const char *inputextension = filename.GetExtension(); const char *outputextension = outfilename.GetExtension(); //if( !inputextension || !outputextension ) return 1; if( inputextension ) { gdcm::Reader reader; if( templated ) { reader.SetFileName( templatefilename ); if( !reader.Read() ) { std::cerr << "Failed to read: " << templatefilename << std::endl; return 1; } } if( gdcm::System::StrCaseCmp(inputextension,".raw") == 0 // watch out that .raw for kakadu means big-endian || gdcm::System::StrCaseCmp(inputextension,".rawl") == 0 // kakadu convention for raw little endian || gdcm::System::StrCaseCmp(inputextension,".gray") == 0 // imagemagick convention || gdcm::System::StrCaseCmp(inputextension,".bin") == 0 // openjp3d convention for raw little endian || gdcm::System::StrCaseCmp(inputextension,".rgb") == 0 // imagemagick convention || gdcm::System::StrCaseCmp(inputextension,".yuv") == 0 ) // ffmpeg convention { if( !size[0] || !size[1] ) { std::cerr << "need to specify size of image stored in RAW file" << std::endl; return 1; } gdcm::RAWCodec raw; gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); // Because the RAW stream is not self sufficient, we need to pass in some extra // user info: unsigned int dims[3] = {}; dims[0] = size[0]; dims[1] = size[1]; if( ndimension == 3 ) { dims[2] = size[2]; } raw.SetDimensions( dims ); gdcm::PixelFormat pf = gdcm::PixelFormat::UINT8; gdcm::PhotometricInterpretation pi = refpi; if( gdcm::System::StrCaseCmp(inputextension,".rgb") == 0 ) { pi = gdcm::PhotometricInterpretation::RGB; spp = 1; pixelspp = 3; } if( !GetPixelFormat( pf, depth, bpp, sign, pixelsign, spp, pixelspp ) ) return 1; raw.SetPixelFormat( pf ); if( spp ) { if( pixelspp == 3 && !pinter ) pi = gdcm::PhotometricInterpretation::RGB; } raw.SetPhotometricInterpretation( pi ); raw.SetNeedByteSwap( false ); raw.SetPlanarConfiguration( pconf ); if( endian ) { if( lsb_msb == "LSB" || lsb_msb == "MSB" ) { if( lsb_msb == "MSB" ) { raw.SetNeedByteSwap( true ); } } else { std::cerr << "Unrecognized endian: " << lsb_msb << std::endl; return 1; } } if( !Populate( writer, raw, filenames, ndimension, start_pos ) ) return 1; // populate will guess pixel format and photometric inter from file, need // to override after calling Populate: if( pformat ) { writer.GetPixmap().SetPixelFormat( pfref ); } if( pinter ) { writer.GetPixmap().SetPhotometricInterpretation( refpi ); } if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { std::cerr << "Failed to write: " << outfilename << std::endl; return 1; } return 0; } if( gdcm::System::StrCaseCmp(inputextension,".rle") == 0 ) { if( !size[0] || !size[1] ) { std::cerr << "need to specify size of image stored in RLE file" << std::endl; return 1; } gdcm::RLECodec rle; gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); // Because the RLE stream is not self sufficient, we need to pass in some extra // user info: unsigned int dims[3] = {}; dims[0] = size[0]; dims[1] = size[1]; rle.SetDimensions( dims ); gdcm::PixelFormat pf = gdcm::PixelFormat::UINT8; if( !GetPixelFormat( pf, depth, bpp, sign, pixelsign, spp, pixelspp ) ) return 1; rle.SetPixelFormat( pf ); gdcm::PhotometricInterpretation pi = refpi; if( spp ) { if( pixelspp == 3 && !pinter ) pi = gdcm::PhotometricInterpretation::RGB; } rle.SetPhotometricInterpretation( pi ); if( !Populate( writer, rle, filenames ) ) return 1; if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { return 1; } return 0; } if( gdcm::System::StrCaseCmp(inputextension,".pgm") == 0 || gdcm::System::StrCaseCmp(inputextension,".pnm") == 0 || gdcm::System::StrCaseCmp(inputextension,".pbm") == 0 || gdcm::System::StrCaseCmp(inputextension,".ppm") == 0 ) { gdcm::PNMCodec pnm; // Let's handle the case where user really wants to specify the data: gdcm::PixelFormat pf = gdcm::PixelFormat::UINT8; if( !GetPixelFormat( pf, depth, bpp, sign, pixelsign ) ) return 1; pnm.SetPixelFormat( pf ); gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); if( !Populate( writer, pnm, filenames ) ) return 1; // populate will guess pixel format and photometric inter from file, need // to override after calling Populate: if( pformat ) { writer.GetPixmap().SetPixelFormat( pfref ); } if( pinter ) { writer.GetPixmap().SetPhotometricInterpretation( refpi ); } // HACK if( endian && lsb_msb == "LSB" ) writer.GetPixmap().SetTransferSyntax( gdcm::TransferSyntax::ImplicitVRLittleEndian ); if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { return 1; } return 0; } if( gdcm::System::StrCaseCmp(inputextension,".pgx") == 0 ) { gdcm::PGXCodec pnm; gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); if( !Populate( writer, pnm, filenames ) ) return 1; if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { return 1; } return 0; } if( gdcm::System::StrCaseCmp(inputextension,".jls") == 0 ) { gdcm::JPEGLSCodec jpeg; gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); if( !Populate( writer, jpeg, filenames ) ) return 1; if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { return 1; } return 0; } if( gdcm::System::StrCaseCmp(inputextension,".jp2") == 0 || gdcm::System::StrCaseCmp(inputextension,".j2k") == 0 || gdcm::System::StrCaseCmp(inputextension,".j2c") == 0 || gdcm::System::StrCaseCmp(inputextension,".jpx") == 0 || gdcm::System::StrCaseCmp(inputextension,".jpf") == 0 || gdcm::System::StrCaseCmp(inputextension,".jpc") == 0 ) { /* * FIXME: Same problem as in classic JPEG: JP2 is NOT a J2K byte stream * need to chop off all extra header information... */ gdcm::JPEG2000Codec jpeg; gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); if( !Populate( writer, jpeg, filenames ) ) return 1; if( pinter ) { writer.GetPixmap().SetPhotometricInterpretation( refpi ); } if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { return 1; } return 0; } if( gdcm::System::StrCaseCmp(inputextension,".jpg") == 0 || gdcm::System::StrCaseCmp(inputextension,".jpeg") == 0 || gdcm::System::StrCaseCmp(inputextension,".ljpg") == 0 || gdcm::System::StrCaseCmp(inputextension,".ljpeg") == 0 ) { gdcm::JPEGCodec jpeg; // Let's handle the case where user really wants to specify signess of data: gdcm::PixelFormat pf = gdcm::PixelFormat::UINT8; if( !GetPixelFormat( pf, depth, bpp, sign, pixelsign ) ) return 1; jpeg.SetPixelFormat( pf ); gdcm::PixmapWriter writer; writer.SetCheckFileMetaInformation( (keepmeta > 0 ? false : true) ); writer.SetFile( reader.GetFile() ); if( !Populate( writer, jpeg, filenames ) ) return 1; if( !AddUIDs(sopclassuid, sopclass, study_uid, series_uid, writer ) ) return 1; writer.SetFileName( outfilename ); if( !writer.Write() ) { std::cerr << "Problem during DICOM steps" << std::endl; return 1; } return 0; } } // else safely assume that if no inputextension matched then it is a DICOM file gdcm::PixmapReader reader; reader.SetFileName( filename ); if( !reader.Read() ) { std::cerr << "Failed to read: " << filename << std::endl; return 1; } const gdcm::Pixmap &imageori = reader.GetPixmap(); const gdcm::File &file = reader.GetFile(); if ( outputextension ) { if( gdcm::System::StrCaseCmp(outputextension,".pgm") == 0 || gdcm::System::StrCaseCmp(outputextension,".pnm") == 0 || gdcm::System::StrCaseCmp(outputextension,".pbm") == 0 || gdcm::System::StrCaseCmp(outputextension,".ppm") == 0 ) { gdcm::PNMCodec pnm; pnm.SetDimensions( imageori.GetDimensions() ); pnm.SetPixelFormat( imageori.GetPixelFormat() ); pnm.SetPhotometricInterpretation( imageori.GetPhotometricInterpretation() ); pnm.SetPlanarConfiguration( imageori.GetPlanarConfiguration() ); pnm.SetLUT( imageori.GetLUT() ); const gdcm::DataElement& in = imageori.GetDataElement(); bool b = pnm.Write( outfilename, in ); if( !b ) { std::cerr << "Problem writing PNM file" << std::endl; return 1; } return 0; } if( gdcm::System::StrCaseCmp(outputextension,".pgx") == 0 ) { gdcm::PGXCodec pnm; pnm.SetDimensions( imageori.GetDimensions() ); pnm.SetPixelFormat( imageori.GetPixelFormat() ); pnm.SetPhotometricInterpretation( imageori.GetPhotometricInterpretation() ); pnm.SetPlanarConfiguration( imageori.GetPlanarConfiguration() ); pnm.SetLUT( imageori.GetLUT() ); const gdcm::DataElement& in = imageori.GetDataElement(); bool b = pnm.Write( outfilename, in ); if( !b ) { std::cerr << "Problem writing PNM file" << std::endl; return 1; } return 0; } } // else safely assume that if no outputextension matched then it is a DICOM file gdcm::PixmapWriter writer; writer.SetFile( file ); writer.SetImage( imageori ); writer.SetFileName( outfilename ); gdcm::DataSet &ds = writer.GetFile().GetDataSet(); if( fill ) { const gdcm::PixelFormat &pixeltype = imageori.GetPixelFormat(); assert( imageori.GetNumberOfDimensions() == 2 || imageori.GetNumberOfDimensions() == 3 ); unsigned long len = imageori.GetBufferLength(); gdcm::SmartPointer image = new gdcm::Pixmap; image->SetNumberOfDimensions( 2 ); // good default const unsigned int *dims = imageori.GetDimensions(); if ( region[0] > region[1] || region[2] > region[3] || region[4] > region[5] || region[1] >= dims[0] || region[3] >= dims[1] || (imageori.GetNumberOfDimensions() > 2 && region[5] >= dims[2]) ) { if( imageori.GetNumberOfDimensions() == 2 ) { std::cerr << "bogus region. Should be at most: (" << dims[0] << "," << dims[1] << "," /*<< dims[2]*/ << ")" << std::endl; } else { std::cerr << "bogus region. Should be at most: (" << dims[0] << "," << dims[1] << "," << dims[2] << ")" << std::endl; } return 1; } image->SetDimension(0, dims[0] ); image->SetDimension(1, dims[1] ); if( imageori.GetNumberOfDimensions() == 3 ) { image->SetNumberOfDimensions( 3 ); image->SetDimension(2, dims[2] ); } image->SetPhotometricInterpretation( imageori.GetPhotometricInterpretation() ); image->SetPixelFormat( imageori.GetPixelFormat() ); image->SetPlanarConfiguration( imageori.GetPlanarConfiguration() ); image->SetLUT( imageori.GetLUT() ); image->SetLossyFlag( imageori.IsLossy() ); // FIXME what is overlay is in pixel data ? gdcm::DataElement pixeldata( gdcm::Tag(0x7fe0,0x0010) ); gdcm::ByteValue *bv = new gdcm::ByteValue(); bv->SetLength( (uint32_t)len ); //memcpy( bv->GetPointer(), imageori imageori.GetBuffer( (char*)bv->GetVoidPointer() ); // Rub out pixels: char *p = (char*)bv->GetVoidPointer(); switch(pixeltype) { case gdcm::PixelFormat::UINT8: FillRegionWithColor ((uint8_t*)p, dims, region, color, pixeltype.GetSamplesPerPixel()); break; case gdcm::PixelFormat::INT8: FillRegionWithColor ((int8_t*)p, dims, region, color, pixeltype.GetSamplesPerPixel()); break; case gdcm::PixelFormat::UINT16: FillRegionWithColor((uint16_t*)p, dims, region, color, pixeltype.GetSamplesPerPixel()); break; case gdcm::PixelFormat::INT16: FillRegionWithColor ((int16_t*)p, dims, region, color, pixeltype.GetSamplesPerPixel()); break; default: std::cerr << "not implemented" << std::endl; return 1; } pixeldata.SetValue( *bv ); image->SetDataElement( pixeldata ); const gdcm::TransferSyntax &ts = imageori.GetTransferSyntax(); // FIXME: for now we do not know how to recompress the image... if( ts.IsExplicit() ) { image->SetTransferSyntax( gdcm::TransferSyntax::ExplicitVRLittleEndian ); } else { assert( ts.IsImplicit() ); image->SetTransferSyntax( gdcm::TransferSyntax::ImplicitVRLittleEndian ); } //imageori.Print( std::cout ); //image.Print( std::cout ); // Set our filled image instead: writer.SetImage( *image ); #if 0 // gdcm::Attribute<0x0028,0x0301> at; at.SetValue( "NO" ); // 'YES' ds.Replace( at.GetAsDataElement() ); // (0008,2111) ST [MedCom Resample v] # 18, 1 DerivationDescriptio gdcm::Attribute<0x0008,0x2111> at2; std::ostringstream os; os << "Fill Region [" << region[0] << "," << region[1] << "," << region[2] << "," << region[3] << "," << region[4] << "," << region[5] << "] with color value=" << std::hex << (int)color; at2.SetValue( os.str() ); ds.Replace( at2.GetAsDataElement() ); #else #endif /* > 1. Replace Value #1 of Image Type by 'DERIVED' Don't do that ... leave Image Type alone (unless you are changing the UID ... vide infra). */ #if 0 // (0008,0008) CS [ORIGINAL\SECONDARY] # 18, 2 ImageType gdcm::Attribute<0x0008,0x0008> at3; static const gdcm::CSComp values[] = {"DERIVED","SECONDARY"}; at3.SetValues( values, 2, true ); // true => copy data ! if( ds.FindDataElement( at3.GetTag() ) ) { const gdcm::DataElement &de = ds.GetDataElement( at3.GetTag() ); at3.SetFromDataElement( de ); // Make sure that value #1 is at least 'DERIVED', so override in all cases: at3.SetValue( 0, values[0] ); } ds.Replace( at3.GetAsDataElement() ); #endif // Make sure to recompute Planar Configuration: ds.Remove( gdcm::Tag(0x0028, 0x0004) ); } // ds.Remove( gdcm::Tag(0x0,0x0) ); // FIXME if( !writer.Write() ) { std::cerr << "Failed to write: " << outfilename << std::endl; return 1; } return 0; }