/*========================================================================= 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: * Should implement the gdcmiodvfy here * I need to implement gdcmoverlay here (print info on overlay / img / LUT ...) */ #include "gdcmReader.h" #include "gdcmImageReader.h" #include "gdcmMediaStorage.h" #include "gdcmFile.h" #include "gdcmDataSet.h" #include "gdcmUIDs.h" #include "gdcmGlobal.h" #include "gdcmModules.h" #include "gdcmDefs.h" #include "gdcmOrientation.h" #include "gdcmVersion.h" #include "gdcmMD5.h" #include "gdcmSystem.h" #include "gdcmDirectory.h" #include "gdcmImageHelper.h" #include "gdcmSplitMosaicFilter.h" #include "gdcmImageChangePlanarConfiguration.h" #ifdef GDCM_USE_SYSTEM_POPPLER #include #include #include #include #include #endif // GDCM_USE_SYSTEM_POPPLER #include "puff.h" #include #include /* for printf */ #include #include /* for exit */ #include #include #include #include static int checkmagick(unsigned char *input) { if( input[128+0] == 'D' && input[128+1] == 'I' && input[128+2] == 'C' && input[128+3] == 'M' ) { return 1; } return 0; } static int checkdeflated(const char *name) { int ret; unsigned char *source; unsigned long len, sourcelen, destlen; unsigned long size; unsigned long size1; unsigned char *buf; FILE *in; struct stat s; //const char *name = 0; union { uint32_t tag; uint16_t tags[2]; char bytes[4]; } tag; char vr[3]; uint16_t vl; uint32_t value; //if (argc < 2) return 2; //name = argv[1]; len = 0; if (stat(name, &s)) { fprintf( stderr, "Cannot stat: %s\n", name ); return 1; } if ((s.st_mode & S_IFMT) != S_IFREG) { fprintf( stderr, "not a regular file\n" ); return 1; } size = (unsigned long)(s.st_size); if (size == 0 || (off_t)size != s.st_size) { fprintf( stderr, "size mismatch\n" ); return 1; } in = fopen(name, "r"); if (in == nullptr) { fprintf( stderr, "in is NULL\n" ); return 1; } buf = (unsigned char*)malloc(size); if (buf != nullptr && (size1 = (unsigned long)fread(buf, 1, size, in)) != size) { free(buf); buf = nullptr; fprintf( stderr, "could not fread: %lu bytes != %lu\n", size, size1 ); fprintf( stderr, "feof: %i ferror %i\n", feof(in), ferror(in) ); } fclose(in); len = size; source = buf; if( source == nullptr ) { fprintf( stderr, "source is NULL\n" ); return 1; } sourcelen = len; if( !checkmagick(source) ) { fprintf( stderr, "checkmagick failed\n" ); return 1; } // magick succeed so skip header: source += 128 + 4; sourcelen -= 128 + 4; memcpy(&tag, source, sizeof(tag) ); fprintf( stdout,"tag: %d, %d\n", tag.tags[0], tag.tags[1] ); source += sizeof(tag); sourcelen -= sizeof(tag); vr[2] = 0; memcpy(vr, source, 2); printf( "vr: %s\n", vr); source += 2; sourcelen -= 2; memcpy(&vl, source, sizeof(vl)); printf( "vl: %d\n", vl); source += sizeof(vl); sourcelen -= sizeof(vl); memcpy(&value, source, sizeof(value)); printf( "value: %d\n", value); source += sizeof(value); sourcelen -= sizeof(value); source += value; sourcelen -= value; len = sourcelen; if( len % 2 ) { printf( "len of bit stream is odd: %lu. Continuing anyway\n", len ); } else { printf( "deflate stream has proper length: %lu\n", len ); } ret = puff(nullptr, &destlen, source, &sourcelen); if (ret) fprintf(stdout,"puff() failed with return code %d\n", ret); else { fprintf(stdout,"puff() succeeded uncompressing %lu bytes\n", destlen); if (sourcelen < len) printf("%lu compressed bytes unused\n", len - sourcelen); } free(buf); return ret; } #ifdef GDCM_USE_SYSTEM_POPPLER static std::string getInfoDate(Dict *infoDict, const char *key) { Object obj; const char *s; int year, mon, day, hour, min, sec, n; struct tm tmStruct; //char buf[256]; std::string out; #ifdef LIBPOPPLER_NEW_OBJECT_API if ((obj = infoDict->lookup(const_cast(key))).isString()) #else if (infoDict->lookup((char*)key, &obj)->isString()) #endif { const GooString* gs = obj.getString(); #ifdef LIBPOPPLER_GOOSTRING_HAS_GETCSTRING s = gs->getCString(); #else s = gs->c_str(); #endif if (s[0] == 'D' && s[1] == ':') { s += 2; } if ((n = sscanf(s, "%4d%2d%2d%2d%2d%2d", &year, &mon, &day, &hour, &min, &sec)) >= 1) { switch (n) { case 1: mon = 1; /* fall through */ case 2: day = 1; /* fall through */ case 3: hour = 0; /* fall through */ case 4: min = 0; /* fall through */ case 5: sec = 0; /* fall through */ } tmStruct.tm_year = year - 1900; tmStruct.tm_mon = mon - 1; tmStruct.tm_mday = day; tmStruct.tm_hour = hour; tmStruct.tm_min = min; tmStruct.tm_sec = sec; tmStruct.tm_wday = -1; tmStruct.tm_yday = -1; tmStruct.tm_isdst = -1; /* // compute the tm_wday and tm_yday fields if (mktime(&tmStruct) != (time_t)-1 && strftime(buf, sizeof(buf), "%c", &tmStruct)) { fputs(buf, stdout); } else { fputs(s, stdout); } } else { fputs(s, stdout); */ } //fputc('\n', stdout); char date[22]; time_t t = mktime(&tmStruct); if( t != -1 ) { if( gdcm::System::FormatDateTime(date, t) ) out = date; } } #ifndef LIBPOPPLER_NEW_OBJECT_API obj.free(); #endif return out; } #ifdef LIBPOPPLER_UNICODEMAP_HAS_CONSTMAPUNICODE static std::string getInfoString(Dict *infoDict, const char *key, const UnicodeMap *uMap) #else static std::string getInfoString(Dict *infoDict, const char *key, UnicodeMap *uMap) #endif { Object obj; #ifdef LIBPOPPLER_GOOSTRING_HAS_CONSTGETCHAR const GooString *s1; #else GooString *s1; #endif bool isUnicode; Unicode u; char buf[8]; int i, n; std::string out; #ifdef LIBPOPPLER_NEW_OBJECT_API if ((obj = infoDict->lookup(const_cast(key))).isString()) #else if (infoDict->lookup((char*)key, &obj)->isString()) #endif { s1 = obj.getString(); if ((s1->getChar(0) & 0xff) == 0xfe && (s1->getChar(1) & 0xff) == 0xff) { isUnicode = true; i = 2; } else { isUnicode = false; i = 0; } while (i < obj.getString()->getLength()) { if (isUnicode) { u = ((s1->getChar(i) & 0xff) << 8) | (s1->getChar(i+1) & 0xff); i += 2; } else { u = pdfDocEncoding[s1->getChar(i) & 0xff]; ++i; } n = uMap->mapUnicode(u, buf, sizeof(buf)); //fwrite(buf,1,n,stdout); out.append( std::string(buf, n) ); } } #ifndef LIBPOPPLER_NEW_OBJECT_API obj.free(); #endif return out; } #endif static void PrintVersion() { std::cout << "gdcminfo: gdcm " << gdcm::Version::GetVersion() << " "; const char date[] = "$Date$"; std::cout << date << std::endl; } static void PrintHelp() { PrintVersion(); std::cout << "Usage: gdcminfo [OPTION]... FILE..." << std::endl; std::cout << "display meta info about the input DICOM file" << std::endl; std::cout << "Parameter:" << std::endl; std::cout << " -i --input DICOM filename or directory" << std::endl; std::cout << "Options:" << std::endl; std::cout << " -r --recursive recursive." << std::endl; std::cout << " -d --check-deflated check if file is proper deflated syntax." << std::endl; std::cout << " --resources-path Resources path." << std::endl; std::cout << " --md5sum Compute md5sum of Pixel Data attribute value." << std::endl; std::cout << " --check-compression check the encapsulated stream compression (lossless/lossy)." << std::endl; // the following options would require an advanced MediaStorage::SetFromFile ... sigh //std::cout << " --media-storage-uid return media storage uid only." << std::endl; //std::cout << " --media-storage-name return media storage name only (when possible)." << std::endl; // std::cout << " -b --check-big-endian check if file is ." << std::endl; std::cout << " --force-rescale force rescale." << std::endl; std::cout << " --force-spacing force spacing." << std::endl; std::cout << " --mosaic dump image information of MOSAIC." << 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_RESOURCES_PATH path pointing to resources files (Part3.xml, ...)" << std::endl; } int deflated = 0; // check deflated int checkcompression = 0; int md5sum = 0; int mosaic = 0; static int ProcessOneFile( std::string const & filename, gdcm::Defs const & defs ) { (void)defs; if( deflated ) { return checkdeflated(filename.c_str()); } //const char *filename = argv[1]; //std::cout << "filename: " << filename << std::endl; gdcm::Reader reader0; reader0.SetFileName( filename.c_str() ); if( !reader0.Read() ) { std::cerr << "Failed to read: " << filename << std::endl; return 1; } const gdcm::File &file = reader0.GetFile(); gdcm::MediaStorage ms; ms.SetFromFile(file); /* * Until gdcm::MediaStorage is fixed only *compile* time constant will be handled * see -> http://chuckhahm.com/Ischem/Zurich/XX_0134 * which make gdcm::UIDs useless :( */ if( ms.IsUndefined() ) { std::cerr << "Unknown MediaStorage" << std::endl; return 1; } gdcm::UIDs uid; uid.SetFromUID( ms.GetString() ); std::cout << "MediaStorage is " << ms << " [" << uid.GetName() << "]" << std::endl; const gdcm::TransferSyntax &ts = file.GetHeader().GetDataSetTransferSyntax(); uid.SetFromUID( ts.GetString() ); std::cout << "TransferSyntax is " << ts << " [" << uid.GetName() << "]" << std::endl; if( gdcm::MediaStorage::IsImage( ms ) ) { gdcm::ImageReader reader; reader.SetFileName( filename.c_str() ); if( !reader.Read() ) { std::cerr << "Could not read image from: " << filename << std::endl; return 1; } gdcm::SplitMosaicFilter filter; const gdcm::Image *pimage = nullptr; const gdcm::Image &image = reader.GetImage(); if( mosaic ) { filter.SetImage( image ); filter.SetFile( reader.GetFile() ); if( !filter.Split() ) { std::cerr << "Could not split mosaic : " << filename << std::endl; return 1; } pimage = &filter.GetImage(); } else { pimage = ℑ } const double *dircos = pimage->GetDirectionCosines(); gdcm::Orientation::OrientationType type = gdcm::Orientation::GetType(dircos); const char *label = gdcm::Orientation::GetLabel( type ); pimage->Print( std::cout ); std::cout << "Orientation Label: " << label << std::endl; if( checkcompression ) { bool lossy = pimage->IsLossy(); std::cout << "Encapsulated Stream was found to be: " << (lossy ? "lossy" : "lossless") << std::endl; } if( md5sum ) { int ret = 0; char *buffer = new char[ pimage->GetBufferLength() ]; gdcm::ImageChangePlanarConfiguration icpc; icpc.SetPlanarConfiguration( 0 ); icpc.SetInput( *pimage ); bool b = icpc.Change(); if( !b ) { std::cerr << "Could not change the Planar Configuration: " << filename << std::endl; return 1; } pimage = &icpc.GetOutput(); if( pimage->GetBuffer( buffer ) ) { char digest[33] = {}; gdcm::MD5::Compute( buffer, pimage->GetBufferLength(), digest ); std::cout << "md5sum: " << digest << std::endl; } else { std::cerr << "Problem decompressing file: " << filename << std::endl; ret = 1; } delete[] buffer; return ret; } } else if ( ms == gdcm::MediaStorage::EncapsulatedPDFStorage ) { #ifdef GDCM_USE_SYSTEM_POPPLER const gdcm::DataSet &ds = file.GetDataSet(); const gdcm::DataElement& de = ds.GetDataElement( gdcm::Tag(0x42,0x11) ); const gdcm::ByteValue* bv = de.GetByteValue(); const char *p = bv->GetPointer(); (void)p; Object appearDict; //appearDict.initDict(xref); //appearDict.dictAdd(copyString("Length"), // obj1.initInt(appearBuf->getLength())); //appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); //obj1.initArray(xref); //obj1.arrayAdd(obj2.initReal(0)); //obj1.arrayAdd(obj2.initReal(0)); //obj1.arrayAdd(obj2.initReal(xMax - xMin)); //obj1.arrayAdd(obj2.initReal(yMax - yMin)); //appearDict.dictAdd(copyString("BBox"), &obj1); MemStream *appearStream; appearStream = new MemStream(const_cast(bv->GetPointer()), 0, #ifdef LIBPOPPLER_NEW_OBJECT_API bv->GetLength(), std::move(appearDict)); #else bv->GetLength(), &appearDict); #endif GooString *ownerPW, *userPW; ownerPW = NULL; userPW = NULL; PDFDoc *doc; doc = new PDFDoc(appearStream, ownerPW, userPW); std::string title; std::string subject; std::string keywords; std::string author; std::string creator; std::string producer; std::string creationdate; std::string moddate; #ifdef LIBPOPPLER_UNICODEMAP_HAS_CONSTMAPUNICODE const UnicodeMap *uMap; #else UnicodeMap *uMap; #endif #ifdef LIBPOPPLER_GLOBALPARAMS_CSTOR_HAS_PARAM globalParams = new GlobalParams(0); #else #ifdef LIBPOPPLER_GLOBALPARAMS_HAS_RESET globalParams.reset(new GlobalParams()); #else globalParams = new GlobalParams(); #endif #endif uMap = globalParams->getTextEncoding(); Object info; if (doc->isOk()) { #ifdef LIBPOPPLER_NEW_OBJECT_API info = doc->getDocInfo(); #else doc->getDocInfo(&info); #endif if (info.isDict()) { title = getInfoString(info.getDict(), "Title", uMap); subject = getInfoString(info.getDict(), "Subject", uMap); keywords = getInfoString(info.getDict(), "Keywords", uMap); author = getInfoString(info.getDict(), "Author", uMap); creator = getInfoString(info.getDict(), "Creator", uMap); producer = getInfoString(info.getDict(), "Producer", uMap); creationdate = getInfoDate( info.getDict(), "CreationDate" ); moddate = getInfoDate( info.getDict(), "ModDate" ); #ifndef LIBPOPPLER_NEW_OBJECT_API info.free(); #endif } #ifdef LIBPOPPLER_CATALOG_HAS_STRUCTTREEROOT const char *tagged = doc->getStructTreeRoot() ? "yes" : "no"; #else const char *tagged = doc->getStructTreeRoot()->isDict() ? "yes" : "no"; #endif int pages = doc->getNumPages(); const char *encrypted = doc->isEncrypted() ? "yes" : "no"; // printf("yes (print:%s copy:%s change:%s addNotes:%s)\n", // doc->okToPrint(true) ? "yes" : "no", // doc->okToCopy(true) ? "yes" : "no", // doc->okToChange(true) ? "yes" : "no", // doc->okToAddNotes(true) ? "yes" : "no"); // print linearization info const char *optimized = doc->isLinearized() ? "yes" : "no"; // print PDF version #ifdef LIBPOPPLER_PDFDOC_HAS_PDFVERSION float pdfversion = doc->getPDFVersion(); #else const double pdfversion = doc->getPDFMajorVersion() + 0.1 * doc->getPDFMinorVersion(); #endif // print page count printf("Pages: %d\n", doc->getNumPages()); std::cout << "PDF Info:" << std::endl; std::cout << " Title: " << title << std::endl; std::cout << " Subject: " << subject << std::endl; std::cout << " Keywords: " << keywords << std::endl; std::cout << " Author: " << author << std::endl; std::cout << " Creator: " << creator << std::endl; std::cout << " Producer: " << producer << std::endl; std::cout << " CreationDate: " << creationdate << std::endl; std::cout << " ModDate: " << moddate << std::endl; std::cout << " Tagged: " << tagged << std::endl; std::cout << " Pages: " << pages << std::endl; std::cout << " Encrypted: " << encrypted << std::endl; //std::cout << "Page size: " << subject << std::endl; std::cout << " File size: " << bv->GetLength() << std::endl; std::cout << " Optimized: " << optimized << std::endl; std::cout << " PDF version: " << pdfversion << std::endl; } else { std::cout << "Problem reading Encapsulated PDF " << std::endl; } #else // GDCM_USE_SYSTEM_POPPLER std::cout << " Encapsulated PDF File" << std::endl; #endif // GDCM_USE_SYSTEM_POPPLER } // Do the IOD verification ! //bool v = defs.Verify( file ); //std::cerr << "IOD Verification: " << (v ? "succeed" : "failed") << std::endl; return 0; } int main(int argc, char *argv[]) { int c; std::string filename; std::string xmlpath; int forcerescale = 0; int forcespacing = 0; int resourcespath = 0; int verbose = 0; int warning = 0; int help = 0; int recursive = 0; int version = 0; int debug = 0; int error = 0; while (true) { int option_index = 0; static struct option long_options[] = { {"input", 1, nullptr, 0}, {"recursive", 0, &recursive, 1}, {"check-deflated", 0, &deflated, 1}, {"resources-path", 0, &resourcespath, 1}, {"md5sum", 0, &md5sum, 1}, {"check-compression", 0, &checkcompression, 1}, {"force-rescale", 0, &forcerescale, 1}, {"force-spacing", 0, &forcespacing, 1}, {"mosaic", 0, &mosaic, 1}, {"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} // required }; static const char short_options[] = "i:rdVWDEhv"; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (c == -1) { break; } switch (c) { case 0: case '-': { 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.empty() ); filename = optarg; } else if( option_index == 3 ) /* resources-path */ { assert( strcmp(s, "resources-path") == 0 ); assert( xmlpath.empty() ); xmlpath = optarg; } //printf (" with arg %s", optarg); } //printf ("\n"); } break; case 'i': //printf ("option i with value '%s'\n", optarg); assert( filename.empty() ); filename = optarg; break; case 'r': recursive = 1; break; case 'd': deflated = 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); } } if (optind < argc) { //printf ("non-option ARGV-elements: %d", optind ); //while (optind < argc) // { // printf ("%s\n", argv[optind++]); // } //printf ("\n"); // Ok there is only one arg, easy, it's the filename: int v = argc - optind; if( v == 1 ) { filename = argv[optind]; } } if( version ) { //std::cout << "version" << std::endl; PrintVersion(); return 0; } if( help ) { //std::cout << "help" << std::endl; PrintHelp(); return 0; } gdcm::ImageHelper::SetForceRescaleInterceptSlope(forcerescale ? true : false); gdcm::ImageHelper::SetForcePixelSpacing(forcespacing ? true : false); if( filename.empty() ) { //std::cerr << "Need input file (-i)\n"; PrintHelp(); return 1; } // Debug is a little too verbose gdcm::Trace::SetDebug( debug != 0); gdcm::Trace::SetWarning( warning != 0); gdcm::Trace::SetError( error != 0); // when verbose is true, make sure warning+error are turned on: if( verbose ) { gdcm::Trace::SetWarning( verbose != 0); gdcm::Trace::SetError( verbose != 0); } if( !gdcm::System::FileExists(filename.c_str()) ) { return 1; } gdcm::Global& g = gdcm::Global::GetInstance(); // First thing we need to locate the XML dict // did the user requested to look XML file in a particular directory ? if( !resourcespath ) { const char *xmlpathenv = getenv("GDCM_RESOURCES_PATH"); if( xmlpathenv ) { // Make sure to look for XML dict in user explicitly specified dir first: xmlpath = xmlpathenv; resourcespath = 1; } } if( resourcespath ) { // xmlpath is set either by the cmd line option or the env var if( !g.Prepend( xmlpath.c_str() ) ) { std::cerr << "specified Resources Path is not valid: " << xmlpath << std::endl; return 1; } } // All set, then load the XML files: if( !g.LoadResourcesFiles() ) { std::cerr << "Could not load XML file from specified path" << std::endl; return 1; } const gdcm::Defs &defs = g.GetDefs(); int res = 0; if( gdcm::System::FileIsDirectory(filename.c_str()) ) { gdcm::Directory d; d.Load(filename, recursive!= 0); gdcm::Directory::FilenamesType const &filenames = d.GetFilenames(); for( gdcm::Directory::FilenamesType::const_iterator it = filenames.begin(); it != filenames.end(); ++it ) { std::cout << "filename: " << *it << std::endl; res += ProcessOneFile(*it, defs); } } else { res += ProcessOneFile( filename, defs ); } return res; }