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) 2013 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.dcm2jpg; 040 041import java.awt.image.BufferedImage; 042import java.awt.image.ColorModel; 043import java.io.File; 044import java.io.IOException; 045import java.text.MessageFormat; 046import java.util.Arrays; 047import java.util.Iterator; 048import java.util.List; 049import java.util.ResourceBundle; 050 051import javax.imageio.IIOImage; 052import javax.imageio.ImageIO; 053import javax.imageio.ImageReadParam; 054import javax.imageio.ImageReader; 055import javax.imageio.ImageWriteParam; 056import javax.imageio.ImageWriter; 057import javax.imageio.stream.ImageInputStream; 058import javax.imageio.stream.ImageOutputStream; 059 060import org.apache.commons.cli.CommandLine; 061import org.apache.commons.cli.OptionBuilder; 062import org.apache.commons.cli.Options; 063import org.apache.commons.cli.ParseException; 064import org.apache.commons.cli.PatternOptionBuilder; 065import org.dcm4che3.data.Attributes; 066import org.dcm4che3.image.BufferedImageUtils; 067import org.dcm4che3.image.PaletteColorModel; 068import org.dcm4che3.imageio.plugins.dcm.DicomImageReadParam; 069import org.dcm4che3.io.DicomInputStream; 070import org.dcm4che3.tool.common.CLIUtils; 071import org.dcm4che3.util.SafeClose; 072 073/** 074 * @author Gunter Zeilinger <gunterze@gmail.com> 075 */ 076public class Dcm2Jpg { 077 078 private static ResourceBundle rb = 079 ResourceBundle.getBundle("org.dcm4che3.tool.dcm2jpg.messages"); 080 081 private String suffix; 082 private int frame = 1; 083 private int windowIndex; 084 private int voiLUTIndex; 085 private boolean preferWindow = true; 086 private float windowCenter; 087 private float windowWidth; 088 private boolean autoWindowing = true; 089 private Attributes prState; 090 private final ImageReader imageReader = 091 ImageIO.getImageReadersByFormatName("DICOM").next(); 092 private ImageWriter imageWriter; 093 private ImageWriteParam imageWriteParam; 094 private int overlayActivationMask = 0xffff; 095 private int overlayGrayscaleValue = 0xffff; 096 097 public void initImageWriter(String formatName, String suffix, 098 String clazz, String compressionType, Number quality) { 099 Iterator<ImageWriter> imageWriters = 100 ImageIO.getImageWritersByFormatName(formatName); 101 if (!imageWriters.hasNext()) 102 throw new IllegalArgumentException( 103 MessageFormat.format(rb.getString("formatNotSupported"), 104 formatName)); 105 this.suffix = suffix != null ? suffix : formatName.toLowerCase(); 106 imageWriter = imageWriters.next(); 107 if (clazz != null) 108 while (!clazz.equals(imageWriter.getClass().getName())) 109 if (imageWriters.hasNext()) 110 imageWriter = imageWriters.next(); 111 else 112 throw new IllegalArgumentException( 113 MessageFormat.format(rb.getString("noSuchImageWriter"), 114 clazz, formatName)); 115 imageWriteParam = imageWriter.getDefaultWriteParam(); 116 if (compressionType != null || quality != null) { 117 imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 118 if (compressionType != null) 119 imageWriteParam.setCompressionType(compressionType); 120 if (quality != null) 121 imageWriteParam.setCompressionQuality(quality.floatValue()); 122 } 123 } 124 125 public final void setFrame(int frame) { 126 this.frame = frame; 127 } 128 129 public final void setWindowCenter(float windowCenter) { 130 this.windowCenter = windowCenter; 131 } 132 133 public final void setWindowWidth(float windowWidth) { 134 this.windowWidth = windowWidth; 135 } 136 137 public final void setWindowIndex(int windowIndex) { 138 this.windowIndex = windowIndex; 139 } 140 141 public final void setVOILUTIndex(int voiLUTIndex) { 142 this.voiLUTIndex = voiLUTIndex; 143 } 144 145 public final void setPreferWindow(boolean preferWindow) { 146 this.preferWindow = preferWindow; 147 } 148 149 public final void setAutoWindowing(boolean autoWindowing) { 150 this.autoWindowing = autoWindowing; 151 } 152 153 public final void setPresentationState(Attributes prState) { 154 this.prState = prState; 155 } 156 157 public void setOverlayActivationMask(int overlayActivationMask) { 158 this.overlayActivationMask = overlayActivationMask; 159 } 160 161 public void setOverlayGrayscaleValue(int overlayGrayscaleValue) { 162 this.overlayGrayscaleValue = overlayGrayscaleValue; 163 } 164 165 @SuppressWarnings("static-access") 166 private static CommandLine parseComandLine(String[] args) 167 throws ParseException { 168 Options opts = new Options(); 169 CLIUtils.addCommonOptions(opts); 170 opts.addOption(OptionBuilder 171 .hasArg() 172 .withArgName("format") 173 .withDescription(rb.getString("format")) 174 .create("F")); 175 opts.addOption(OptionBuilder 176 .hasArg() 177 .withArgName("class") 178 .withDescription(rb.getString("encoder")) 179 .create("E")); 180 opts.addOption(OptionBuilder 181 .hasArg() 182 .withArgName("type") 183 .withDescription(rb.getString("compression")) 184 .create("C")); 185 opts.addOption(OptionBuilder 186 .hasArg() 187 .withArgName("quality") 188 .withType(PatternOptionBuilder.NUMBER_VALUE) 189 .withDescription(rb.getString("quality")) 190 .create("q")); 191 opts.addOption(OptionBuilder 192 .hasArg() 193 .withArgName("suffix") 194 .withDescription(rb.getString("suffix")) 195 .withLongOpt("suffix") 196 .create()); 197 opts.addOption(OptionBuilder 198 .hasArg() 199 .withArgName("number") 200 .withType(PatternOptionBuilder.NUMBER_VALUE) 201 .withDescription(rb.getString("frame")) 202 .withLongOpt("frame") 203 .create()); 204 opts.addOption(OptionBuilder 205 .hasArg() 206 .withArgName("center") 207 .withType(PatternOptionBuilder.NUMBER_VALUE) 208 .withDescription(rb.getString("windowCenter")) 209 .withLongOpt("windowCenter") 210 .create("c")); 211 opts.addOption(OptionBuilder 212 .hasArg() 213 .withArgName("width") 214 .withType(PatternOptionBuilder.NUMBER_VALUE) 215 .withDescription(rb.getString("windowWidth")) 216 .withLongOpt("windowWidth") 217 .create("w")); 218 opts.addOption(OptionBuilder 219 .hasArg() 220 .withArgName("number") 221 .withType(PatternOptionBuilder.NUMBER_VALUE) 222 .withDescription(rb.getString("window")) 223 .withLongOpt("window") 224 .create()); 225 opts.addOption(OptionBuilder 226 .hasArg() 227 .withArgName("number") 228 .withType(PatternOptionBuilder.NUMBER_VALUE) 229 .withDescription(rb.getString("voilut")) 230 .withLongOpt("voilut") 231 .create()); 232 opts.addOption(OptionBuilder 233 .hasArg() 234 .withArgName("file") 235 .withType(PatternOptionBuilder.EXISTING_FILE_VALUE) 236 .withDescription(rb.getString("ps")) 237 .withLongOpt("ps") 238 .create()); 239 opts.addOption(OptionBuilder 240 .hasArg() 241 .withArgName("mask") 242 .withDescription(rb.getString("overlays")) 243 .withLongOpt("overlays") 244 .create()); 245 opts.addOption(OptionBuilder 246 .hasArg() 247 .withArgName("value") 248 .withDescription(rb.getString("ovlygray")) 249 .withLongOpt("ovlygray") 250 .create()); 251 opts.addOption(null, "uselut", false, rb.getString("uselut")); 252 opts.addOption(null, "noauto", false, rb.getString("noauto")); 253 opts.addOption(null, "lsE", false, rb.getString("lsencoders")); 254 opts.addOption(null, "lsF", false, rb.getString("lsformats")); 255 256 CommandLine cl = CLIUtils.parseComandLine(args, opts, rb, Dcm2Jpg.class); 257 if (cl.hasOption("lsF")) { 258 listSupportedFormats(); 259 System.exit(0); 260 } 261 if (cl.hasOption("lsE")) { 262 listSupportedImageWriters(cl.getOptionValue("F", "JPEG")); 263 System.exit(0); 264 } 265 return cl; 266 } 267 268 public static void main(String[] args) { 269 try { 270 CommandLine cl = parseComandLine(args); 271 Dcm2Jpg main = new Dcm2Jpg(); 272 main.initImageWriter( 273 cl.getOptionValue("F", "JPEG"), 274 cl.getOptionValue("suffix"), 275 cl.getOptionValue("E"), 276 cl.getOptionValue("C"), 277 (Number) cl.getParsedOptionValue("q")); 278 if (cl.hasOption("frame")) 279 main.setFrame( 280 ((Number) cl.getParsedOptionValue("frame")).intValue()); 281 if (cl.hasOption("c")) 282 main.setWindowCenter( 283 ((Number) cl.getParsedOptionValue("c")).floatValue()); 284 if (cl.hasOption("w")) 285 main.setWindowWidth( 286 ((Number) cl.getParsedOptionValue("w")).floatValue()); 287 if (cl.hasOption("window")) 288 main.setWindowIndex( 289 ((Number) cl.getParsedOptionValue("window")).intValue() - 1); 290 if (cl.hasOption("voilut")) 291 main.setVOILUTIndex( 292 ((Number) cl.getParsedOptionValue("voilut")).intValue() - 1); 293 if (cl.hasOption("overlays")) 294 main.setOverlayActivationMask( 295 parseHex(cl.getOptionValue("overlays"))); 296 if (cl.hasOption("ovlygray")) 297 main.setOverlayGrayscaleValue( 298 parseHex(cl.getOptionValue("ovlygray"))); 299 main.setPreferWindow(!cl.hasOption("uselut")); 300 main.setAutoWindowing(!cl.hasOption("noauto")); 301 main.setPresentationState( 302 loadDicomObject((File) cl.getParsedOptionValue("ps"))); 303 @SuppressWarnings("unchecked") 304 final List<String> argList = cl.getArgList(); 305 int argc = argList.size(); 306 if (argc < 2) 307 throw new ParseException(rb.getString("missing")); 308 File dest = new File(argList.get(argc-1)); 309 if ((argc > 2 || new File(argList.get(0)).isDirectory()) 310 && !dest.isDirectory()) 311 throw new ParseException( 312 MessageFormat.format(rb.getString("nodestdir"), dest)); 313 for (String src : argList.subList(0, argc-1)) 314 main.mconvert(new File(src), dest); 315 } catch (ParseException e) { 316 System.err.println("dcm2jpg: " + e.getMessage()); 317 System.err.println(rb.getString("try")); 318 System.exit(2); 319 } catch (Exception e) { 320 System.err.println("dcm2jpg: " + e.getMessage()); 321 e.printStackTrace(); 322 System.exit(2); 323 } 324 } 325 326 private static int parseHex(String s) throws ParseException { 327 try { 328 return Integer.parseInt(s, 16); 329 } catch (NumberFormatException e) { 330 throw new ParseException(e.getMessage()); 331 } 332 } 333 334 private void mconvert(File src, File dest) { 335 if (src.isDirectory()) { 336 dest.mkdir(); 337 for (File file : src.listFiles()) 338 mconvert(file, new File(dest, 339 file.isFile() ? suffix(file) : file.getName())); 340 return; 341 } 342 if (dest.isDirectory()) 343 dest = new File(dest, suffix(src)); 344 try { 345 convert(src, dest); 346 System.out.println( 347 MessageFormat.format(rb.getString("converted"), 348 src, dest)); 349 } catch (Exception e) { 350 System.out.println( 351 MessageFormat.format(rb.getString("failed"), 352 src, e.getMessage())); 353 e.printStackTrace(System.out); 354 } 355 } 356 357 public void convert(File src, File dest) throws IOException { 358 ImageInputStream iis = ImageIO.createImageInputStream(src); 359 try { 360 BufferedImage bi = readImage(iis); 361 bi = convert(bi); 362 dest.delete(); 363 ImageOutputStream ios = ImageIO.createImageOutputStream(dest); 364 try { 365 writeImage(ios, bi); 366 } finally { 367 try { ios.close(); } catch (IOException ignore) {} 368 } 369 } finally { 370 try { iis.close(); } catch (IOException ignore) {} 371 } 372 } 373 374 private BufferedImage convert(BufferedImage bi) { 375 ColorModel cm = bi.getColorModel(); 376 return cm.getNumComponents() == 3 ? BufferedImageUtils.convertToIntRGB(bi) : bi; 377 } 378 379 private BufferedImage readImage(ImageInputStream iis) throws IOException { 380 imageReader.setInput(iis); 381 return imageReader.read(frame-1, readParam()); 382 } 383 384 private ImageReadParam readParam() { 385 DicomImageReadParam param = 386 (DicomImageReadParam) imageReader.getDefaultReadParam(); 387 param.setWindowCenter(windowCenter); 388 param.setWindowWidth(windowWidth); 389 param.setAutoWindowing(autoWindowing); 390 param.setWindowIndex(windowIndex); 391 param.setVOILUTIndex(voiLUTIndex); 392 param.setPreferWindow(preferWindow); 393 param.setPresentationState(prState); 394 param.setOverlayActivationMask(overlayActivationMask); 395 param.setOverlayGrayscaleValue(overlayGrayscaleValue); 396 return param; 397 } 398 399 private void writeImage(ImageOutputStream ios, BufferedImage bi) 400 throws IOException { 401 imageWriter.setOutput(ios); 402 imageWriter.write(null, new IIOImage(bi, null, null), imageWriteParam); 403 } 404 405 406 private String suffix(File src) { 407 return src.getName() + '.' + suffix; 408 } 409 410 private static Attributes loadDicomObject(File f) throws IOException { 411 if (f == null) 412 return null; 413 DicomInputStream dis = new DicomInputStream(f); 414 try { 415 return dis.readDataset(-1, -1); 416 } finally { 417 SafeClose.close(dis); 418 } 419 } 420 421 public static void listSupportedImageWriters(String format) { 422 System.out.println(MessageFormat.format(rb.getString("writers"), format)); 423 Iterator<ImageWriter> it = ImageIO.getImageWritersByFormatName(format); 424 while (it.hasNext()) { 425 ImageWriter writer = it.next(); 426 ImageWriteParam param = writer.getDefaultWriteParam(); 427 System.out.println(MessageFormat.format(rb.getString("writer"), 428 writer.getClass().getName(), 429 param.canWriteCompressed(), 430 param.canWriteProgressive(), 431 param.canWriteTiles(), 432 param.canOffsetTiles(), 433 param.canWriteCompressed() 434 ? Arrays.toString(param.getCompressionTypes()) 435 : null)); 436 } 437 } 438 439 public static void listSupportedFormats() { 440 System.out.println( 441 MessageFormat.format(rb.getString("formats"), 442 Arrays.toString(ImageIO.getWriterFormatNames()))); 443 } 444}