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