/* * * Copyright (C) 1994-2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmdata * * Author: Andreas Barth * * Purpose: create a DICOM FileFormat or DataSet from an ASCII-dump * */ /* * Input File Description: * The input file can be an output of dcmdump. One element (tag, VR, value) * must be written into one line separated by arbitrary spaces or tab characters. * A '#' begins a comment that ends at the line end. Empty lines are allowed. * The individual parts of a line have the following syntax: * Tag: (gggg,eeee) * with gggg and eeee are 4 character hexadecimal values representing * group and element tag. Spaces and tabs can be anywhere in a tag * specification. * VR: Value Representation must be written as 2 characters as in Part 6 * of the DICOM standard. No spaces or tabs are allowed between the * two characters. If the VR can determined from the tag, this part * of a line is optional. * Value: There are several rules for writing values: * 1. US, SS, UL, SL, UV, SV, FD, FL, OD, OF, OL and OV are written as * decimal strings that can be read by scanf(). * 2. AT is written as '(gggg,eeee)' with additional spaces stripped * off automatically and gggg and eeee being decimal strings that * can be read by scanf(). * 3. OB and OW values are written as byte or word hexadecimal values * separated by '\' character. Alternatively, OB or OW values can * be read from a separate file by writing the filename prefixed * by a '=' character (e.g. '=largepix.dat'). The contents of the * file will be read as is. By default, OW data is expected to be * little endian ordered and will be swapped if necessary. No * checks will be made to ensure that the amount of data is * reasonable in terms of other attributes such as Rows or Columns. * In case of compressed pixel data, the line should start with * '(7fe0,0010) OB (PixelSequence' in order to distinguish from * uncompressed pixel data. * 4. UI is written as '=Name' in data dictionary or as unique * identifier string (see 6.) , e.g. '[1.2.840.....]' * 5. Strings without () <> [] spaces, tabs and # can be written * directly. * 6. Other strings must be surrounded by '[' and ']'. No bracket * structure is passed. The value ends at the last ']' in the * line. Anything after the ']' is interpreted as comment. * 7. '(' and '<' are interpreted special and may not be used when * writing an input file by hand as beginning characters of a * string. Multiple Value are separated by '\'. The sequence of * lines must not be ordered but they can. References in DICOM * Directories are not supported. Semantic errors are not detected. * * Examples: * (0008,0020) DA [19921012] # 8, 1 StudyDate * (0008,0016) UI =MRImageStorage # 26, 1 SOPClassUID * (0002,0012) UI [1.2.276.0.7230010.100.1.1] * (0020,0032) DS [0.0\0.0] # 8, 2 ImagePositionPatient * (0028,0009) AT (3004,000c) # 4, 1 FrameIncrementPointer * (0028,0010) US 256 # 4, 1 Rows * (0002,0001) OB 01\00 * */ #include "dcmtk/config/osconfig.h" // if defined, use createValueFromTempFile() for large binary data files //#define EXPERIMENTAL_READ_FROM_FILE #ifdef HAVE_SYS_TYPES_H #include #endif #include "dcmtk/ofstd/ofstd.h" #include "dcmtk/ofstd/ofconapp.h" #include "dcmtk/ofstd/ofstream.h" #include "dcmtk/dcmdata/dctk.h" #include "dcmtk/dcmdata/dcpxitem.h" #include "dcmtk/dcmdata/cmdlnarg.h" #include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */ #include "dcmtk/dcmdata/dcostrmz.h" /* for dcmZlibCompressionLevel */ #ifdef EXPERIMENTAL_READ_FROM_FILE #include "dcmtk/dcmdata/dcistrmf.h" /* for class DcmInputFileStream */ #endif #ifdef WITH_ZLIB #include /* for zlibVersion() */ #endif #define OFFIS_CONSOLE_APPLICATION "dump2dcm" static OFLogger dump2dcmLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION); static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v" OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $"; #define SHORTCOL 3 #define LONGCOL 21 // Maximum Line Size (default) const unsigned int DCM_DumpMaxLineSize = 4096; // Global variables static E_ByteOrder opt_fileContentsByteOrdering = EBO_LittleEndian; // Field definition separators #define DCM_DumpCommentChar '#' #define DCM_DumpTagDelim ')' #define DCM_DumpOpenString '[' #define DCM_DumpCloseString ']' #define DCM_DumpOpenDescription '(' #define DCM_DumpOpenFile '<' #define DCM_DumpCloseFile '>' #define TO_UCHAR(s) OFstatic_cast(unsigned char, (s)) static void stripWhitespace(char *s) { char *p; if (s == NULL) return; p = s; while (*s != '\0') { if (isspace(TO_UCHAR(*s)) == OFFalse) { *p++ = *s; } s++; } *p = '\0'; } static char * stripTrailingWhitespace(char *s) { if (s == NULL) return s; for (size_t i = strlen(s); i > 0 && isspace(TO_UCHAR(s[--i])); s[i] = '\0'); return s; } static char * stripPrecedingWhitespace(char *s) { char *p; if (s == NULL) return s; for(p = s; *p && isspace(TO_UCHAR(*p)); p++) ; return p; } static OFBool onlyWhitespace(const char *s) { while(*s) if (!isspace(TO_UCHAR(*s++))) return OFFalse; return OFTrue; } static char * getLine(char *line, int maxLineLen, FILE *f, const unsigned long lineNumber) { char *s; s = fgets(line, maxLineLen, f); // if line is too long, throw rest of it away if (s && strlen(s) == size_t(maxLineLen - 1) && s[maxLineLen - 2] != '\n') { int c = fgetc(f); while(c != '\n' && c != EOF) c = fgetc(f); OFLOG_ERROR(dump2dcmLogger, "line " << lineNumber << " too long"); } /* strip any trailing white space */ stripTrailingWhitespace(s); return s; } static OFBool isaCommentLine(const char *s) { // skip leading spaces while(isspace(TO_UCHAR(*s))) ++s; return *s == DCM_DumpCommentChar; } static OFBool parseTag(char *&s, DcmTagKey &key) { OFBool ok = OFTrue; char *p; unsigned int g, e; // find tag begin p = strchr(s, DCM_DumpTagDelim); if (p) { // string all white spaces and read tag size_t len = p - s + 1; p = new char[len + 1]; OFStandard::strlcpy(p, s, len + 1); stripWhitespace(p); s += len; if (sscanf(p, "(%x,%x)", &g, &e) == 2) key.set(OFstatic_cast(Uint16, g), OFstatic_cast(Uint16, e)); else ok = OFFalse; delete[] p; } else ok = OFFalse; return ok; } static OFBool parseVR(char *&s, DcmEVR &vr) { OFBool ok = OFTrue; s = stripPrecedingWhitespace(s); // Are there two upper characters? if (isupper(TO_UCHAR(*s)) && isupper(TO_UCHAR(*(s + 1)))) { char c_vr[3]; OFStandard::strlcpy(c_vr, s, 3); // Convert to VR DcmVR dcmVR(c_vr); vr = dcmVR.getEVR(); s += 2; } else if ((*s == 'p') && (*(s + 1) == 'i')) { vr = EVR_pixelItem; s += 2; } else if (((*s == 'o') && (*(s + 1) == 'x')) || ((*s == 'x') && (*(s + 1) == 's')) || ((*s == 'n') && (*(s + 1) == 'a')) || ((*s == 'u') && (*(s + 1) == 'p'))) { // swallow internal VRs vr = EVR_UNKNOWN; s += 2; } // dcmdump uses "??" in case of "Unknown Tag & Data" and implicit VR else if ((*s == '?') && (*(s + 1) == '?')) { vr = EVR_UNKNOWN; s += 2; } else ok = OFFalse; return ok; } #undef TO_UCHAR static size_t searchLastClose(char *s, const char closeChar) { // search last close bracket in a line // no bracket structure will be considered char *found = NULL; char *p = s; while(p && *p) { p = strchr(p, closeChar); if (p) { found = p; p++; } } if (found) return (found - s) + 1; else return 0; } static size_t searchCommentOrEol(char *s) { char *comment = strchr(s, DCM_DumpCommentChar); if (comment) return comment - s; else if (s) return strlen(s); else return 0; } static void convertNewlineCharacters(char *s) { // convert the string "\n" into the \r\n combination required by DICOM if (s) for (; *s; ++s) if (*s == '\\' && *(s+1) == 'n') { *s = '\r'; *++s = '\n'; } } static OFBool parseValue(char *&s, char *&value, DcmEVR &vr, const DcmTagKey &tagkey) { OFBool ok = OFTrue; size_t len; value = NULL; s = stripPrecedingWhitespace(s); switch (*s) { case DCM_DumpOpenString: len = searchLastClose(s, DCM_DumpCloseString); if (len == 0) ok = OFFalse; else if (len > 2) { value = new char[len - 1]; OFStandard::strlcpy(value, s + 1, len - 1); convertNewlineCharacters(value); } else value = NULL; break; case DCM_DumpOpenDescription: /* need to distinguish VR=AT from description field */ /* NB: if the VR is unknown this workaround will not succeed */ if (vr == EVR_AT) { len = searchLastClose(s, DCM_DumpTagDelim); if (len >= 11) // (gggg,eeee) allow non-significant spaces { char *pv = s; DcmTagKey tag; if (parseTag(pv, tag)) // check for valid tag format { value = new char[len + 1]; OFStandard::strlcpy(value, s, len + 1); stripWhitespace(value); } else ok = OFFalse; // skip description } else ok = OFFalse; // skip description } /* need to distinguish pixel sequence */ else if ((tagkey == DCM_PixelData) && (vr == EVR_OB)) { if (strncmp(s + 1, "PixelSequence", 13) == 0) vr = EVR_pixelSQ; } break; case DCM_DumpOpenFile: ok = OFFalse; // currently not supported break; case DCM_DumpCommentChar: break; default: len = searchCommentOrEol(s); if (len) { value = new char[len + 1]; OFStandard::strlcpy(value, s, len + 1); stripTrailingWhitespace(value); } break; } return ok; } static OFCondition putFileContentsIntoElement(DcmElement *elem, const char *filename) { OFCondition ec = EC_Normal; #ifdef EXPERIMENTAL_READ_FROM_FILE /* create stream object for binary file */ DcmInputFileStream fileStream(filename); ec = fileStream.status(); if (ec.good()) { /* NB: if size is odd file will be rejected */ const size_t fileLen = OFStandard::getFileSize(filename); /* read element value from binary file (requires even length) */ ec = elem->createValueFromTempFile(fileStream.newFactory(), fileLen, EBO_LittleEndian); if (ec.bad()) OFLOG_ERROR(dump2dcmLogger, "cannot process binary data file: " << filename); } else { OFLOG_ERROR(dump2dcmLogger, "cannot open binary data file: " << filename); ec = EC_InvalidTag; } #else FILE *f = NULL; if ((f = fopen(filename, "rb")) == NULL) { OFLOG_ERROR(dump2dcmLogger, "cannot open binary data file: " << filename); return EC_InvalidTag; } const size_t len = OFStandard::getFileSize(filename); size_t buflen = len; if (buflen & 1) buflen++; /* if odd then make even (DICOM requires even length values) */ Uint8 *buf = NULL; const DcmEVR evr = elem->getVR(); /* create buffer of OB or OW data */ if (evr == EVR_OB || evr == EVR_pixelItem) ec = elem->createUint8Array(OFstatic_cast(Uint32, buflen), buf); else if (evr == EVR_OW) { Uint16 *buf16 = NULL; ec = elem->createUint16Array(OFstatic_cast(Uint32, buflen / 2), buf16); buf = OFreinterpret_cast(Uint8 *, buf16); } else ec = EC_IllegalCall; if (ec.good()) { OFLOG_INFO(dump2dcmLogger, "reading " << len << " bytes from binary data file: " << filename); OFLOG_DEBUG(dump2dcmLogger, " and storing it in the element " << elem->getTag()); /* read binary file into the buffer */ if (fread(buf, 1, len, f) != len) { OFLOG_ERROR(dump2dcmLogger, "error reading binary data file: " << filename << ": " << OFStandard::getLastSystemErrorCode().message()); ec = EC_CorruptedData; } else if (evr == EVR_OW) { /* swap 16 bit OW data (if necessary) */ swapIfNecessary(gLocalByteOrder, opt_fileContentsByteOrdering, buf, OFstatic_cast(Uint32, buflen), sizeof(Uint16)); } } else if (ec == EC_MemoryExhausted) OFLOG_ERROR(dump2dcmLogger, "out of memory reading binary data file: " << filename); else OFLOG_ERROR(dump2dcmLogger, "illegal call processing binary data file: " << filename); fclose(f); #endif return ec; } static OFCondition insertIntoSet(DcmStack &stack, const E_TransferSyntax xfer, const DcmTagKey &tagkey, const DcmEVR &vr, const char *value) { // insert new element into dataset or metaheader OFCondition l_error = EC_Normal; OFCondition newElementError = EC_Normal; if (stack.empty()) l_error = EC_CorruptedData; if (l_error == EC_Normal) { DcmElement *newElement = NULL; DcmObject *topOfStack = stack.top(); // convert tagkey to tag including VR DcmTag tag(tagkey); DcmVR dcmvr(vr); const DcmEVR tagvr = tag.getEVR(); /* check VR and consider various special cases */ if (tagvr != vr && vr != EVR_UNKNOWN && tagvr != EVR_UNKNOWN && (tagkey != DCM_LUTData || (vr != EVR_US && vr != EVR_SS && vr != EVR_OW)) && (tagkey != DCM_PixelData || (vr != EVR_OB && vr != EVR_OW && vr != EVR_pixelSQ)) && (tagvr != EVR_xs || (vr != EVR_US && vr != EVR_SS)) && ((tagvr != EVR_ox && tagvr != EVR_px) || (vr != EVR_OB && vr != EVR_OW)) && (tagvr != EVR_na || vr != EVR_pixelItem)) { OFLOG_WARN(dump2dcmLogger, "Tag " << tag << " with wrong VR '" << dcmvr.getVRName() << "' found, correct is '" << tag.getVR().getVRName() << "'"); } else if ((tagvr == EVR_UNKNOWN) && (vr == EVR_UNKNOWN)) { OFLOG_WARN(dump2dcmLogger, "VR of Tag " << tag << " is unknown, " << "using 'UN' for this data element"); } if (vr != EVR_UNKNOWN) tag.setVR(dcmvr); const DcmEVR newTagVR = tag.getEVR(); // create new element (special handling for pixel sequence and item) if (newTagVR == EVR_pixelSQ) newElement = new DcmPixelData(tag); else if (newTagVR == EVR_pixelItem) newElement = new DcmPixelItem(DCM_PixelItemTag); else newElementError = DcmItem::newDicomElementWithVR(newElement, tag); if (newElementError == EC_Normal) { // tag describes an element if (!newElement) { // Tag was ambiguous - should never happen according to the current implementation of newDicomElement() l_error = EC_InvalidVR; } else { // check for uncompressed pixel data (i.e. no pixel sequence present) if (tagkey == DCM_PixelData && (newTagVR == EVR_OB || newTagVR == EVR_OW)) OFstatic_cast(DcmPixelData *, newElement)->setNonEncapsulationFlag(OFTrue /*alwaysUnencapsulated*/); // fill value if (value) { if (value[0] == '=' && (newTagVR == EVR_OB || newTagVR == EVR_OW || newTagVR == EVR_pixelItem)) { /* * Special case handling for OB, OW and pixel item data. * Allow a value beginning with a '=' character to represent * a file containing data to be used as the attribute value. * A '=' character is not a normal value since OB and OW values * must be written as multivalued hexadecimal (e.g. "00\ff\0d\8f"); */ l_error = putFileContentsIntoElement(newElement, value + 1); } else { l_error = newElement->putString(value); } } // insert element into hierarchy if (l_error == EC_Normal) { switch(topOfStack->ident()) { case EVR_item: case EVR_dirRecord: case EVR_dataset: case EVR_metainfo: { DcmItem *item = OFstatic_cast(DcmItem *, topOfStack); item->insert(newElement); // special handling for pixel sequence if (newTagVR == EVR_pixelSQ) { DcmPixelSequence *pixelSeq = new DcmPixelSequence(DCM_PixelSequenceTag); if (pixelSeq != NULL) { OFstatic_cast(DcmPixelData *, newElement)->putOriginalRepresentation(xfer, NULL, pixelSeq); stack.push(pixelSeq); } } else if (newElement->ident() == EVR_SQ) stack.push(newElement); } break; case EVR_pixelSQ: if (newTagVR == EVR_pixelItem) { DcmPixelSequence *pixelSeq = OFstatic_cast(DcmPixelSequence *, topOfStack); pixelSeq->insert(OFstatic_cast(DcmPixelItem *, newElement)); } else l_error = EC_InvalidTag; break; default: l_error = EC_InvalidTag; break; } } } } else if (newElementError == EC_SequEnd) { // pop stack if stack object was a sequence if (topOfStack->ident() == EVR_SQ || topOfStack->ident() == EVR_pixelSQ) stack.pop(); else l_error = EC_InvalidTag; } else if (newElementError == EC_ItemEnd) { // pop stack if stack object was an item switch (topOfStack->ident()) { case EVR_item: case EVR_dirRecord: case EVR_dataset: case EVR_metainfo: stack.pop(); break; default: l_error = EC_InvalidTag; break; } } else if (newElementError == EC_InvalidTag) { if (tag.getXTag() == DCM_Item) { DcmItem *item = NULL; if (topOfStack->getTag().getXTag() == DCM_DirectoryRecordSequence) { // an Item must be pushed to the stack item = new DcmDirectoryRecord(tag, 0); OFstatic_cast(DcmSequenceOfItems *, topOfStack)->insert(item); stack.push(item); } else if (topOfStack->ident() == EVR_SQ) { // an item must be pushed to the stack item = new DcmItem(tag); OFstatic_cast(DcmSequenceOfItems *, topOfStack)->insert(item); stack.push(item); } else l_error = EC_InvalidTag; } else l_error = EC_InvalidTag; } else l_error = EC_InvalidTag; } return l_error; } static OFBool readDumpFile(DcmMetaInfo *metaheader, DcmDataset *dataset, FILE *infile, const char *ifname, E_TransferSyntax &xfer, const OFBool stopOnErrors, const unsigned long maxLineLength) { char *lineBuf = new char[maxLineLength]; int lineNumber = 0; OFBool errorOnThisLine = OFFalse; char *parse = NULL; char *value = NULL; DcmEVR vr = EVR_UNKNOWN; int errorsEncountered = 0; DcmTagKey tagkey; DcmStack metaheaderStack; DcmStack datasetStack; xfer = EXS_Unknown; if (metaheader) metaheaderStack.push(metaheader); datasetStack.push(dataset); while (getLine(lineBuf, OFstatic_cast(int, maxLineLength), infile, lineNumber + 1)) { lineNumber++; OFLOG_TRACE(dump2dcmLogger, "processing line " << lineNumber << " of the input file"); // ignore empty lines and comment lines if (onlyWhitespace(lineBuf)) continue; if (isaCommentLine(lineBuf)) continue; errorOnThisLine = OFFalse; parse = &lineBuf[0]; // parse tag an the line if (!parseTag(parse, tagkey)) { OFLOG_ERROR(dump2dcmLogger, OFFIS_CONSOLE_APPLICATION ": "<< ifname << ": " << "no Tag found (line " << lineNumber << ")"); errorOnThisLine = OFTrue; } // parse optional VR if (!errorOnThisLine && !parseVR(parse, vr)) vr = EVR_UNKNOWN; // parse optional value if (!errorOnThisLine && !parseValue(parse, value, vr, tagkey)) { OFLOG_ERROR(dump2dcmLogger, OFFIS_CONSOLE_APPLICATION ": "<< ifname << ": " << "incorrect value specification (line " << lineNumber << ")"); errorOnThisLine = OFTrue; } // insert new element that consists of tag, VR, and value if (!errorOnThisLine) { OFCondition l_error = EC_Normal; if (tagkey.getGroup() == 0x0002) { if (metaheader) { l_error = insertIntoSet(metaheaderStack, xfer, tagkey, vr, value); // check for transfer syntax in meta-header if ((tagkey == DCM_TransferSyntaxUID) && (xfer == EXS_Unknown)) { const char *xferUID; // use resolved value (UID) if (metaheader->findAndGetString(DCM_TransferSyntaxUID, xferUID).good()) xfer = DcmXfer(xferUID).getXfer(); } } } else l_error = insertIntoSet(datasetStack, xfer, tagkey, vr, value); if (value) { delete[] value; value = NULL; } if (l_error != EC_Normal) { errorOnThisLine = OFTrue; OFLOG_ERROR(dump2dcmLogger, OFFIS_CONSOLE_APPLICATION ": " << ifname << ": Error in creating Element: " << l_error.text() << " (line " << lineNumber << ")"); } } if (errorOnThisLine) errorsEncountered++; } // test blocking structure if (metaheader && metaheaderStack.card() != 1) { OFLOG_ERROR(dump2dcmLogger, OFFIS_CONSOLE_APPLICATION ": " << ifname << ": Block Error in metaheader"); errorsEncountered++; } if (datasetStack.card() != 1) { OFLOG_ERROR(dump2dcmLogger, OFFIS_CONSOLE_APPLICATION ": " << ifname << ": Block Error in dataset"); errorsEncountered++; } delete[] lineBuf; if (errorsEncountered) { OFLOG_ERROR(dump2dcmLogger, errorsEncountered << " Errors found in " << ifname); return !stopOnErrors; } else return OFTrue; } // ******************************************** int main(int argc, char *argv[]) { OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "Convert ASCII dump to DICOM file", rcsid); OFCommandLine cmd; cmd.setOptionColumns(LONGCOL, SHORTCOL); cmd.setParamColumn(LONGCOL + SHORTCOL + 4); cmd.addParam("dumpfile-in", "dump input filename"); cmd.addParam("dcmfile-out", "DICOM output filename"); cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2); cmd.addOption("--help", "-h", "print this help text and exit", OFCommandLine::AF_Exclusive); cmd.addOption("--version", "print version information and exit", OFCommandLine::AF_Exclusive); OFLog::addOptions(cmd); cmd.addGroup("input options:", LONGCOL, SHORTCOL + 2); cmd.addSubGroup("input file format:"); cmd.addOption("--read-meta-info", "+f", "read meta information if present (default)"); cmd.addOption("--ignore-meta-info", "-f", "ignore file meta information"); cmd.addSubGroup("file contents byte ordering:"); cmd.addOption("--read-file-little", "+rl", "read OW data with little endian (default)"); cmd.addOption("--read-file-big", "+rb", "read OW data from file with big endian"); cmd.addSubGroup("other input options:"); cmd.addOption("--line", "+l", 1, "[m]ax-length: integer", "maximum line length m (default: 4096)"); cmd.addGroup("processing options:"); cmd.addSubGroup("unique identifiers:"); cmd.addOption("--generate-new-uids", "+Ug", "generate new Study/Series/SOP Instance UID"); cmd.addOption("--dont-overwrite-uids", "-Uo", "do not overwrite existing UIDs (default)"); cmd.addOption("--overwrite-uids", "+Uo", "overwrite existing UIDs"); cmd.addGroup("output options:"); cmd.addSubGroup("output file format:"); cmd.addOption("--write-file", "+F", "write file format (default)"); cmd.addOption("--write-dataset", "-F", "write data set without file meta information"); cmd.addOption("--update-meta-info", "+Fu", "update particular file meta information"); cmd.addSubGroup("output transfer syntax:"); cmd.addOption("--write-xfer-same", "+t=", "write with same TS as input (default)"); cmd.addOption("--write-xfer-little", "+te", "write with explicit VR little endian"); cmd.addOption("--write-xfer-big", "+tb", "write with explicit VR big endian TS"); cmd.addOption("--write-xfer-implicit", "+ti", "write with implicit VR little endian TS"); #ifdef WITH_ZLIB cmd.addOption("--write-xfer-deflated", "+td", "write with deflated expl. VR little endian TS"); #endif cmd.addSubGroup("error handling:"); cmd.addOption("--stop-on-error", "-E", "do not write if dump is damaged (default)"); cmd.addOption("--ignore-errors", "+E", "attempt to write even if dump is damaged"); cmd.addSubGroup("post-1993 value representations:"); cmd.addOption("--enable-new-vr", "+u", "enable support for new VRs (UN/UT) (default)"); cmd.addOption("--disable-new-vr", "-u", "disable support for new VRs, convert to OB"); cmd.addSubGroup("group length encoding:"); cmd.addOption("--group-length-recalc", "+g=", "recalculate group lengths if present (default)"); cmd.addOption("--group-length-create", "+g", "always write with group length elements"); cmd.addOption("--group-length-remove", "-g", "always write without group length elements"); cmd.addSubGroup("length encoding in sequences and items:"); cmd.addOption("--length-explicit", "+e", "write with explicit lengths (default)"); cmd.addOption("--length-undefined", "-e", "write with undefined lengths"); cmd.addSubGroup("data set trailing padding (not with --write-dataset):"); cmd.addOption("--padding-retain", "-p=", "do not change padding (default)"); cmd.addOption("--padding-off", "-p", "no padding (implicit if --write-dataset)"); cmd.addOption("--padding-create", "+p", 2, "[f]ile-pad [i]tem-pad: integer", "align file on multiple of f bytes\nand items on multiple of i bytes"); #ifdef WITH_ZLIB cmd.addSubGroup("deflate compression level (only with --write-xfer-deflated):"); cmd.addOption("--compression-level", "+cl", 1, "[l]evel: integer (default: 6)", "0=uncompressed, 1=fastest, 9=best compression"); #endif const char *opt_ifname = NULL; const char *opt_ofname = NULL; E_TransferSyntax opt_xfer = EXS_Unknown; E_EncodingType opt_enctype = EET_ExplicitLength; E_GrpLenEncoding opt_glenc = EGL_recalcGL; E_PaddingEncoding opt_padenc = EPD_withoutPadding; E_FileWriteMode opt_writeMode = EWM_fileformat; OFCmdUnsignedInt opt_filepad = 0; OFCmdUnsignedInt opt_itempad = 0; OFCmdUnsignedInt opt_linelength = DCM_DumpMaxLineSize; OFBool opt_stopOnErrors = OFTrue; OFBool opt_metaInfo = OFTrue; OFBool opt_generateUIDs = OFFalse; OFBool opt_overwriteUIDs = OFFalse; /* evaluate command line */ prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION); if (app.parseCommandLine(cmd, argc, argv)) { /* check exclusive options first */ if (cmd.hasExclusiveOption()) { if (cmd.findOption("--version")) { app.printHeader(OFTrue /*print host identifier*/); COUT << OFendl << "External libraries used:"; #ifdef WITH_ZLIB COUT << OFendl << "- ZLIB, Version " << zlibVersion() << OFendl; #else COUT << " none" << OFendl; #endif return 0; } } /* command line parameters */ cmd.getParam(1, opt_ifname); cmd.getParam(2, opt_ofname); OFLog::configureFromCommandLine(cmd, app); /* input options */ cmd.beginOptionBlock(); if (cmd.findOption("--read-meta-info")) opt_metaInfo = OFTrue; if (cmd.findOption("--ignore-meta-info")) opt_metaInfo = OFFalse; cmd.endOptionBlock(); cmd.beginOptionBlock(); if (cmd.findOption("--read-file-little")) opt_fileContentsByteOrdering = EBO_LittleEndian; if (cmd.findOption("--read-file-big")) opt_fileContentsByteOrdering = EBO_BigEndian; cmd.endOptionBlock(); if (cmd.findOption("--line")) app.checkValue(cmd.getValueAndCheckMin(opt_linelength, 80)); /* processing options */ if (cmd.findOption("--generate-new-uids")) opt_generateUIDs = OFTrue; cmd.beginOptionBlock(); if (cmd.findOption("--dont-overwrite-uids")) opt_overwriteUIDs = OFFalse; if (cmd.findOption("--overwrite-uids")) opt_overwriteUIDs = OFTrue; cmd.endOptionBlock(); /* output options */ cmd.beginOptionBlock(); if (cmd.findOption("--write-file")) opt_writeMode = EWM_fileformat; if (cmd.findOption("--write-dataset")) opt_writeMode = EWM_dataset; cmd.endOptionBlock(); if (cmd.findOption("--update-meta-info")) { app.checkConflict("--update-meta-info", "--write-dataset", opt_writeMode == EWM_dataset); opt_writeMode = EWM_updateMeta; } cmd.beginOptionBlock(); if (cmd.findOption("--write-xfer-same")) opt_xfer = EXS_Unknown;; if (cmd.findOption("--write-xfer-little")) opt_xfer = EXS_LittleEndianExplicit; if (cmd.findOption("--write-xfer-big")) opt_xfer = EXS_BigEndianExplicit; if (cmd.findOption("--write-xfer-implicit")) opt_xfer = EXS_LittleEndianImplicit; #ifdef WITH_ZLIB if (cmd.findOption("--write-xfer-deflated")) opt_xfer = EXS_DeflatedLittleEndianExplicit; #endif cmd.endOptionBlock(); cmd.beginOptionBlock(); if (cmd.findOption("--stop-on-error")) opt_stopOnErrors = OFTrue; if (cmd.findOption("--ignore-errors")) opt_stopOnErrors = OFFalse; cmd.endOptionBlock(); cmd.beginOptionBlock(); if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs(); if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs(); cmd.endOptionBlock(); cmd.beginOptionBlock(); if (cmd.findOption("--group-length-recalc")) opt_glenc = EGL_recalcGL; if (cmd.findOption("--group-length-create")) opt_glenc = EGL_withGL; if (cmd.findOption("--group-length-remove")) opt_glenc = EGL_withoutGL; cmd.endOptionBlock(); cmd.beginOptionBlock(); if (cmd.findOption("--length-explicit")) opt_enctype = EET_ExplicitLength; if (cmd.findOption("--length-undefined")) opt_enctype = EET_UndefinedLength; cmd.endOptionBlock(); cmd.beginOptionBlock(); if (cmd.findOption("--padding-retain")) { app.checkConflict("--padding-retain", "--write-dataset", opt_writeMode == EWM_dataset); opt_padenc = EPD_noChange; } if (cmd.findOption("--padding-off")) opt_padenc = EPD_withoutPadding; if (cmd.findOption("--padding-create")) { app.checkConflict("--padding-create", "--write-dataset", opt_writeMode == EWM_dataset); app.checkValue(cmd.getValueAndCheckMin(opt_filepad, 0)); app.checkValue(cmd.getValueAndCheckMin(opt_itempad, 0)); opt_padenc = EPD_withPadding; } cmd.endOptionBlock(); #ifdef WITH_ZLIB if (cmd.findOption("--compression-level")) { OFCmdUnsignedInt comprLevel = 0; app.checkDependence("--compression-level", "--write-xfer-deflated", opt_xfer == EXS_DeflatedLittleEndianExplicit); app.checkValue(cmd.getValueAndCheckMinMax(comprLevel, 0, 9)); dcmZlibCompressionLevel.set(OFstatic_cast(int, comprLevel)); } #endif } /* print resource identifier */ OFLOG_DEBUG(dump2dcmLogger, rcsid << OFendl); DcmFileFormat fileformat; DcmMetaInfo *metaheader = (opt_metaInfo) ? fileformat.getMetaInfo() : NULL; DcmDataset *dataset = fileformat.getDataset(); /* make sure data dictionary is loaded */ if (!dcmDataDict.isDictionaryLoaded()) { OFLOG_WARN(dump2dcmLogger, "no data dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE); } // open input dump file if ((opt_ifname == NULL) || (strlen(opt_ifname) == 0)) { OFLOG_FATAL(dump2dcmLogger, "invalid input filename: "); return 1; } OFLOG_INFO(dump2dcmLogger, "reading dump file: " << opt_ifname); FILE *dumpfile = fopen(opt_ifname, "r"); if (!dumpfile) { OFLOG_FATAL(dump2dcmLogger, "input file does not exist: " << opt_ifname); return 1; } int status = 0; E_TransferSyntax xfer; // read dump file into metaheader and dataset if (readDumpFile(metaheader, dataset, dumpfile, opt_ifname, xfer, opt_stopOnErrors, OFstatic_cast(unsigned long, opt_linelength))) { /* generate new UIDs (if required) */ if (opt_generateUIDs) { char uid[100]; if (opt_overwriteUIDs || !dataset->tagExistsWithValue(DCM_StudyInstanceUID)) { OFLOG_INFO(dump2dcmLogger, "generating new Study Instance UID"); dataset->putAndInsertString(DCM_StudyInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT)); } if (opt_overwriteUIDs || !dataset->tagExistsWithValue(DCM_SeriesInstanceUID)) { OFLOG_INFO(dump2dcmLogger, "generating new Series Instance UID"); dataset->putAndInsertString(DCM_SeriesInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT)); } if (opt_overwriteUIDs || !dataset->tagExistsWithValue(DCM_SOPInstanceUID)) { OFLOG_INFO(dump2dcmLogger, "generating new SOP Instance UID"); dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT)); /* make sure that the file meta information is updated correspondingly */ if (opt_writeMode == EWM_fileformat) opt_writeMode = EWM_updateMeta; } } // write into file format or dataset OFLOG_INFO(dump2dcmLogger, "writing DICOM file"); /* determine transfer syntax to write the file */ if (opt_xfer == EXS_Unknown) { opt_xfer = xfer; /* check whether output xfer is still unknown */ if (opt_xfer == EXS_Unknown) { OFLOG_WARN(dump2dcmLogger, "output transfer syntax unknown, assuming --write-xfer-little"); opt_xfer = EXS_LittleEndianExplicit; } } /* check whether it is possible to write the file */ if (fileformat.canWriteXfer(opt_xfer)) { /* check whether pixel data is compressed */ if ((opt_writeMode == EWM_dataset) && DcmXfer(xfer).isEncapsulated()) { OFLOG_WARN(dump2dcmLogger, "encapsulated pixel data require file format, ignoring --write-dataset"); opt_writeMode = EWM_fileformat; } OFCondition l_error = fileformat.saveFile(opt_ofname, opt_xfer, opt_enctype, opt_glenc, opt_padenc, OFstatic_cast(Uint32, opt_filepad), OFstatic_cast(Uint32, opt_itempad), opt_writeMode); if (l_error == EC_Normal) { OFLOG_INFO(dump2dcmLogger, "dump successfully converted"); } else { OFLOG_ERROR(dump2dcmLogger, l_error.text() << ": writing file: " << opt_ofname); status = 1; } } else { OFLOG_ERROR(dump2dcmLogger, "no conversion to transfer syntax " << DcmXfer(opt_xfer).getXferName() << " possible!"); status = 2; } } fclose(dumpfile); return status; }