1 /*
2  *
3  *  Copyright (C) 2007-2020, 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:  Michael Onken
17  *
18  *  Purpose: Implements utility for converting standard image formats to DICOM
19  *
20  */
21 
22 
23 #include "dcmtk/config/osconfig.h"
24 #include "dcmtk/dcmdata/cmdlnarg.h"
25 #include "dcmtk/ofstd/ofconapp.h"
26 #include "dcmtk/dcmdata/dcuid.h"
27 #include "dcmtk/dcmdata/dcfilefo.h"
28 #include "dcmtk/dcmdata/dcdict.h"
29 #include "dcmtk/dcmdata/libi2d/i2d.h"
30 #include "dcmtk/dcmdata/libi2d/i2djpgs.h"
31 #include "dcmtk/dcmdata/libi2d/i2dbmps.h"
32 #include "dcmtk/dcmdata/libi2d/i2dplsc.h"
33 #include "dcmtk/dcmdata/libi2d/i2dplvlp.h"
34 #include "dcmtk/dcmdata/libi2d/i2dplnsc.h"
35 
36 #define OFFIS_CONSOLE_APPLICATION "img2dcm"
37 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v" OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
38 
39 #define SHORTCOL 4
40 #define LONGCOL 21
41 
42 static OFLogger img2dcmLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
43 
evaluateFromFileOptions(OFCommandLine & cmd,Image2Dcm & converter)44 static OFCondition evaluateFromFileOptions(OFCommandLine& cmd,
45                                            Image2Dcm& converter)
46 {
47   OFCondition cond;
48   // Parse command line options dealing with DICOM file import
49   if ( cmd.findOption("--dataset-from") )
50   {
51     OFString tempStr;
52     OFCommandLine::E_ValueStatus valStatus;
53     valStatus = cmd.getValue(tempStr);
54     if (valStatus != OFCommandLine::VS_Normal)
55       return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to read value of --dataset-from option");
56     converter.setTemplateFile(tempStr);
57   }
58 
59   if (cmd.findOption("--study-from"))
60   {
61     OFString tempStr;
62     OFCommandLine::E_ValueStatus valStatus;
63     valStatus = cmd.getValue(tempStr);
64     if (valStatus != OFCommandLine::VS_Normal)
65       return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to read value of --study-from option");
66     converter.setStudyFrom(tempStr);
67   }
68 
69   if (cmd.findOption("--series-from"))
70   {
71     OFString tempStr;
72     OFCommandLine::E_ValueStatus valStatus;
73     valStatus = cmd.getValue(tempStr);
74     if (valStatus != OFCommandLine::VS_Normal)
75       return makeOFCondition(OFM_dcmdata, 18, OF_error, "Unable to read value of --series-from option");
76     converter.setSeriesFrom(tempStr);
77   }
78 
79   if (cmd.findOption("--instance-inc"))
80     converter.setIncrementInstanceNumber(OFTrue);
81 
82   // Return success
83   return EC_Normal;
84 }
85 
86 
addCmdLineOptions(OFCommandLine & cmd)87 static void addCmdLineOptions(OFCommandLine& cmd)
88 {
89   cmd.addParam("imgfile-in",  "image input filename");
90   cmd.addParam("dcmfile-out", "DICOM output filename");
91 
92   cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
93     cmd.addOption("--help",                  "-h",      "print this help text and exit", OFCommandLine::AF_Exclusive);
94     cmd.addOption("--version",                          "print version information and exit", OFCommandLine::AF_Exclusive);
95     OFLog::addOptions(cmd);
96 
97   cmd.addGroup("input options:", LONGCOL, SHORTCOL + 2);
98     cmd.addSubGroup("general:");
99       cmd.addOption("--input-format",        "-i",   1, "[i]nput file format: string", "supported formats: JPEG (default), BMP");
100       cmd.addOption("--dataset-from",        "-df",  1, "[f]ilename: string",
101                                                         "use dataset from DICOM file f");
102 
103       cmd.addOption("--study-from",          "-stf", 1, "[f]ilename: string",
104                                                         "read patient/study from DICOM file f");
105       cmd.addOption("--series-from",         "-sef", 1, "[f]ilename: string",
106                                                         "read patient/study/series from DICOM file f");
107       cmd.addOption("--instance-inc",        "-ii",     "increase instance number read from DICOM file");
108     cmd.addSubGroup("JPEG format:");
109       cmd.addOption("--disable-progr",       "-dp",     "disable support for progressive JPEG");
110       cmd.addOption("--disable-ext",         "-de",     "disable support for extended sequential JPEG");
111       cmd.addOption("--insist-on-jfif",      "-jf",     "insist on JFIF header");
112       cmd.addOption("--keep-appn",           "-ka",     "keep APPn sections (except JFIF)");
113 
114   cmd.addGroup("processing options:", LONGCOL, SHORTCOL + 2);
115     cmd.addSubGroup("attribute checking:");
116       cmd.addOption("--do-checks",                      "enable attribute validity checking (default)");
117       cmd.addOption("--no-checks",                      "disable attribute validity checking");
118       cmd.addOption("--insert-type2",        "+i2",     "insert missing type 2 attributes (default)\n(only with --do-checks)");
119       cmd.addOption("--no-type2-insert",     "-i2",     "do not insert missing type 2 attributes \n(only with --do-checks)");
120       cmd.addOption("--invent-type1",        "+i1",     "invent missing type 1 attributes (default)\n(only with --do-checks)");
121       cmd.addOption("--no-type1-invent",     "-i1",     "do not invent missing type 1 attributes\n(only with --do-checks)");
122     cmd.addSubGroup("character set:");
123       cmd.addOption("--latin1",              "+l1",     "set latin-1 as standard character set (default)");
124       cmd.addOption("--no-latin1",           "-l1",     "keep 7-bit ASCII as standard character set");
125     cmd.addSubGroup("other processing options:");
126       cmd.addOption("--key",                 "-k",   1, "[k]ey: gggg,eeee=\"str\", path or dict. name=\"str\"",
127                                                         "add further attribute");
128 
129   cmd.addGroup("output options:");
130     cmd.addSubGroup("target SOP class:");
131       cmd.addOption("--sec-capture",         "-sc",     "write Secondary Capture SOP class (default)");
132       cmd.addOption("--new-sc",              "-nsc",    "write new Secondary Capture SOP classes");
133       cmd.addOption("--vl-photo",            "-vlp",    "write Visible Light Photographic SOP class");
134 
135     cmd.addSubGroup("output file format:");
136       cmd.addOption("--write-file",          "+F",      "write file format (default)");
137       cmd.addOption("--write-dataset",       "-F",      "write data set without file meta information");
138     cmd.addSubGroup("group length encoding:");
139       cmd.addOption("--group-length-recalc", "+g=",     "recalculate group lengths if present (default)");
140       cmd.addOption("--group-length-create", "+g",      "always write with group length elements");
141       cmd.addOption("--group-length-remove", "-g",      "always write without group length elements");
142     cmd.addSubGroup("length encoding in sequences and items:");
143       cmd.addOption("--length-explicit",     "+e",      "write with explicit lengths (default)");
144       cmd.addOption("--length-undefined",    "-e",      "write with undefined lengths");
145     cmd.addSubGroup("data set trailing padding (not with --write-dataset):");
146       cmd.addOption("--padding-off",         "-p",      "no padding (implicit if --write-dataset)");
147       cmd.addOption("--padding-create",      "+p",   2, "[f]ile-pad [i]tem-pad: integer",
148                                                         "align file on multiple of f bytes\nand items on multiple of i bytes");
149 }
150 
151 
startConversion(OFCommandLine & cmd,int argc,char * argv[])152 static OFCondition startConversion(OFCommandLine& cmd,
153                                    int argc,
154                                    char *argv[])
155 {
156   // Parse command line and exclusive options
157   prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
158   OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION, "Convert standard image formats into DICOM format", rcsid);
159   if (app.parseCommandLine(cmd, argc, argv))
160   {
161     /* check exclusive options first */
162     if (cmd.hasExclusiveOption())
163     {
164       if (cmd.findOption("--version"))
165       {
166         app.printHeader(OFTrue /*print host identifier*/);
167         exit(0);
168       }
169     }
170   }
171 
172   /* print resource identifier */
173   OFLOG_DEBUG(img2dcmLogger, rcsid << OFendl);
174 
175   // Main class for controlling conversion
176   Image2Dcm i2d;
177   // Output plugin to use (i.e. SOP class to write)
178   I2DOutputPlug *outPlug = NULL;
179   // Input plugin to use (i.e. file format to read)
180   I2DImgSource *inputPlug = NULL;
181   // Group length encoding mode for output DICOM file
182   E_GrpLenEncoding grpLengthEnc = EGL_recalcGL;
183   // Item and Sequence encoding mode for output DICOM file
184   E_EncodingType lengthEnc = EET_ExplicitLength;
185   // Padding mode for output DICOM file
186   E_PaddingEncoding padEnc = EPD_noChange;
187   // File pad length for output DICOM file
188   OFCmdUnsignedInt filepad = 0;
189   // Item pad length for output DICOM file
190   OFCmdUnsignedInt itempad = 0;
191   // Write file format (with meta header)
192   E_FileWriteMode writeMode = EWM_fileformat;
193   // Override keys are applied at the very end of the conversion "pipeline"
194   OFList<OFString> overrideKeys;
195   // The transfer syntax proposed to be written by output plugin
196   E_TransferSyntax writeXfer;
197 
198   // Parse rest of command line options
199   OFLog::configureFromCommandLine(cmd, app);
200 
201   OFString pixDataFile, outputFile, tempStr;
202   cmd.getParam(1, tempStr);
203 
204   if (tempStr.empty())
205   {
206     OFLOG_ERROR(img2dcmLogger, "No image input filename specified");
207     return EC_IllegalCall;
208   }
209   else
210     pixDataFile = tempStr;
211 
212   cmd.getParam(2, tempStr);
213   if (tempStr.empty())
214   {
215     OFLOG_ERROR(img2dcmLogger, "No DICOM output filename specified");
216     return EC_IllegalCall;
217   }
218   else
219     outputFile = tempStr;
220 
221   if (cmd.findOption("--input-format"))
222   {
223     app.checkValue(cmd.getValue(tempStr));
224     if (tempStr == "JPEG")
225     {
226       inputPlug = new I2DJpegSource();
227     }
228     else if (tempStr == "BMP")
229     {
230       inputPlug = new I2DBmpSource();
231     }
232     else
233     {
234       return makeOFCondition(OFM_dcmdata, 18, OF_error, "No plugin for selected input format available");
235     }
236     if (!inputPlug)
237     {
238       return EC_MemoryExhausted;
239     }
240   }
241   else // default is JPEG
242   {
243     inputPlug = new I2DJpegSource();
244   }
245   OFLOG_INFO(img2dcmLogger, OFFIS_CONSOLE_APPLICATION ": Instantiated input plugin: " << inputPlug->inputFormat());
246 
247  // Find out which plugin to use
248   cmd.beginOptionBlock();
249   if (cmd.findOption("--sec-capture"))
250     outPlug = new I2DOutputPlugSC();
251   if (cmd.findOption("--vl-photo"))
252   {
253     outPlug = new I2DOutputPlugVLP();
254   }
255   if (cmd.findOption("--new-sc"))
256     outPlug = new I2DOutputPlugNewSC();
257   cmd.endOptionBlock();
258   if (!outPlug) // default is the old Secondary Capture object
259     outPlug = new I2DOutputPlugSC();
260   if (outPlug == NULL) return EC_MemoryExhausted;
261   OFLOG_INFO(img2dcmLogger, OFFIS_CONSOLE_APPLICATION ": Instantiated output plugin: " << outPlug->ident());
262 
263   cmd.beginOptionBlock();
264   if (cmd.findOption("--write-file"))    writeMode = EWM_fileformat;
265   if (cmd.findOption("--write-dataset")) writeMode = EWM_dataset;
266   cmd.endOptionBlock();
267 
268   cmd.beginOptionBlock();
269   if (cmd.findOption("--group-length-recalc")) grpLengthEnc = EGL_recalcGL;
270   if (cmd.findOption("--group-length-create")) grpLengthEnc = EGL_withGL;
271   if (cmd.findOption("--group-length-remove")) grpLengthEnc = EGL_withoutGL;
272   cmd.endOptionBlock();
273 
274   cmd.beginOptionBlock();
275   if (cmd.findOption("--length-explicit"))  lengthEnc = EET_ExplicitLength;
276   if (cmd.findOption("--length-undefined")) lengthEnc = EET_UndefinedLength;
277   cmd.endOptionBlock();
278 
279   cmd.beginOptionBlock();
280   if (cmd.findOption("--padding-off"))
281   {
282     filepad = 0;
283     itempad = 0;
284   }
285   else if (cmd.findOption("--padding-create"))
286   {
287     OFCmdUnsignedInt opt_filepad; OFCmdUnsignedInt opt_itempad;
288     app.checkValue(cmd.getValueAndCheckMin(opt_filepad, 0));
289     app.checkValue(cmd.getValueAndCheckMin(opt_itempad, 0));
290     itempad = opt_itempad;
291     filepad = opt_filepad;
292   }
293   cmd.endOptionBlock();
294 
295   // create override attribute dataset (copied from findscu code)
296   if (cmd.findOption("--key", 0, OFCommandLine::FOM_FirstFromLeft))
297   {
298     const char *ovKey = NULL;
299     do {
300       app.checkValue(cmd.getValue(ovKey));
301       overrideKeys.push_back(ovKey);
302     } while (cmd.findOption("--key", 0, OFCommandLine::FOM_NextFromLeft));
303   }
304   i2d.setOverrideKeys(overrideKeys);
305 
306   // Test for ISO Latin 1 option
307   OFBool insertLatin1 = OFTrue;
308   cmd.beginOptionBlock();
309   if (cmd.findOption("--latin1"))
310     insertLatin1 = OFTrue;
311   if (cmd.findOption("--no-latin1"))
312     insertLatin1 = OFFalse;
313   cmd.endOptionBlock();
314   i2d.setISOLatin1(insertLatin1);
315 
316   // evaluate validity checking options
317   OFBool insertType2 = OFTrue;
318   OFBool inventType1 = OFTrue;
319   OFBool doChecks = OFTrue;
320   cmd.beginOptionBlock();
321   if (cmd.findOption("--no-checks"))
322     doChecks = OFFalse;
323   if (cmd.findOption("--do-checks"))
324     doChecks = OFTrue;
325   cmd.endOptionBlock();
326 
327   cmd.beginOptionBlock();
328   if (cmd.findOption("--insert-type2"))
329     insertType2 = OFTrue;
330   if (cmd.findOption("--no-type2-insert"))
331     insertType2 = OFFalse;
332   cmd.endOptionBlock();
333 
334   cmd.beginOptionBlock();
335   if (cmd.findOption("--invent-type1"))
336     inventType1 = OFTrue;
337   if (cmd.findOption("--no-type1-invent"))
338     inventType1 = OFFalse;
339   cmd.endOptionBlock();
340   i2d.setValidityChecking(doChecks, insertType2, inventType1);
341   outPlug->setValidityChecking(doChecks, insertType2, inventType1);
342 
343   // evaluate --xxx-from options and transfer syntax options
344   OFCondition cond;
345   cond = evaluateFromFileOptions(cmd, i2d);
346   if (cond.bad())
347   {
348     delete outPlug; outPlug = NULL;
349     delete inputPlug; inputPlug = NULL;
350     return cond;
351   }
352 
353   if (inputPlug->inputFormat() == "JPEG")
354   {
355     I2DJpegSource *jpgSource = OFstatic_cast(I2DJpegSource*, inputPlug);
356     if (!jpgSource)
357     {
358        delete outPlug; outPlug = NULL;
359        delete inputPlug; inputPlug = NULL;
360        return EC_MemoryExhausted;
361     }
362     if ( cmd.findOption("--disable-progr") )
363       jpgSource->setProgrSupport(OFFalse);
364     if ( cmd.findOption("--disable-ext") )
365       jpgSource->setExtSeqSupport(OFFalse);
366     if ( cmd.findOption("--insist-on-jfif") )
367       jpgSource->setInsistOnJFIF(OFTrue);
368     if ( cmd.findOption("--keep-appn") )
369       jpgSource->setKeepAPPn(OFTrue);
370   }
371   inputPlug->setImageFile(pixDataFile);
372 
373   /* make sure data dictionary is loaded */
374   if (!dcmDataDict.isDictionaryLoaded())
375   {
376     OFLOG_WARN(img2dcmLogger, "no data dictionary loaded, check environment variable: "
377       << DCM_DICT_ENVIRONMENT_VARIABLE);
378   }
379 
380   DcmDataset *resultObject = NULL;
381   OFLOG_INFO(img2dcmLogger, OFFIS_CONSOLE_APPLICATION ": Starting image conversion");
382   cond = i2d.convert(inputPlug, outPlug, resultObject, writeXfer);
383 
384   // Save
385   if (cond.good())
386   {
387     OFLOG_INFO(img2dcmLogger, OFFIS_CONSOLE_APPLICATION ": Saving output DICOM to file " << outputFile);
388     DcmFileFormat dcmff(resultObject);
389     cond = dcmff.saveFile(outputFile.c_str(), writeXfer, lengthEnc,  grpLengthEnc, padEnc, OFstatic_cast(Uint32, filepad), OFstatic_cast(Uint32, itempad), writeMode);
390   }
391 
392   // Cleanup and return
393   delete outPlug; outPlug = NULL;
394   delete inputPlug; inputPlug = NULL;
395   delete resultObject; resultObject = NULL;
396 
397   return cond;
398 }
399 
400 
main(int argc,char * argv[])401 int main(int argc, char *argv[])
402 {
403 
404   // variables for command line
405   OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION, "Convert image file to DICOM", rcsid);
406   OFCommandLine cmd;
407 
408   cmd.setOptionColumns(LONGCOL, SHORTCOL);
409   cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
410   addCmdLineOptions(cmd);
411 
412   OFCondition cond = startConversion(cmd, argc, argv);
413   if (cond.bad())
414   {
415     OFLOG_FATAL(img2dcmLogger, "Error converting file: " << cond.text());
416     return 1;
417   }
418 
419   return 0;
420 }
421