001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.tool.dcm2dcm;
040
041import java.io.File;
042import java.io.IOException;
043import java.text.MessageFormat;
044import java.util.ArrayList;
045import java.util.List;
046import java.util.ResourceBundle;
047
048import org.apache.commons.cli.CommandLine;
049import org.apache.commons.cli.OptionBuilder;
050import org.apache.commons.cli.OptionGroup;
051import org.apache.commons.cli.Options;
052import org.apache.commons.cli.ParseException;
053import org.apache.commons.cli.PatternOptionBuilder;
054import org.dcm4che3.data.Tag;
055import org.dcm4che3.data.UID;
056import org.dcm4che3.data.Attributes;
057import org.dcm4che3.data.Fragments;
058import org.dcm4che3.data.VR;
059import org.dcm4che3.imageio.codec.Compressor;
060import org.dcm4che3.imageio.codec.Decompressor;
061import org.dcm4che3.imageio.codec.TransferSyntaxType;
062import org.dcm4che3.io.DicomEncodingOptions;
063import org.dcm4che3.io.DicomInputStream;
064import org.dcm4che3.io.DicomOutputStream;
065import org.dcm4che3.io.DicomInputStream.IncludeBulkData;
066import org.dcm4che3.tool.common.CLIUtils;
067import org.dcm4che3.util.Property;
068import org.dcm4che3.util.SafeClose;
069
070/**
071 * @author Gunter Zeilinger <gunterze@gmail.com>
072 */
073public class Dcm2Dcm {
074
075    private static ResourceBundle rb =
076        ResourceBundle.getBundle("org.dcm4che3.tool.dcm2dcm.messages");
077
078    private String tsuid;
079    private TransferSyntaxType tstype;
080    private boolean retainfmi;
081    private boolean nofmi;
082    private DicomEncodingOptions encOpts = DicomEncodingOptions.DEFAULT;
083    private final List<Property> params = new ArrayList<Property>();
084
085    public final void setTransferSyntax(String uid) {
086        this.tsuid = uid;
087        this.tstype = TransferSyntaxType.forUID(uid);
088        if (tstype == null) {
089            throw new IllegalArgumentException(
090                    "Unsupported Transfer Syntax: " + tsuid);
091        }
092    }
093
094    public final void setRetainFileMetaInformation(boolean retainfmi) {
095        this.retainfmi = retainfmi;
096    }
097
098    public final void setWithoutFileMetaInformation(boolean nofmi) {
099        this.nofmi = nofmi;
100    }
101
102    public final void setEncodingOptions(DicomEncodingOptions encOpts) {
103        this.encOpts = encOpts;
104    }
105
106    public void addCompressionParam(String name, Object value) {
107        params.add(new Property(name, value));
108    }
109
110    private static Object toValue(String s) {
111        try {
112            return Double.valueOf(s);
113        } catch (NumberFormatException e) {
114            return s.equalsIgnoreCase("true") ? Boolean.TRUE :
115                  s.equalsIgnoreCase("false") ? Boolean.FALSE
116                                              : s;
117        }
118    }
119
120    @SuppressWarnings("static-access")
121    private static CommandLine parseComandLine(String[] args)
122            throws ParseException{
123        Options opts = new Options();
124        CLIUtils.addCommonOptions(opts);
125        CLIUtils.addEncodingOptions(opts);
126        OptionGroup tsGroup = new OptionGroup();
127        tsGroup.addOption(OptionBuilder
128                .withLongOpt("transfer-syntax")
129                .hasArg()
130                .withArgName("uid")
131                .withDescription(rb.getString("transfer-syntax"))
132                .create("t"));
133        tsGroup.addOption(OptionBuilder
134                .withLongOpt("jpeg")
135                .withDescription(rb.getString("jpeg"))
136                .create());
137        tsGroup.addOption(OptionBuilder
138                .withLongOpt("jpll")
139                .withDescription(rb.getString("jpll"))
140                .create());
141        tsGroup.addOption(OptionBuilder
142                .withLongOpt("jpls")
143                .withDescription(rb.getString("jpls"))
144                .create());
145        tsGroup.addOption(OptionBuilder
146                .withLongOpt("j2kr")
147                .withDescription(rb.getString("j2kr"))
148                .create());
149        tsGroup.addOption(OptionBuilder
150                .withLongOpt("j2ki")
151                .withDescription(rb.getString("j2ki"))
152                .create());
153        opts.addOptionGroup(tsGroup);
154        OptionGroup fmiGroup = new OptionGroup();
155        fmiGroup.addOption(OptionBuilder
156                .withLongOpt("no-fmi")
157                .withDescription(rb.getString("no-fmi"))
158                .create("F"));
159        fmiGroup.addOption(OptionBuilder
160                .withLongOpt("retain-fmi")
161                .withDescription(rb.getString("retain-fmi"))
162                .create("f"));
163        opts.addOptionGroup(fmiGroup);
164        opts.addOption(OptionBuilder
165                .hasArg()
166                .withArgName("max-error")
167                .withType(PatternOptionBuilder.NUMBER_VALUE)
168                .withDescription(rb.getString("verify"))
169                .withLongOpt("verify")
170                .create());
171        opts.addOption(OptionBuilder
172                .hasArg()
173                .withArgName("size")
174                .withType(PatternOptionBuilder.NUMBER_VALUE)
175                .withDescription(rb.getString("verify-block"))
176                .withLongOpt("verify-block")
177                .create());
178        opts.addOption(OptionBuilder
179                .hasArg()
180                .withArgName("quality")
181                .withType(PatternOptionBuilder.NUMBER_VALUE)
182                .withDescription(rb.getString("quality"))
183                .create("q"));
184        opts.addOption(OptionBuilder
185                .hasArg()
186                .withArgName("encoding-rate")
187                .withType(PatternOptionBuilder.NUMBER_VALUE)
188                .withDescription(rb.getString("encoding-rate"))
189                .create("Q"));
190        opts.addOption(OptionBuilder
191                .hasArgs()
192                .withArgName("name=value")
193                .withValueSeparator()
194                .withDescription(rb.getString("compression-param"))
195                .create("C"));
196        CommandLine cl = CLIUtils.parseComandLine(args, opts, rb, Dcm2Dcm.class);
197        return cl;
198    }
199
200     public static void main(String[] args) {
201        try {
202            CommandLine cl = parseComandLine(args);
203            Dcm2Dcm main = new Dcm2Dcm();
204            main.setEncodingOptions(CLIUtils.encodingOptionsOf(cl));
205            if (cl.hasOption("F")) {
206                if (transferSyntaxOf(cl, null) != null)
207                    throw new ParseException(rb.getString("transfer-syntax-no-fmi"));
208                main.setTransferSyntax(UID.ImplicitVRLittleEndian);
209                main.setWithoutFileMetaInformation(true);
210            } else {
211                main.setTransferSyntax(transferSyntaxOf(cl, UID.ExplicitVRLittleEndian));
212                main.setRetainFileMetaInformation(cl.hasOption("f"));
213            }
214
215            if (cl.hasOption("verify"))
216                main.addCompressionParam("maxPixelValueError",
217                        cl.getParsedOptionValue("verify"));
218
219            if (cl.hasOption("verify-block"))
220                main.addCompressionParam("avgPixelValueBlockSize",
221                        cl.getParsedOptionValue("verify-block"));
222
223            if (cl.hasOption("q"))
224                main.addCompressionParam("compressionQuality",
225                        cl.getParsedOptionValue("q"));
226
227            if (cl.hasOption("Q"))
228                main.addCompressionParam("encodingRate",
229                        cl.getParsedOptionValue("Q"));
230
231            String[] cparams = cl.getOptionValues("C");
232            if (cparams != null)
233                for (int i = 0; i < cparams.length;)
234                    main.addCompressionParam(cparams[i++], toValue(cparams[i++]));
235
236            @SuppressWarnings("unchecked")
237            final List<String> argList = cl.getArgList();
238            int argc = argList.size();
239            if (argc < 2)
240                throw new ParseException(rb.getString("missing"));
241            File dest = new File(argList.get(argc-1));
242            if ((argc > 2 || new File(argList.get(0)).isDirectory())
243                    && !dest.isDirectory())
244                throw new ParseException(
245                        MessageFormat.format(rb.getString("nodestdir"), dest));
246            for (String src : argList.subList(0, argc-1))
247                main.mtranscode(new File(src), dest);
248        } catch (ParseException e) {
249            System.err.println("dcm2dcm: " + e.getMessage());
250            System.err.println(rb.getString("try"));
251            System.exit(2);
252        } catch (Exception e) {
253            System.err.println("dcm2dcm: " + e.getMessage());
254            e.printStackTrace();
255            System.exit(2);
256        }
257    }
258
259    private static String transferSyntaxOf(CommandLine cl, String def) {
260        return cl.hasOption("ivrle") ? UID.ImplicitVRLittleEndian
261                : cl.hasOption("evrbe") ? UID.ExplicitVRBigEndianRetired
262                : cl.hasOption("defl") ? UID.DeflatedExplicitVRLittleEndian
263                : cl.hasOption("jpeg") ? UID.JPEGBaseline1
264                : cl.hasOption("jpll") ? UID.JPEGLossless
265                : cl.hasOption("jpls") ? UID.JPEGLSLossless
266                : cl.hasOption("j2kr") ? UID.JPEG2000LosslessOnly
267                : cl.hasOption("j2ki") ? UID.JPEG2000
268                : cl.getOptionValue("t", def);
269    }
270
271    private void mtranscode(File src, File dest) {
272         if (src.isDirectory()) {
273             dest.mkdir();
274             for (File file : src.listFiles())
275                 mtranscode(file, new File(dest, file.getName()));
276             return;
277         }
278         if (dest.isDirectory())
279             dest = new File(dest, src.getName());
280         try {
281             transcode(src, dest);
282             System.out.println(
283                     MessageFormat.format(rb.getString("transcoded"),
284                             src, dest));
285         } catch (Exception e) {
286             System.out.println(
287                     MessageFormat.format(rb.getString("failed"),
288                             src, e.getMessage()));
289             e.printStackTrace(System.out);
290         }
291     }
292
293     public void transcode(File src, File dest) throws IOException {
294        Attributes fmi;
295        Attributes dataset;
296        DicomInputStream dis = new DicomInputStream(src);
297        try {
298            dis.setIncludeBulkData(IncludeBulkData.URI);
299            fmi = dis.readFileMetaInformation();
300            dataset = dis.readDataset(-1, -1);
301        } finally {
302            dis.close();
303        }
304        Object pixeldata = dataset.getValue(Tag.PixelData);
305        Compressor compressor = null;
306        DicomOutputStream dos = null;
307        try {
308            String tsuid = this.tsuid;
309            if (pixeldata != null) {
310                if (tstype.isPixeldataEncapsulated()) {
311                    tsuid = adjustTransferSyntax(tsuid,
312                            dataset.getInt(Tag.BitsStored, 8));
313                    compressor = new Compressor(dataset, dis.getTransferSyntax(),
314                            tsuid, params.toArray(new Property[params.size()]));
315                    compressor.compress();
316                } else if (pixeldata instanceof Fragments)
317                    Decompressor.decompress(dataset, dis.getTransferSyntax());
318            }
319            if (nofmi)
320                fmi = null;
321            else if (retainfmi && fmi != null)
322                fmi.setString(Tag.TransferSyntaxUID, VR.UI, tsuid);
323            else
324                fmi = dataset.createFileMetaInformation(tsuid);
325            dos = new DicomOutputStream(dest);
326            dos.setEncodingOptions(encOpts);
327            dos.writeDataset(fmi, dataset);
328        } finally {
329            SafeClose.close(compressor);
330            SafeClose.close(dos);
331        }
332     }
333
334    private String adjustTransferSyntax(String tsuid, int bitsStored) {
335        switch (tstype) {
336        case JPEG_BASELINE:
337            if (bitsStored > 8)
338                return UID.JPEGExtended24;
339            break;
340        case JPEG_EXTENDED:
341            if (bitsStored <= 8)
342                return UID.JPEGBaseline1;
343            break;
344        default:
345        }
346        return tsuid;
347    }
348
349}