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 http://sourceforge.net/projects/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Gunter Zeilinger, Huetteldorferstr. 24/10, 1150 Vienna/Austria/Europe. 019 * Portions created by the Initial Developer are Copyright (C) 2017 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * Gunter Zeilinger <gunterze@gmail.com> 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.jpg2dcm; 040 041import java.io.File; 042import java.io.BufferedInputStream; 043import java.io.FileInputStream; 044import java.io.IOException; 045import java.util.Arrays; 046import java.util.Date; 047import java.util.ResourceBundle; 048 049import com.sun.imageio.plugins.jpeg.JPEG; 050import org.apache.commons.cli.CommandLine; 051import org.apache.commons.cli.Options; 052import org.apache.commons.cli.OptionBuilder; 053import org.apache.commons.cli.MissingArgumentException; 054import org.apache.commons.cli.ParseException; 055 056import org.dcm4che3.data.Attributes; 057import org.dcm4che3.data.Tag; 058import org.dcm4che3.data.UID; 059import org.dcm4che3.data.VR; 060import org.dcm4che3.imageio.codec.jpeg.JPEGHeader; 061import org.dcm4che3.imageio.codec.mpeg.MPEGHeader; 062import org.dcm4che3.io.DicomOutputStream; 063import org.dcm4che3.io.SAXReader; 064import org.dcm4che3.tool.common.CLIUtils; 065import org.dcm4che3.util.StreamUtils; 066import org.dcm4che3.util.UIDUtils; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069import org.xml.sax.SAXException; 070 071import javax.xml.parsers.ParserConfigurationException; 072 073/** 074 * @author gunter zeilinger<gunterze@gmail.com> 075 * @author Hesham Elbadawi <bsdreko@gmail.com> 076 * @author Vrinda Nayak <vrinda.nayak@j4care.com> 077 * @since May 2017 078 */ 079public class Jpg2Dcm { 080 081 static final Logger LOG = LoggerFactory.getLogger(Jpg2Dcm.class); 082 083 private String charset = "ISO_IR 100"; 084 085 private int fileLen; 086 087 private boolean noAPPn; 088 089 private static Options opts; 090 private static ResourceBundle rb = ResourceBundle 091 .getBundle("org.dcm4che3.tool.jpg2dcm.messages"); 092 private Attributes keys; 093 private String metadataFile; 094 private JPEGHeader jpegHeader; 095 private File pixelDataFile; 096 private byte[] pixelData; 097 098 public Jpg2Dcm() { 099 } 100 101 public void convert(Attributes metadata, Jpg2Dcm jpg2Dcm, File dcmFile) 102 throws Exception { 103 File pixelDataFile = jpg2Dcm.pixelDataFile; 104 fileLen = (int) pixelDataFile.length(); 105 DicomOutputStream dos = null; 106 try { 107 dos = new DicomOutputStream(dcmFile); 108 Date now = new Date(); 109 metadata.setDate(Tag.InstanceCreationDate, VR.DA, now); 110 metadata.setDate(Tag.InstanceCreationTime, VR.TM, now); 111 Attributes fmi = metadata.createFileMetaInformation(metadata.getString(Tag.TransferSyntaxUID)); 112 dos.writeDataset(fmi, metadata); 113 dos.writeHeader(Tag.PixelData, VR.OB, -1); 114 dos.writeHeader(Tag.Item, null, 0); 115 if (noAPPn && jpg2Dcm.jpegHeader != null) { 116 int off = jpg2Dcm.jpegHeader.offsetAfterAPP(); 117 dos.writeHeader(Tag.Item, null, fileLen - off + 3); 118 dos.write((byte) -1); 119 dos.write((byte) JPEG.SOI); 120 dos.write((byte) -1); 121 dos.write(jpg2Dcm.pixelData); 122 } else { 123 dos.writeHeader(Tag.Item, null, (fileLen + 1) & ~1); 124 dos.write(jpg2Dcm.pixelData); 125 } 126 if ((fileLen & 1) != 0) { 127 dos.write(0); 128 } 129 dos.writeHeader(Tag.SequenceDelimitationItem, null, 0); 130 } finally { 131 if (dos != null) 132 dos.close(); 133 } 134 } 135 136 public static void main(String[] args) { 137 try { 138 CommandLine cl = parseCommandLine(args); 139 Jpg2Dcm jpg2Dcm = new Jpg2Dcm(); 140 jpg2Dcm.keys = configureKeys(jpg2Dcm, cl); 141 LOG.info("added keys for coercion: \n" + jpg2Dcm.keys.toString()); 142 jpg2Dcm.metadataFile = cl.getOptionValue("f"); 143 if (cl.getArgs().length < 2) 144 throw new MissingArgumentException("Either input pixel data file or output binary file is missing. See example in jpg2dcm help."); 145 jpg2Dcm.pixelDataFile = new File(cl.getArgs()[0]); 146 Extension ext = getExt(jpg2Dcm.pixelDataFile.getName()); 147 jpg2Dcm.noAPPn = Boolean.valueOf(cl.getOptionValue("na")); 148 Attributes metadata = getMetadata(jpg2Dcm, ext); 149 150 @SuppressWarnings("rawtypes") 151 File dcmFile = new File(cl.getArgs()[1]); 152 long start = System.currentTimeMillis(); 153 jpg2Dcm.convert(metadata, jpg2Dcm, dcmFile); 154 long fin = System.currentTimeMillis(); 155 LOG.info("Encapsulated " + jpg2Dcm.pixelDataFile + " to " + dcmFile.getPath() + " in " 156 + (fin - start) + "ms."); 157 } catch (ParseException e) { 158 System.err.println("jpg2dcm: " + e.getMessage()); 159 System.err.println(rb.getString("try")); 160 System.exit(2); 161 } catch (Exception e) { 162 LOG.error("Error: \n", e); 163 e.printStackTrace(); 164 System.exit(2); 165 } 166 } 167 168 private static CommandLine parseCommandLine(String[] args) 169 throws ParseException { 170 opts = new Options(); 171 opts.addOption(OptionBuilder.hasArgs(2).withArgName("[seq/]attr=value") 172 .withValueSeparator().withDescription(rb.getString("metadata")) 173 .create("m")); 174 opts.addOption("f", "file", true, rb.getString("file")); 175 opts.addOption("na","no-appn", true, rb.getString("no-appn")); 176 CLIUtils.addCommonOptions(opts); 177 return CLIUtils.parseComandLine(args, opts, rb, Jpg2Dcm.class); 178 } 179 180 private static Attributes configureKeys(Jpg2Dcm main, CommandLine cl) { 181 Attributes temp = new Attributes(); 182 CLIUtils.addAttributes(temp, cl.getOptionValues("m")); 183 return temp; 184 } 185 186 private static Attributes getMetadata(Jpg2Dcm jpg2Dcm, Extension ext) 187 throws Exception { 188 Attributes metadata = defaultMetadata(); 189 String metadataFile = jpg2Dcm.metadataFile; 190 if (metadataFile != null) { 191 String metadataFileExt = metadataFile.substring(metadataFile.lastIndexOf(".")+1).toLowerCase(); 192 if (!metadataFileExt.equals("xml")) 193 throw new IllegalArgumentException("Metadata file extension not supported. Read -f option in jpg2dcm help"); 194 File filePath = new File(metadataFile); 195 metadata = SAXReader.parse(filePath.toString()); 196 } 197 coerceAttributes(metadata, jpg2Dcm); 198 switch (ext) { 199 case jpg: 200 case jpeg: 201 metadata.setString(Tag.SOPClassUID, VR.UI, metadata.getString(Tag.SOPClassUID, UID.SecondaryCaptureImageStorage)); 202 metadata.setString(Tag.TransferSyntaxUID, VR.UI, metadata.getString(Tag.TransferSyntaxUID, UID.JPEGBaseline1)); 203 readPixelHeader(jpg2Dcm, metadata, jpg2Dcm.pixelDataFile, false); 204 break; 205 case mpg: 206 case mpeg: 207 case mpg2: 208 metadata.setString(Tag.SOPClassUID, VR.UI, metadata.getString(Tag.SOPClassUID, UID.VideoPhotographicImageStorage)); 209 metadata.setString(Tag.TransferSyntaxUID, VR.UI, metadata.getString(Tag.TransferSyntaxUID, UID.MPEG2)); 210 readPixelHeader(jpg2Dcm, metadata, jpg2Dcm.pixelDataFile, true); 211 break; 212 } 213 return metadata; 214 } 215 216 private static Attributes defaultMetadata() throws ParserConfigurationException, SAXException, IOException { 217 LOG.info("Always first set default metadata in the event of required attributes not set/sent by user."); 218 Attributes metadata = new Attributes(); 219 metadata.setString(Tag.PatientName, VR.PN, "JPG2DCM-PatientName"); 220 metadata.setString(Tag.StudyInstanceUID, VR.UI, UIDUtils.createUID()); 221 metadata.setString(Tag.SeriesInstanceUID, VR.UI, UIDUtils.createUID()); 222 metadata.setString(Tag.SOPInstanceUID, VR.UI, UIDUtils.createUID()); 223 return metadata; 224 } 225 226 private static void coerceAttributes(Attributes metadata, Jpg2Dcm jpg2Dcm) { 227 if (jpg2Dcm.keys.tags().length > 0) 228 LOG.info("Coercing the following keys from specified attributes to metadata:"); 229 metadata.addAll(jpg2Dcm.keys); 230 metadata.setString(Tag.SpecificCharacterSet, VR.CS, metadata.getString(Tag.SpecificCharacterSet, jpg2Dcm.charset)); 231 LOG.info(jpg2Dcm.keys.toString()); 232 } 233 234 enum Extension { 235 mpg, mpg2, mpeg, jpeg, jpg 236 } 237 private static Extension getExt(String file) { 238 String fileExt = file.substring(file.lastIndexOf(".")+1).toLowerCase(); 239 for (Extension ext : Extension.values()) 240 if (ext.name().equals(fileExt)) 241 return ext; 242 throw new IllegalArgumentException("Specified file for conversion is not Pixel Data Type. Read jpg2dcm help for supported extension types."); 243 } 244 245 private static void readPixelHeader(Jpg2Dcm jpg2Dcm, Attributes metadata, File pixelDataFile, boolean isMpeg) 246 throws Exception { 247 int fileLen = (int) pixelDataFile.length(); 248 FileInputStream fis = null; 249 BufferedInputStream bis = null; 250 try { 251 fis = new FileInputStream(pixelDataFile); 252 bis = new BufferedInputStream(fis); 253 byte[] b16384 = new byte[16384]; 254 StreamUtils.readFully(bis, b16384, 0, 16384); 255 jpg2Dcm.pixelData = new byte[fileLen]; 256 if (isMpeg) { 257 MPEGHeader mpegHeader = new MPEGHeader(b16384); 258 mpegHeader.toAttributes(metadata, pixelDataFile.length()); 259 jpg2Dcm.pixelData = Arrays.copyOf(b16384, fileLen); 260 StreamUtils.readFully(bis, jpg2Dcm.pixelData, 16384, fileLen-16384); 261 } else { 262 jpg2Dcm.jpegHeader = new JPEGHeader(b16384, JPEG.SOS); 263 jpg2Dcm.jpegHeader.toAttributes(metadata); 264 if (jpg2Dcm.noAPPn) { 265 int off = jpg2Dcm.jpegHeader.offsetAfterAPP(); 266 byte[] bTemp = Arrays.copyOf(b16384, fileLen); 267 StreamUtils.readFully(bis, bTemp, 16384, fileLen - 16384); 268 jpg2Dcm.pixelData = Arrays.copyOfRange(bTemp, off, fileLen); 269 } else { 270 jpg2Dcm.pixelData = Arrays.copyOf(b16384, fileLen); 271 StreamUtils.readFully(bis, jpg2Dcm.pixelData, 16384, fileLen - 16384); 272 } 273 } 274 } finally { 275 if (bis != null) 276 bis.close(); 277 if (fis != null) 278 fis.close(); 279 } 280 } 281 282}