1 /*
2  *
3  *  Copyright (C) 2002-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:  dcmimage
15  *
16  *  Authors: Joerg Riesmeier
17  *
18  *  Purpose: Scale DICOM images
19  *
20  */
21 
22 
23 #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
24 
25 #define INCLUDE_CSTDIO
26 #define INCLUDE_CSTRING
27 #include "dcmtk/ofstd/ofstdinc.h"
28 
29 #include "dcmtk/dcmdata/dctk.h"          /* for various dcmdata headers */
30 #include "dcmtk/dcmdata/cmdlnarg.h"      /* for prepareCmdLineArgs */
31 #include "dcmtk/dcmdata/dcuid.h"         /* for dcmtk version name */
32 
33 #include "dcmtk/ofstd/ofconapp.h"        /* for OFConsoleApplication */
34 #include "dcmtk/ofstd/ofcmdln.h"         /* for OFCommandLine */
35 
36 #include "dcmtk/oflog/oflog.h"           /* for OFLogger */
37 
38 #include "dcmtk/dcmimgle/dcmimage.h"     /* for DicomImage */
39 #include "dcmtk/dcmimage/diregist.h"     /* include to support color images */
40 #include "dcmtk/dcmdata/dcrledrg.h"      /* for DcmRLEDecoderRegistration */
41 
42 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
43 #include "dcmtk/dcmjpeg/djdecode.h"      /* for dcmjpeg decoders */
44 #include "dcmtk/dcmjpeg/dipijpeg.h"      /* for dcmimage JPEG plugin */
45 #endif
46 
47 #ifdef WITH_ZLIB
48 #include <zlib.h>          /* for zlibVersion() */
49 #endif
50 
51 #define OFFIS_CONSOLE_DESCRIPTION "Scale DICOM images"
52 
53 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
54 #define OFFIS_CONSOLE_APPLICATION "dcmjscal"
55 #else
56 #define OFFIS_CONSOLE_APPLICATION "dcmscale"
57 #endif
58 
59 static OFLogger dcmscaleLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
60 
61 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
62   OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
63 
64 #define SHORTCOL 4
65 #define LONGCOL 21
66 
67 
68 // ********************************************
69 
main(int argc,char * argv[])70 int main(int argc, char *argv[])
71 {
72     OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION, OFFIS_CONSOLE_DESCRIPTION, rcsid);
73     OFCommandLine cmd;
74 
75     OFBool opt_uidCreation = OFTrue;
76     E_FileReadMode opt_readMode = ERM_autoDetect;
77     E_FileWriteMode opt_writeMode = EWM_createNewMeta;
78     E_TransferSyntax opt_ixfer = EXS_Unknown;
79     E_TransferSyntax opt_oxfer = EXS_Unknown;
80     E_GrpLenEncoding opt_oglenc = EGL_recalcGL;
81     E_EncodingType opt_oenctype = EET_ExplicitLength;
82     E_PaddingEncoding opt_opadenc = EPD_noChange;
83     OFCmdUnsignedInt opt_filepad = 0;
84     OFCmdUnsignedInt opt_itempad = 0;
85 
86 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
87     // JPEG parameters, currently not used
88 # if 0
89     OFCmdUnsignedInt opt_quality = 90;                 /* default: 90% JPEG quality */
90     E_SubSampling opt_sampling = ESS_422;              /* default: 4:2:2 sub-sampling */
91 # endif
92     E_DecompressionColorSpaceConversion opt_decompCSconversion = EDC_photometricInterpretation;
93 #endif
94 
95     int opt_useAspectRatio = 1;                        /* default: use aspect ratio for scaling */
96     OFCmdUnsignedInt opt_useInterpolation = 1;         /* default: use interpolation method '1' for scaling */
97     int opt_scaleType = 0;                             /* default: no scaling */
98                                                        /* 1 = X-factor, 2 = Y-factor, 3=X-size, 4=Y-size */
99     OFCmdFloat opt_scale_factor = 1.0;
100     OFCmdUnsignedInt opt_scale_size = 1;
101 
102     OFBool           opt_useClip = OFFalse;            /* default: don't clip */
103     OFCmdSignedInt   opt_left = 0, opt_top = 0;        /* clip region (origin) */
104     OFCmdUnsignedInt opt_width = 0, opt_height = 0;    /* clip region (extension) */
105 
106     const char *opt_ifname = NULL;
107     const char *opt_ofname = NULL;
108 
109     prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
110     cmd.setOptionColumns(LONGCOL, SHORTCOL);
111 
112     cmd.addParam("dcmfile-in",  "DICOM input filename to be scaled");
113     cmd.addParam("dcmfile-out", "DICOM output filename to be written");
114 
115     cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
116      cmd.addOption("--help",                "-h",       "print this help text and exit", OFCommandLine::AF_Exclusive);
117      cmd.addOption("--version",                         "print version information and exit", OFCommandLine::AF_Exclusive);
118      OFLog::addOptions(cmd);
119 
120     cmd.addGroup("input options:");
121      cmd.addSubGroup("input file format:");
122       cmd.addOption("--read-file",          "+f",       "read file format or data set (default)");
123       cmd.addOption("--read-file-only",     "+fo",      "read file format only");
124       cmd.addOption("--read-dataset",       "-f",       "read data set without file meta information");
125      cmd.addSubGroup("input transfer syntax:");
126       cmd.addOption("--read-xfer-auto",     "-t=",      "use TS recognition (default)");
127       cmd.addOption("--read-xfer-detect",   "-td",      "ignore TS specified in the file meta header");
128       cmd.addOption("--read-xfer-little",   "-te",      "read with explicit VR little endian TS");
129       cmd.addOption("--read-xfer-big",      "-tb",      "read with explicit VR big endian TS");
130       cmd.addOption("--read-xfer-implicit", "-ti",      "read with implicit VR little endian TS");
131 
132     cmd.addGroup("image processing and encoding options:");
133 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
134      cmd.addSubGroup("color space conversion options (compressed images only):");
135       cmd.addOption("--conv-photometric",   "+cp",      "convert if YCbCr photometric interpr. (default)");
136       cmd.addOption("--conv-lossy",         "+cl",      "convert YCbCr to RGB if lossy JPEG");
137       cmd.addOption("--conv-guess",         "+cg",      "convert to RGB if YCbCr is guessed by library");
138       cmd.addOption("--conv-guess-lossy",   "+cgl",     "convert to RGB if lossy JPEG and YCbCr is\nguessed by the underlying JPEG library");
139       cmd.addOption("--conv-always",        "+ca",      "always convert YCbCr to RGB");
140       cmd.addOption("--conv-never",         "+cn",      "never convert color space");
141 #endif
142      cmd.addSubGroup("scaling:");
143       cmd.addOption("--recognize-aspect",    "+a",      "recognize pixel aspect ratio when scaling (def)");
144       cmd.addOption("--ignore-aspect",       "-a",      "ignore pixel aspect ratio when scaling");
145       cmd.addOption("--interpolate",         "+i",   1, "[n]umber of algorithm: integer",
146                                                         "use interpolation when scaling (1..4, def: 1)");
147       cmd.addOption("--no-interpolation",    "-i",      "no interpolation when scaling");
148       cmd.addOption("--no-scaling",          "-S",      "no scaling, ignore pixel aspect ratio (default)");
149       cmd.addOption("--scale-x-factor",      "+Sxf", 1, "[f]actor: float",
150                                                         "scale x axis by factor, auto-compute y axis");
151       cmd.addOption("--scale-y-factor",      "+Syf", 1, "[f]actor: float",
152                                                         "scale y axis by factor, auto-compute x axis");
153       cmd.addOption("--scale-x-size",        "+Sxv", 1, "[n]umber: integer",
154                                                         "scale x axis to n pixels, auto-compute y axis");
155       cmd.addOption("--scale-y-size",        "+Syv", 1, "[n]umber: integer",
156                                                         "scale y axis to n pixels, auto-compute x axis");
157      cmd.addSubGroup("other transformations:");
158       cmd.addOption("--clip-region",         "+C",   4, "[l]eft [t]op [w]idth [h]eight: integer",
159                                                         "clip rectangular image region (l, t, w, h)");
160      cmd.addSubGroup("SOP Instance UID:");
161       cmd.addOption("--uid-always",          "+ua",     "always assign new SOP Instance UID (default)");
162       cmd.addOption("--uid-never",           "+un",     "never assign new SOP Instance UID");
163 
164   cmd.addGroup("output options:");
165     cmd.addSubGroup("output file format:");
166       cmd.addOption("--write-file",          "+F",      "write file format (default)");
167       cmd.addOption("--write-dataset",       "-F",      "write data set without file meta information");
168     cmd.addSubGroup("output transfer syntax:");
169       cmd.addOption("--write-xfer-same",     "+t=",     "write with same TS as input (default)");
170       cmd.addOption("--write-xfer-little",   "+te",     "write with explicit VR little endian TS");
171       cmd.addOption("--write-xfer-big",      "+tb",     "write with explicit VR big endian TS");
172       cmd.addOption("--write-xfer-implicit", "+ti",     "write with implicit VR little endian TS");
173     cmd.addSubGroup("post-1993 value representations:");
174       cmd.addOption("--enable-new-vr",       "+u",      "enable support for new VRs (UN/UT) (default)");
175       cmd.addOption("--disable-new-vr",      "-u",      "disable support for new VRs, convert to OB");
176     cmd.addSubGroup("group length encoding:");
177       cmd.addOption("--group-length-recalc", "+g=",     "recalculate group lengths if present (default)");
178       cmd.addOption("--group-length-create", "+g",      "always write with group length elements");
179       cmd.addOption("--group-length-remove", "-g",      "always write without group length elements");
180     cmd.addSubGroup("length encoding in sequences and items:");
181       cmd.addOption("--length-explicit",     "+e",      "write with explicit lengths (default)");
182       cmd.addOption("--length-undefined",    "-e",      "write with undefined lengths");
183     cmd.addSubGroup("data set trailing padding (not with --write-dataset):");
184       cmd.addOption("--padding-retain",      "-p=",     "do not change padding\n(default if not --write-dataset)");
185       cmd.addOption("--padding-off",         "-p",      "no padding (implicit if --write-dataset)");
186       cmd.addOption("--padding-create",      "+p",   2, "[f]ile-pad [i]tem-pad: integer",
187                                                         "align file on multiple of f bytes\nand items on multiple of i bytes");
188 
189     if (app.parseCommandLine(cmd, argc, argv))
190     {
191       /* check exclusive options first */
192       if (cmd.hasExclusiveOption())
193       {
194           if (cmd.findOption("--version"))
195           {
196               app.printHeader(OFTrue /*print host identifier*/);
197               COUT << OFendl << "External libraries used:";
198 #if !defined(WITH_ZLIB) && !defined(BUILD_DCMSCALE_AS_DCMJSCAL)
199               COUT << " none" << OFendl;
200 #else
201               COUT << OFendl;
202 #endif
203 #ifdef WITH_ZLIB
204               COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
205 #endif
206 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
207               COUT << "- " << DiJPEGPlugin::getLibraryVersionString() << OFendl;
208 #endif
209               return 0;
210           }
211       }
212 
213       /* command line parameters */
214 
215       cmd.getParam(1, opt_ifname);
216       cmd.getParam(2, opt_ofname);
217 
218       /* general options */
219 
220       OFLog::configureFromCommandLine(cmd, app);
221 
222       /* input options */
223 
224       cmd.beginOptionBlock();
225       if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
226       if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
227       if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
228       cmd.endOptionBlock();
229 
230       cmd.beginOptionBlock();
231       if (cmd.findOption("--read-xfer-auto"))
232           opt_ixfer = EXS_Unknown;
233       if (cmd.findOption("--read-xfer-detect"))
234           dcmAutoDetectDatasetXfer.set(OFTrue);
235       if (cmd.findOption("--read-xfer-little"))
236       {
237           app.checkDependence("--read-xfer-little", "--read-dataset", opt_readMode == ERM_dataset);
238           opt_ixfer = EXS_LittleEndianExplicit;
239       }
240       if (cmd.findOption("--read-xfer-big"))
241       {
242           app.checkDependence("--read-xfer-big", "--read-dataset", opt_readMode == ERM_dataset);
243           opt_ixfer = EXS_BigEndianExplicit;
244       }
245       if (cmd.findOption("--read-xfer-implicit"))
246       {
247           app.checkDependence("--read-xfer-implicit", "--read-dataset", opt_readMode == ERM_dataset);
248           opt_ixfer = EXS_LittleEndianImplicit;
249       }
250       cmd.endOptionBlock();
251 
252       /* image processing options: color space conversion */
253 
254 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
255       cmd.beginOptionBlock();
256       if (cmd.findOption("--conv-photometric"))
257           opt_decompCSconversion = EDC_photometricInterpretation;
258       if (cmd.findOption("--conv-lossy"))
259           opt_decompCSconversion = EDC_lossyOnly;
260       if (cmd.findOption("--conv-guess"))
261           opt_decompCSconversion = EDC_guess;
262       if (cmd.findOption("--conv-guess-lossy"))
263           opt_decompCSconversion = EDC_guessLossyOnly;
264       if (cmd.findOption("--conv-always"))
265           opt_decompCSconversion = EDC_always;
266       if (cmd.findOption("--conv-never"))
267           opt_decompCSconversion = EDC_never;
268       cmd.endOptionBlock();
269 #endif
270 
271       /* image processing options: scaling */
272 
273       cmd.beginOptionBlock();
274       if (cmd.findOption("--recognize-aspect"))
275           opt_useAspectRatio = 1;
276       if (cmd.findOption("--ignore-aspect"))
277           opt_useAspectRatio = 0;
278       cmd.endOptionBlock();
279 
280       cmd.beginOptionBlock();
281       if (cmd.findOption("--interpolate"))
282           app.checkValue(cmd.getValueAndCheckMinMax(opt_useInterpolation, 1, 4));
283       if (cmd.findOption("--no-interpolation"))
284           opt_useInterpolation = 0;
285       cmd.endOptionBlock();
286 
287       cmd.beginOptionBlock();
288       if (cmd.findOption("--no-scaling"))
289           opt_scaleType = 0;
290       if (cmd.findOption("--scale-x-factor"))
291       {
292           opt_scaleType = 1;
293           app.checkValue(cmd.getValueAndCheckMin(opt_scale_factor, 0.0, OFFalse));
294       }
295       if (cmd.findOption("--scale-y-factor"))
296       {
297           opt_scaleType = 2;
298           app.checkValue(cmd.getValueAndCheckMin(opt_scale_factor, 0.0, OFFalse));
299       }
300       if (cmd.findOption("--scale-x-size"))
301       {
302           opt_scaleType = 3;
303           app.checkValue(cmd.getValueAndCheckMin(opt_scale_size, 1));
304       }
305       if (cmd.findOption("--scale-y-size"))
306       {
307           opt_scaleType = 4;
308           app.checkValue(cmd.getValueAndCheckMin(opt_scale_size, 1));
309       }
310       cmd.endOptionBlock();
311 
312       /* image processing options: other transformations */
313 
314       if (cmd.findOption("--clip-region"))
315       {
316           app.checkValue(cmd.getValue(opt_left));
317           app.checkValue(cmd.getValue(opt_top));
318           app.checkValue(cmd.getValue(opt_width));
319           app.checkValue(cmd.getValue(opt_height));
320           opt_useClip = OFTrue;
321       }
322 
323       /* image processing options: SOP Instance UID options */
324 
325       cmd.beginOptionBlock();
326       if (cmd.findOption("--uid-always")) opt_uidCreation = OFTrue;
327       if (cmd.findOption("--uid-never")) opt_uidCreation = OFFalse;
328       cmd.endOptionBlock();
329 
330       /* output options */
331 
332       cmd.beginOptionBlock();
333       if (cmd.findOption("--write-file")) opt_writeMode = EWM_createNewMeta;
334       if (cmd.findOption("--write-dataset")) opt_writeMode = EWM_dataset;
335       cmd.endOptionBlock();
336 
337       cmd.beginOptionBlock();
338       if (cmd.findOption("--write-xfer-same")) opt_oxfer = EXS_Unknown;
339       if (cmd.findOption("--write-xfer-little")) opt_oxfer = EXS_LittleEndianExplicit;
340       if (cmd.findOption("--write-xfer-big")) opt_oxfer = EXS_BigEndianExplicit;
341       if (cmd.findOption("--write-xfer-implicit")) opt_oxfer = EXS_LittleEndianImplicit;
342       cmd.endOptionBlock();
343 
344       cmd.beginOptionBlock();
345       if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
346       if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
347       cmd.endOptionBlock();
348 
349       cmd.beginOptionBlock();
350       if (cmd.findOption("--group-length-recalc")) opt_oglenc = EGL_recalcGL;
351       if (cmd.findOption("--group-length-create")) opt_oglenc = EGL_withGL;
352       if (cmd.findOption("--group-length-remove")) opt_oglenc = EGL_withoutGL;
353       cmd.endOptionBlock();
354 
355       cmd.beginOptionBlock();
356       if (cmd.findOption("--length-explicit")) opt_oenctype = EET_ExplicitLength;
357       if (cmd.findOption("--length-undefined")) opt_oenctype = EET_UndefinedLength;
358       cmd.endOptionBlock();
359 
360       cmd.beginOptionBlock();
361       if (cmd.findOption("--padding-retain"))
362       {
363           app.checkConflict("--padding-retain", "--write-dataset", opt_writeMode == EWM_dataset);
364           opt_opadenc = EPD_noChange;
365       }
366       if (cmd.findOption("--padding-off")) opt_opadenc = EPD_withoutPadding;
367       if (cmd.findOption("--padding-create"))
368       {
369           app.checkConflict("--padding-create", "--write-dataset", opt_writeMode == EWM_dataset);
370           app.checkValue(cmd.getValueAndCheckMin(opt_filepad, 0));
371           app.checkValue(cmd.getValueAndCheckMin(opt_itempad, 0));
372           opt_opadenc = EPD_withPadding;
373       }
374       cmd.endOptionBlock();
375     }
376 
377     /* print resource identifier */
378     OFLOG_DEBUG(dcmscaleLogger, rcsid << OFendl);
379 
380     /* make sure data dictionary is loaded */
381     if (!dcmDataDict.isDictionaryLoaded())
382     {
383         OFLOG_WARN(dcmscaleLogger, "no data dictionary loaded, check environment variable: "
384             << DCM_DICT_ENVIRONMENT_VARIABLE);
385     }
386 
387     // register RLE decompression codec
388     DcmRLEDecoderRegistration::registerCodecs();
389 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
390     // register global decompression codecs
391     DJDecoderRegistration::registerCodecs(opt_decompCSconversion);
392 #endif
393 
394     // ======================================================================
395     // read input file
396 
397     if ((opt_ifname == NULL) || (strlen(opt_ifname) == 0))
398     {
399         OFLOG_FATAL(dcmscaleLogger, "invalid input filename: <empty string>");
400         return 1;
401     }
402 
403     /* no clipping/scaling */
404     if (!opt_scaleType && !opt_useClip)
405     {
406         OFLOG_FATAL(dcmscaleLogger, "nothing to do");
407         return 1;
408     }
409 
410     OFLOG_INFO(dcmscaleLogger, "open input file " << opt_ifname);
411 
412     DcmFileFormat fileformat;
413     DcmDataset *dataset = fileformat.getDataset();
414 
415     OFCondition error = fileformat.loadFile(opt_ifname, opt_ixfer, EGL_noChange, DCM_MaxReadLength, opt_readMode);
416     if (error.bad())
417     {
418         OFLOG_FATAL(dcmscaleLogger, error.text() << ": reading file: " <<  opt_ifname);
419         return 1;
420     }
421 
422     OFLOG_INFO(dcmscaleLogger, "load all data into memory");
423 
424     /* make sure that pixel data is loaded before output file is created */
425     dataset->loadAllDataIntoMemory();
426 
427     // select uncompressed output transfer syntax.
428     // this will implicitly decompress the image if necessary.
429 
430     if (opt_oxfer == EXS_Unknown)
431     {
432         OFLOG_INFO(dcmscaleLogger, "set output transfer syntax to input transfer syntax");
433         opt_oxfer = dataset->getOriginalXfer();
434     }
435 
436     OFLOG_INFO(dcmscaleLogger, "check if new output transfer syntax is possible");
437 
438     DcmXfer opt_oxferSyn(opt_oxfer);
439     if (dataset->chooseRepresentation(opt_oxfer, NULL).good() && dataset->canWriteXfer(opt_oxfer))
440     {
441         OFLOG_INFO(dcmscaleLogger, "output transfer syntax " << opt_oxferSyn.getXferName()
442             << " can be written");
443     } else {
444         OFLOG_FATAL(dcmscaleLogger, "no conversion to transfer syntax " << opt_oxferSyn.getXferName()
445             << " possible");
446         return 1;
447     }
448 
449     // ======================================================================
450     // image processing starts here
451 
452     OFLOG_INFO(dcmscaleLogger, "preparing pixel data");
453 
454     const unsigned long flags = (opt_scaleType > 0) ? CIF_MayDetachPixelData : 0;
455     // create DicomImage object
456     DicomImage *di = new DicomImage(dataset, opt_oxfer, flags);
457     if (di == NULL)
458     {
459         OFLOG_FATAL(dcmscaleLogger, "memory exhausted");
460         return 1;
461     }
462     if (di->getStatus() != EIS_Normal)
463     {
464         OFLOG_FATAL(dcmscaleLogger, DicomImage::getString(di->getStatus()));
465         return 1;
466     }
467 
468     DicomImage *newimage = NULL;
469     OFString derivationDescription;
470 
471     if (opt_useClip)
472         OFLOG_INFO(dcmscaleLogger, "clipping image to (" << opt_left << "," << opt_top
473             << "," << opt_width << "," << opt_height << ")");
474     // perform clipping (without scaling)
475     if (opt_scaleType <= 0)
476     {
477         if (opt_useClip)
478         {
479             newimage = di->createClippedImage(opt_left, opt_top, opt_width, opt_height);
480             derivationDescription = "Clipped rectangular image region";
481         }
482     }
483     // perform scaling (and possibly clipping)
484     else if (opt_scaleType <= 4)
485     {
486         switch (opt_scaleType)
487         {
488             case 1:
489                 OFLOG_INFO(dcmscaleLogger, "scaling image, X factor=" << opt_scale_factor
490                     << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
491                     << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
492                 if (opt_useClip)
493                     newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, opt_scale_factor, 0.0,
494                         OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
495                 else
496                     newimage = di->createScaledImage(opt_scale_factor, 0.0, OFstatic_cast(int, opt_useInterpolation),
497                         opt_useAspectRatio);
498                 break;
499             case 2:
500                 OFLOG_INFO(dcmscaleLogger, "scaling image, Y factor=" << opt_scale_factor
501                     << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
502                     << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
503                 if (opt_useClip)
504                     newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, 0.0, opt_scale_factor,
505                         OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
506                 else
507                     newimage = di->createScaledImage(0.0, opt_scale_factor, OFstatic_cast(int, opt_useInterpolation),
508                         opt_useAspectRatio);
509                 break;
510             case 3:
511                 OFLOG_INFO(dcmscaleLogger, "scaling image, X size=" << opt_scale_size
512                     << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
513                     << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
514                 if (opt_useClip)
515                     newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, opt_scale_size, 0,
516                         OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
517                 else
518                     newimage = di->createScaledImage(opt_scale_size, 0, OFstatic_cast(int, opt_useInterpolation),
519                         opt_useAspectRatio);
520                 break;
521             case 4:
522                 OFLOG_INFO(dcmscaleLogger, "scaling image, Y size=" << opt_scale_size
523                     << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
524                     << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
525                 if (opt_useClip)
526                     newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, 0, opt_scale_size,
527                         OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
528                 else
529                     newimage = di->createScaledImage(0, opt_scale_size, OFstatic_cast(int, opt_useInterpolation),
530                         opt_useAspectRatio);
531                 break;
532             default:
533                 break;
534         }
535         if (opt_useClip)
536             derivationDescription = "Scaled rectangular image region";
537         else
538             derivationDescription = "Scaled image";
539     }
540     if (opt_scaleType > 4)
541         OFLOG_ERROR(dcmscaleLogger, "internal error: unknown scaling type");
542     else if (newimage == NULL)
543     {
544         OFLOG_FATAL(dcmscaleLogger, "cannot create new image");
545         return 1;
546     }
547     else if (newimage->getStatus() != EIS_Normal)
548     {
549         OFLOG_FATAL(dcmscaleLogger, DicomImage::getString(newimage->getStatus()));
550         return 1;
551     }
552     /* write scaled image to dataset (update attributes of Image Pixel Module) */
553     else if (!newimage->writeImageToDataset(*dataset))
554     {
555         OFLOG_FATAL(dcmscaleLogger, "cannot write new image to dataset");
556         return 1;
557     }
558     delete newimage;
559 
560     /* cleanup original image */
561     delete di;
562 
563     // ======================================================================
564     // update some header attributes
565 
566     // update Derivation Description
567     if (!derivationDescription.empty())
568     {
569         const char *oldDerivation = NULL;
570         if (dataset->findAndGetString(DCM_DerivationDescription, oldDerivation).good() && oldDerivation)
571         {
572              // append old Derivation Description, if any
573             derivationDescription += " [";
574             derivationDescription += oldDerivation;
575             derivationDescription += "]";
576             if (derivationDescription.length() > 1024)
577             {
578                 // ST is limited to 1024 characters, cut off tail
579                 derivationDescription.erase(1020);
580                 derivationDescription += "...]";
581             }
582         }
583         dataset->putAndInsertString(DCM_DerivationDescription, derivationDescription.c_str());
584     }
585 
586     // update Image Type
587     OFString imageType = "DERIVED";
588     const char *oldImageType = NULL;
589     if (dataset->findAndGetString(DCM_ImageType, oldImageType).good())
590     {
591         if (oldImageType != NULL)
592         {
593             // append old image type information beginning with second entry
594             const char *pos = strchr(oldImageType, '\\');
595             if (pos != NULL)
596                 imageType += pos;
597         }
598     }
599     dataset->putAndInsertString(DCM_ImageType, imageType.c_str());
600 
601     // update SOP Instance UID
602     if (opt_uidCreation)
603     {
604         // add reference to source image
605         DcmItem *ditem = NULL;
606         const char *sopClassUID = NULL;
607         const char *sopInstanceUID = NULL;
608         if (dataset->findAndGetString(DCM_SOPClassUID, sopClassUID).good() &&
609             dataset->findAndGetString(DCM_SOPInstanceUID, sopInstanceUID).good())
610         {
611             dataset->findAndDeleteElement(DCM_SourceImageSequence);
612             if (dataset->findOrCreateSequenceItem(DCM_SourceImageSequence, ditem).good())
613             {
614                 ditem->putAndInsertString(DCM_SOPClassUID, sopClassUID);
615                 ditem->putAndInsertString(DCM_SOPInstanceUID, sopInstanceUID);
616             }
617         }
618         // create new SOP instance UID
619         char new_uid[100];
620         dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(new_uid));
621     }
622 
623     // ======================================================================
624     // write back output file
625 
626     OFLOG_INFO(dcmscaleLogger, "create output file " << opt_ofname);
627 
628     error = fileformat.saveFile(opt_ofname, opt_oxfer, opt_oenctype, opt_oglenc, opt_opadenc,
629         OFstatic_cast(Uint32, opt_filepad), OFstatic_cast(Uint32, opt_itempad), opt_writeMode);
630 
631     if (error.bad())
632     {
633         OFLOG_FATAL(dcmscaleLogger, error.text() << ": writing file: " <<  opt_ofname);
634         return 1;
635     }
636 
637     OFLOG_INFO(dcmscaleLogger, "conversion successful");
638 
639     // deregister RLE decompression codec
640     DcmRLEDecoderRegistration::cleanup();
641 #ifdef BUILD_DCMSCALE_AS_DCMJSCAL
642     // deregister global decompression codecs
643     DJDecoderRegistration::cleanup();
644 #endif
645 
646     return 0;
647 }
648