1 /*
2  *
3  *  Copyright (C) 2002-2018, 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: Compress DICOM file with RLE Transfer Syntax
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/dcrleerg.h"  /* for DcmRLEEncoderRegistration */
34 
35 #ifdef WITH_ZLIB
36 #include <zlib.h>      /* for zlibVersion() */
37 #endif
38 
39 #define OFFIS_CONSOLE_APPLICATION "dcmcrle"
40 
41 static OFLogger dcmcrleLogger = 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_FileReadMode opt_readMode = ERM_autoDetect;
59   E_TransferSyntax opt_ixfer = EXS_Unknown;
60   E_GrpLenEncoding opt_oglenc = EGL_recalcGL;
61   E_EncodingType opt_oenctype = EET_ExplicitLength;
62   E_PaddingEncoding opt_opadenc = EPD_noChange;
63   OFCmdUnsignedInt opt_filepad = 0;
64   OFCmdUnsignedInt opt_itempad = 0;
65 
66   // RLE options
67   E_TransferSyntax opt_oxfer = EXS_RLELossless;
68   OFCmdUnsignedInt opt_fragmentSize = 0; // 0=unlimited
69   OFBool           opt_createOffsetTable = OFTrue;
70   OFBool           opt_uidcreation = OFFalse;
71   OFBool           opt_secondarycapture = OFFalse;
72 
73   OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION, "Encode DICOM file to RLE transfer syntax", rcsid);
74   OFCommandLine cmd;
75   cmd.setOptionColumns(LONGCOL, SHORTCOL);
76   cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
77 
78   cmd.addParam("dcmfile-in",  "DICOM input filename to be converted");
79   cmd.addParam("dcmfile-out", "DICOM output filename");
80 
81   cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
82     cmd.addOption("--help",                  "-h",     "print this help text and exit", OFCommandLine::AF_Exclusive);
83     cmd.addOption("--version",                         "print version information and exit", OFCommandLine::AF_Exclusive);
84     OFLog::addOptions(cmd);
85 
86   cmd.addGroup("input options:");
87     cmd.addSubGroup("input file format:");
88       cmd.addOption("--read-file",           "+f",     "read file format or data set (default)");
89       cmd.addOption("--read-file-only",      "+fo",    "read file format only");
90       cmd.addOption("--read-dataset",        "-f",     "read data set without file meta information");
91     cmd.addSubGroup("input transfer syntax:", LONGCOL, SHORTCOL);
92       cmd.addOption("--read-xfer-auto",      "-t=",    "use TS recognition (default)");
93       cmd.addOption("--read-xfer-detect",    "-td",    "ignore TS specified in the file meta header");
94       cmd.addOption("--read-xfer-little",    "-te",    "read with explicit VR little endian TS");
95       cmd.addOption("--read-xfer-big",       "-tb",    "read with explicit VR big endian TS");
96       cmd.addOption("--read-xfer-implicit",  "-ti",    "read with implicit VR little endian TS");
97 
98   cmd.addGroup("encapsulated pixel data encoding options:");
99     cmd.addSubGroup("pixel data fragmentation:");
100       cmd.addOption("--fragment-per-frame",  "+ff",    "encode each frame as one fragment (default)");
101       cmd.addOption("--fragment-size",       "+fs", 1, "[s]ize: integer",
102                                                        "limit fragment size to s kbytes (non-standard)");
103     cmd.addSubGroup("basic offset table encoding:");
104       cmd.addOption("--offset-table-create", "+ot",    "create offset table (default)");
105       cmd.addOption("--offset-table-empty",  "-ot",    "leave offset table empty");
106 
107     cmd.addSubGroup("SOP Class UID:");
108       cmd.addOption("--class-default",       "+cd",    "keep SOP Class UID (default)");
109       cmd.addOption("--class-sc",            "+cs",    "convert to Secondary Capture Image\n(implies --uid-always)");
110 
111     cmd.addSubGroup("SOP Instance UID:");
112       cmd.addOption("--uid-never",           "+un",    "never assign new UID (default)");
113       cmd.addOption("--uid-always",          "+ua",    "always assign new UID");
114 
115   cmd.addGroup("output options:");
116     cmd.addSubGroup("post-1993 value representations:");
117       cmd.addOption("--enable-new-vr",       "+u",     "enable support for new VRs (UN/UT) (default)");
118       cmd.addOption("--disable-new-vr",      "-u",     "disable support for new VRs, convert to OB");
119     cmd.addSubGroup("group length encoding:");
120       cmd.addOption("--group-length-recalc", "+g=",    "recalculate group lengths if present (default)");
121       cmd.addOption("--group-length-create", "+g",     "always write with group length elements");
122       cmd.addOption("--group-length-remove", "-g",     "always write without group length elements");
123     cmd.addSubGroup("length encoding in sequences and items:");
124       cmd.addOption("--length-explicit",     "+e",     "write with explicit lengths (default)");
125       cmd.addOption("--length-undefined",    "-e",     "write with undefined lengths");
126     cmd.addSubGroup("data set trailing padding:");
127       cmd.addOption("--padding-retain",      "-p=",    "do not change padding (default)");
128       cmd.addOption("--padding-off",         "-p",     "no padding");
129       cmd.addOption("--padding-create",      "+p",  2, "[f]ile-pad [i]tem-pad: integer",
130                                                        "align file on multiple of f bytes\nand items on multiple of i bytes");
131 
132     /* evaluate command line */
133     prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
134     if (app.parseCommandLine(cmd, argc, argv))
135     {
136       /* check exclusive options first */
137       if (cmd.hasExclusiveOption())
138       {
139           if (cmd.findOption("--version"))
140           {
141               app.printHeader(OFTrue /*print host identifier*/);
142               COUT << OFendl << "External libraries used:";
143 #ifdef WITH_ZLIB
144               COUT << OFendl << "- ZLIB, Version " << zlibVersion() << OFendl;
145 #else
146               COUT << " none" << OFendl;
147 #endif
148               return 0;
149           }
150       }
151 
152       /* command line parameters */
153 
154       cmd.getParam(1, opt_ifname);
155       cmd.getParam(2, opt_ofname);
156 
157       OFLog::configureFromCommandLine(cmd, app);
158 
159       cmd.beginOptionBlock();
160       if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
161       if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
162       if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
163       cmd.endOptionBlock();
164 
165       cmd.beginOptionBlock();
166       if (cmd.findOption("--read-xfer-auto"))
167           opt_ixfer = EXS_Unknown;
168       if (cmd.findOption("--read-xfer-detect"))
169           dcmAutoDetectDatasetXfer.set(OFTrue);
170       if (cmd.findOption("--read-xfer-little"))
171       {
172           app.checkDependence("--read-xfer-little", "--read-dataset", opt_readMode == ERM_dataset);
173           opt_ixfer = EXS_LittleEndianExplicit;
174       }
175       if (cmd.findOption("--read-xfer-big"))
176       {
177           app.checkDependence("--read-xfer-big", "--read-dataset", opt_readMode == ERM_dataset);
178           opt_ixfer = EXS_BigEndianExplicit;
179       }
180       if (cmd.findOption("--read-xfer-implicit"))
181       {
182           app.checkDependence("--read-xfer-implicit", "--read-dataset", opt_readMode == ERM_dataset);
183           opt_ixfer = EXS_LittleEndianImplicit;
184       }
185       cmd.endOptionBlock();
186 
187       // RLE options
188 
189       cmd.beginOptionBlock();
190       if (cmd.findOption("--fragment-per-frame")) opt_fragmentSize = 0;
191       if (cmd.findOption("--fragment-size"))
192       {
193         app.checkValue(cmd.getValueAndCheckMin(opt_fragmentSize, OFstatic_cast(OFCmdUnsignedInt, 1)));
194       }
195       cmd.endOptionBlock();
196 
197       cmd.beginOptionBlock();
198       if (cmd.findOption("--offset-table-create")) opt_createOffsetTable = OFTrue;
199       if (cmd.findOption("--offset-table-empty")) opt_createOffsetTable = OFFalse;
200       cmd.endOptionBlock();
201 
202       cmd.beginOptionBlock();
203       if (cmd.findOption("--class-default")) opt_secondarycapture = OFFalse;
204       if (cmd.findOption("--class-sc")) opt_secondarycapture = OFTrue;
205       cmd.endOptionBlock();
206 
207       cmd.beginOptionBlock();
208       if (cmd.findOption("--uid-always")) opt_uidcreation = OFTrue;
209       if (cmd.findOption("--uid-never")) opt_uidcreation = OFFalse;
210       cmd.endOptionBlock();
211 
212       cmd.beginOptionBlock();
213       if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
214       if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
215       cmd.endOptionBlock();
216 
217       cmd.beginOptionBlock();
218       if (cmd.findOption("--group-length-recalc")) opt_oglenc = EGL_recalcGL;
219       if (cmd.findOption("--group-length-create")) opt_oglenc = EGL_withGL;
220       if (cmd.findOption("--group-length-remove")) opt_oglenc = EGL_withoutGL;
221       cmd.endOptionBlock();
222 
223       cmd.beginOptionBlock();
224       if (cmd.findOption("--length-explicit")) opt_oenctype = EET_ExplicitLength;
225       if (cmd.findOption("--length-undefined")) opt_oenctype = EET_UndefinedLength;
226       cmd.endOptionBlock();
227 
228       cmd.beginOptionBlock();
229       if (cmd.findOption("--padding-retain")) opt_opadenc = EPD_noChange;
230       if (cmd.findOption("--padding-off")) opt_opadenc = EPD_withoutPadding;
231       if (cmd.findOption("--padding-create"))
232       {
233           app.checkValue(cmd.getValueAndCheckMin(opt_filepad, 0));
234           app.checkValue(cmd.getValueAndCheckMin(opt_itempad, 0));
235           opt_opadenc = EPD_withPadding;
236       }
237       cmd.endOptionBlock();
238     }
239 
240     /* print resource identifier */
241     OFLOG_DEBUG(dcmcrleLogger, rcsid << OFendl);
242 
243     // register RLE compression codec
244     DcmRLEEncoderRegistration::registerCodecs(opt_uidcreation,
245       OFstatic_cast(Uint32, opt_fragmentSize), opt_createOffsetTable, opt_secondarycapture);
246 
247     /* make sure data dictionary is loaded */
248     if (!dcmDataDict.isDictionaryLoaded())
249     {
250         OFLOG_WARN(dcmcrleLogger, "no data dictionary loaded, check environment variable: "
251             << DCM_DICT_ENVIRONMENT_VARIABLE);
252     }
253 
254     // open inputfile
255     if ((opt_ifname == NULL) || (strlen(opt_ifname) == 0))
256     {
257         OFLOG_FATAL(dcmcrleLogger, "invalid filename: <empty string>");
258         return 1;
259     }
260 
261     DcmFileFormat fileformat;
262     DcmDataset * dataset = fileformat.getDataset();
263 
264     OFLOG_INFO(dcmcrleLogger, "open input file " << opt_ifname);
265 
266     OFCondition error = fileformat.loadFile(opt_ifname, opt_ixfer, EGL_noChange, DCM_MaxReadLength, opt_readMode);
267 
268     if (error.bad())
269     {
270         OFLOG_FATAL(dcmcrleLogger, error.text() << ": reading file: " << opt_ifname);
271         return 1;
272     }
273 
274     DcmXfer original_xfer(dataset->getOriginalXfer());
275     if (original_xfer.isEncapsulated())
276     {
277       OFLOG_INFO(dcmcrleLogger, "DICOM file is already compressed, converting to uncompressed transfer syntax first");
278       if (EC_Normal != dataset->chooseRepresentation(EXS_LittleEndianExplicit, NULL))
279       {
280         OFLOG_FATAL(dcmcrleLogger, "No conversion from compressed original to uncompressed transfer syntax possible!");
281         return 1;
282       }
283     }
284 
285     OFString sopClass;
286     if (fileformat.getMetaInfo()->findAndGetOFString(DCM_MediaStorageSOPClassUID, sopClass).good())
287     {
288         /* check for DICOMDIR files */
289         if (sopClass == UID_MediaStorageDirectoryStorage)
290         {
291             OFLOG_FATAL(dcmcrleLogger, "DICOMDIR files (Media Storage Directory Storage SOP Class) cannot be compressed!");
292             return 1;
293         }
294     }
295 
296     OFLOG_INFO(dcmcrleLogger, "Convert DICOM file to compressed transfer syntax");
297 
298     DcmXfer opt_oxferSyn(opt_oxfer);
299 
300     if (dataset->chooseRepresentation(opt_oxfer, NULL).good() && dataset->canWriteXfer(opt_oxfer))
301     {
302         OFLOG_INFO(dcmcrleLogger, "Output transfer syntax " << opt_oxferSyn.getXferName() << " can be written");
303     } else {
304         OFLOG_FATAL(dcmcrleLogger, "No conversion to transfer syntax " << opt_oxferSyn.getXferName() << " possible!");
305         return 1;
306     }
307 
308     OFLOG_INFO(dcmcrleLogger, "create output file " << opt_ofname);
309 
310     fileformat.loadAllDataIntoMemory();
311     error = fileformat.saveFile(opt_ofname, opt_oxfer, opt_oenctype, opt_oglenc, opt_opadenc,
312         OFstatic_cast(Uint32, opt_filepad), OFstatic_cast(Uint32, opt_itempad), EWM_updateMeta);
313 
314     if (error.bad())
315     {
316         OFLOG_FATAL(dcmcrleLogger, error.text() << ": writing file: " << opt_ofname);
317         return 1;
318     }
319 
320     OFLOG_INFO(dcmcrleLogger, "conversion successful");
321 
322     // deregister RLE codec
323     DcmRLEEncoderRegistration::cleanup();
324 
325     return 0;
326 }
327