1 /*
2  *
3  *  Copyright (C) 2002-2017, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmdata
15  *
16  *  Author:  Marco Eichelberg
17  *
18  *  Purpose: Decompress RLE-compressed DICOM file
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
23 
24 #define INCLUDE_CSTDLIB
25 #define INCLUDE_CSTDIO
26 #define INCLUDE_CSTRING
27 #include "dcmtk/ofstd/ofstdinc.h"
28 
29 #include "dcmtk/dcmdata/dctk.h"
30 #include "dcmtk/dcmdata/cmdlnarg.h"
31 #include "dcmtk/ofstd/ofconapp.h"
32 #include "dcmtk/dcmdata/dcuid.h"     /* for dcmtk version name */
33 #include "dcmtk/dcmdata/dcrledrg.h"  /* for DcmRLEDecoderRegistration */
34 
35 #ifdef WITH_ZLIB
36 #include <zlib.h>      /* for zlibVersion() */
37 #endif
38 
39 #define OFFIS_CONSOLE_APPLICATION "dcmdrle"
40 
41 static OFLogger dcmdrleLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
42 
43 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
44   OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
45 
46 // ********************************************
47 
48 
49 #define SHORTCOL 3
50 #define LONGCOL 21
51 
main(int argc,char * argv[])52 int main(int argc, char *argv[])
53 {
54 
55   const char *opt_ifname = NULL;
56   const char *opt_ofname = NULL;
57 
58   E_TransferSyntax opt_oxfer = EXS_LittleEndianExplicit;
59   E_GrpLenEncoding opt_oglenc = EGL_recalcGL;
60   E_EncodingType opt_oenctype = EET_ExplicitLength;
61   E_PaddingEncoding opt_opadenc = EPD_noChange;
62   OFCmdUnsignedInt opt_filepad = 0;
63   OFCmdUnsignedInt opt_itempad = 0;
64   E_FileReadMode opt_readMode = ERM_autoDetect;
65   E_FileWriteMode opt_writeMode = EWM_fileformat;
66   E_TransferSyntax opt_ixfer = EXS_Unknown;
67 
68   // RLE parameters
69   OFBool opt_uidcreation = OFFalse;
70   OFBool opt_reversebyteorder = OFFalse;
71 
72   OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION, "Decode RLE-compressed DICOM file", rcsid);
73   OFCommandLine cmd;
74   cmd.setOptionColumns(LONGCOL, SHORTCOL);
75   cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
76 
77   cmd.addParam("dcmfile-in",  "DICOM input filename to be converted");
78   cmd.addParam("dcmfile-out", "DICOM output filename");
79 
80   cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
81     cmd.addOption("--help",                  "-h",     "print this help text and exit", OFCommandLine::AF_Exclusive);
82     cmd.addOption("--version",                         "print version information and exit", OFCommandLine::AF_Exclusive);
83     OFLog::addOptions(cmd);
84 
85   cmd.addGroup("input options:");
86     cmd.addSubGroup("input file format:");
87       cmd.addOption("--read-file",           "+f",     "read file format or data set (default)");
88       cmd.addOption("--read-file-only",      "+fo",    "read file format only");
89       cmd.addOption("--read-dataset",        "-f",     "read data set without file meta information");
90 
91   cmd.addGroup("processing options:");
92     cmd.addSubGroup("SOP Instance UID:");
93       cmd.addOption("--uid-default",         "+ud",    "keep same SOP Instance UID (default)");
94       cmd.addOption("--uid-always",          "+ua",    "always assign new UID");
95     cmd.addSubGroup("RLE byte segment order:");
96       cmd.addOption("--byte-order-default",  "+bd",    "most significant byte first (default)");
97       cmd.addOption("--byte-order-reverse",  "+br",    "least significant byte first");
98 
99   cmd.addGroup("output options:");
100     cmd.addSubGroup("output file format:");
101       cmd.addOption("--write-file",          "+F",     "write file format (default)");
102       cmd.addOption("--write-dataset",       "-F",     "write data set without file meta information");
103     cmd.addSubGroup("output transfer syntax:");
104       cmd.addOption("--write-xfer-little",   "+te",    "write with explicit VR little endian (default)");
105       cmd.addOption("--write-xfer-big",      "+tb",    "write with explicit VR big endian TS");
106       cmd.addOption("--write-xfer-implicit", "+ti",    "write with implicit VR little endian TS");
107     cmd.addSubGroup("post-1993 value representations:");
108       cmd.addOption("--enable-new-vr",       "+u",     "enable support for new VRs (UN/UT) (default)");
109       cmd.addOption("--disable-new-vr",      "-u",     "disable support for new VRs, convert to OB");
110     cmd.addSubGroup("group length encoding:");
111       cmd.addOption("--group-length-recalc", "+g=",    "recalculate group lengths if present (default)");
112       cmd.addOption("--group-length-create", "+g",     "always write with group length elements");
113       cmd.addOption("--group-length-remove", "-g",     "always write without group length elements");
114     cmd.addSubGroup("length encoding in sequences and items:");
115       cmd.addOption("--length-explicit",     "+e",     "write with explicit lengths (default)");
116       cmd.addOption("--length-undefined",    "-e",     "write with undefined lengths");
117     cmd.addSubGroup("data set trailing padding (not with --write-dataset):");
118       cmd.addOption("--padding-retain",      "-p=",    "do not change padding\n(default if not --write-dataset)");
119       cmd.addOption("--padding-off",         "-p",     "no padding (implicit if --write-dataset)");
120       cmd.addOption("--padding-create",      "+p",  2, "[f]ile-pad [i]tem-pad: integer",
121                                                        "align file on multiple of f bytes\nand items on multiple of i bytes");
122 
123     /* evaluate command line */
124     prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
125     if (app.parseCommandLine(cmd, argc, argv))
126     {
127       /* check exclusive options first */
128       if (cmd.hasExclusiveOption())
129       {
130           if (cmd.findOption("--version"))
131           {
132               app.printHeader(OFTrue /*print host identifier*/);
133               COUT << OFendl << "External libraries used:";
134 #ifdef WITH_ZLIB
135               COUT << OFendl << "- ZLIB, Version " << zlibVersion() << OFendl;
136 #else
137               COUT << " none" << OFendl;
138 #endif
139               return 0;
140           }
141       }
142 
143       /* command line parameters */
144 
145       cmd.getParam(1, opt_ifname);
146       cmd.getParam(2, opt_ofname);
147 
148       OFLog::configureFromCommandLine(cmd, app);
149 
150       cmd.beginOptionBlock();
151       if (cmd.findOption("--uid-default")) opt_uidcreation = OFFalse;
152       if (cmd.findOption("--uid-always")) opt_uidcreation = OFTrue;
153       cmd.endOptionBlock();
154 
155       cmd.beginOptionBlock();
156       if (cmd.findOption("--byte-order-default")) opt_reversebyteorder = OFFalse;
157       if (cmd.findOption("--byte-order-reverse")) opt_reversebyteorder = OFTrue;
158       cmd.endOptionBlock();
159 
160       cmd.beginOptionBlock();
161       if (cmd.findOption("--read-file"))
162       {
163         opt_readMode = ERM_autoDetect;
164         opt_ixfer = EXS_Unknown;
165       }
166       if (cmd.findOption("--read-file-only"))
167       {
168         opt_readMode = ERM_fileOnly;
169         opt_ixfer = EXS_Unknown;
170       }
171       if (cmd.findOption("--read-dataset"))
172       {
173         opt_readMode = ERM_dataset;
174         opt_ixfer = EXS_RLELossless;
175       }
176       cmd.endOptionBlock();
177 
178       cmd.beginOptionBlock();
179       if (cmd.findOption("--write-file")) opt_writeMode = EWM_fileformat;
180       if (cmd.findOption("--write-dataset")) opt_writeMode = EWM_dataset;
181       cmd.endOptionBlock();
182 
183       cmd.beginOptionBlock();
184       if (cmd.findOption("--write-xfer-little")) opt_oxfer = EXS_LittleEndianExplicit;
185       if (cmd.findOption("--write-xfer-big")) opt_oxfer = EXS_BigEndianExplicit;
186       if (cmd.findOption("--write-xfer-implicit")) opt_oxfer = EXS_LittleEndianImplicit;
187       cmd.endOptionBlock();
188 
189       cmd.beginOptionBlock();
190       if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
191       if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
192       cmd.endOptionBlock();
193 
194       cmd.beginOptionBlock();
195       if (cmd.findOption("--group-length-recalc")) opt_oglenc = EGL_recalcGL;
196       if (cmd.findOption("--group-length-create")) opt_oglenc = EGL_withGL;
197       if (cmd.findOption("--group-length-remove")) opt_oglenc = EGL_withoutGL;
198       cmd.endOptionBlock();
199 
200       cmd.beginOptionBlock();
201       if (cmd.findOption("--length-explicit")) opt_oenctype = EET_ExplicitLength;
202       if (cmd.findOption("--length-undefined")) opt_oenctype = EET_UndefinedLength;
203       cmd.endOptionBlock();
204 
205       cmd.beginOptionBlock();
206       if (cmd.findOption("--padding-retain"))
207       {
208         app.checkConflict("--padding-retain", "--write-dataset", opt_writeMode == EWM_dataset);
209         opt_opadenc = EPD_noChange;
210       }
211       if (cmd.findOption("--padding-off")) opt_opadenc = EPD_withoutPadding;
212       if (cmd.findOption("--padding-create"))
213       {
214         app.checkConflict("--padding-create", "--write-dataset", opt_writeMode == EWM_dataset);
215         app.checkValue(cmd.getValueAndCheckMin(opt_filepad, 0));
216         app.checkValue(cmd.getValueAndCheckMin(opt_itempad, 0));
217         opt_opadenc = EPD_withPadding;
218       }
219       cmd.endOptionBlock();
220     }
221 
222     /* print resource identifier */
223     OFLOG_DEBUG(dcmdrleLogger, rcsid << OFendl);
224 
225     // register global decompression codecs
226     DcmRLEDecoderRegistration::registerCodecs(opt_uidcreation, opt_reversebyteorder);
227 
228     /* make sure data dictionary is loaded */
229     if (!dcmDataDict.isDictionaryLoaded())
230     {
231         OFLOG_WARN(dcmdrleLogger, "no data dictionary loaded, check environment variable: "
232             << DCM_DICT_ENVIRONMENT_VARIABLE);
233     }
234 
235     // open inputfile
236     if ((opt_ifname == NULL) || (strlen(opt_ifname) == 0))
237     {
238         OFLOG_FATAL(dcmdrleLogger, "invalid filename: <empty string>");
239         return 1;
240     }
241 
242     DcmFileFormat fileformat;
243     DcmDataset * dataset = fileformat.getDataset();
244 
245     OFLOG_INFO(dcmdrleLogger, "open input file " << opt_ifname);
246 
247     OFCondition error = fileformat.loadFile(opt_ifname, opt_ixfer, EGL_noChange, DCM_MaxReadLength, opt_readMode);
248 
249     if (error.bad())
250     {
251         OFLOG_FATAL(dcmdrleLogger, error.text() << ": reading file: " <<  opt_ifname);
252         return 1;
253     }
254 
255     OFLOG_INFO(dcmdrleLogger, "decompressing file");
256 
257     DcmXfer opt_oxferSyn(opt_oxfer);
258     DcmXfer original_xfer(dataset->getOriginalXfer());
259 
260     error = dataset->chooseRepresentation(opt_oxfer, NULL);
261     if (error.bad())
262     {
263         OFLOG_FATAL(dcmdrleLogger, error.text() << ": decompressing file: " <<  opt_ifname);
264         if (error == EC_CannotChangeRepresentation)
265             OFLOG_FATAL(dcmdrleLogger, "input transfer syntax " << original_xfer.getXferName() << " not supported");
266         return 1;
267     }
268 
269     if (! dataset->canWriteXfer(opt_oxfer))
270     {
271         OFLOG_FATAL(dcmdrleLogger, "no conversion to transfer syntax " << opt_oxferSyn.getXferName() << " possible");
272         return 1;
273     }
274 
275     OFLOG_INFO(dcmdrleLogger, "create output file " << opt_ofname);
276 
277     // update file meta information with new SOP Instance UID
278     if (opt_uidcreation && (opt_writeMode == EWM_fileformat))
279         opt_writeMode = EWM_updateMeta;
280 
281     fileformat.loadAllDataIntoMemory();
282     error = fileformat.saveFile(opt_ofname, opt_oxfer, opt_oenctype, opt_oglenc, opt_opadenc,
283         OFstatic_cast(Uint32, opt_filepad), OFstatic_cast(Uint32, opt_itempad), opt_writeMode);
284 
285     if (error.bad())
286     {
287         OFLOG_FATAL(dcmdrleLogger, error.text() << ": writing file: " <<  opt_ofname);
288         return 1;
289     }
290 
291     OFLOG_INFO(dcmdrleLogger, "conversion successful");
292 
293     // deregister RLE codec
294     DcmRLEDecoderRegistration::cleanup();
295 
296     return 0;
297 }
298