001/* 002 * **** BEGIN LICENSE BLOCK ***** 003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 004 * 005 * The contents of this file are subject to the Mozilla Public License Version 006 * 1.1 (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * http://www.mozilla.org/MPL/ 009 * 010 * Software distributed under the License is distributed on an "AS IS" basis, 011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 012 * for the specific language governing rights and limitations under the 013 * License. 014 * 015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 016 * Java(TM), hosted at https://github.com/dcm4che. 017 * 018 * The Initial Developer of the Original Code is Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011-2015 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 */ 039 040package org.dcm4che3.imageio.codec; 041 042import org.dcm4che3.data.Attributes; 043import org.dcm4che3.data.Tag; 044import org.dcm4che3.data.VR; 045import org.dcm4che3.image.Overlays; 046import org.dcm4che3.imageio.codec.jpeg.PatchJPEGLS; 047import org.dcm4che3.imageio.codec.jpeg.PatchJPEGLSImageOutputStream; 048import org.dcm4che3.io.DicomInputStream; 049import org.dcm4che3.io.DicomOutputStream; 050import org.dcm4che3.util.ByteUtils; 051import org.dcm4che3.util.Property; 052import org.dcm4che3.util.StreamUtils; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056import javax.imageio.*; 057import javax.imageio.stream.ImageInputStream; 058import javax.imageio.stream.MemoryCacheImageOutputStream; 059import java.awt.image.*; 060import java.io.IOException; 061 062/** 063 * @author Gunter Zeilinger <gunterze@gmail.com> 064 * @since Feb 2015. 065 * 066 * @deprecated This is prototype code. StreamCompressor will be replaced by a Transcoder that supports both stream 067 * compression and decompression. For now you can continue using {@link Compressor} for non-stream compression. 068 */ 069@Deprecated 070public class StreamCompressor extends StreamDecompressor { 071 072 private static final Logger LOG = LoggerFactory.getLogger(StreamCompressor.class); 073 074 private TransferSyntaxType compressTsType; 075 private ImageWriter compressor; 076 private PatchJPEGLS compressPatchJPEGLS; 077 private ImageWriteParam compressParam; 078 private int maxPixelValueError = -1; 079 private int avgPixelValueBlockSize = 1; 080 private ImageReader verifier; 081 private ImageReadParam verifyParam; 082 private ImageParams imageParams; 083 private BufferedImage bi2; 084 private int frameIndex; 085 086 public StreamCompressor(DicomInputStream in, String inTransferSyntaxUID, DicomOutputStream out) { 087 super(in, inTransferSyntaxUID, out); 088 } 089 090 public boolean compress(String compressTsuid, Property... params) throws IOException { 091 if (compressTsuid == null) 092 throw new NullPointerException("compressTsuid"); 093 094 this.compressTsType = TransferSyntaxType.forUID(compressTsuid); 095 if (compressTsType == null) 096 throw new IllegalArgumentException("Unknown Transfer Syntax: " + compressTsuid); 097 098 ImageWriterFactory.ImageWriterParam param = 099 ImageWriterFactory.getImageWriterParam(compressTsuid); 100 if (param == null) 101 throw new UnsupportedOperationException( 102 "Unsupported Transfer Syntax: " + compressTsuid); 103 104 this.compressor = ImageWriterFactory.getImageWriter(param); 105 LOG.debug("Compressor: {}", compressor.getClass().getName()); 106 this.compressPatchJPEGLS = param.patchJPEGLS; 107 this.compressParam = compressor.getDefaultWriteParam(); 108 int count = 0; 109 for (Property property : cat(param.getImageWriteParams(), params)) { 110 String name = property.getName(); 111 if (name.equals("maxPixelValueError")) 112 this.maxPixelValueError = ((Number) property.getValue()).intValue(); 113 else if (name.equals("avgPixelValueBlockSize")) 114 this.avgPixelValueBlockSize = ((Number) property.getValue()).intValue(); 115 else { 116 if (count++ == 0) 117 compressParam.setCompressionMode( 118 ImageWriteParam.MODE_EXPLICIT); 119 property.setAt(compressParam); 120 } 121 } 122 123 if (maxPixelValueError >= 0) { 124 ImageReaderFactory.ImageReaderParam readerParam = 125 ImageReaderFactory.getImageReaderParam(compressTsuid); 126 if (readerParam == null) 127 throw new UnsupportedOperationException( 128 "Unsupported Transfer Syntax: " + compressTsuid); 129 130 this.verifier = ImageReaderFactory.getImageReader(readerParam); 131 this.verifyParam = verifier.getDefaultReadParam(); 132 LOG.debug("Verifier: {}", verifier.getClass().getName()); 133 } 134 decompress(); 135 return pixeldataProcessed; 136 } 137 138 @Override 139 public void dispose() { 140 super.dispose(); 141 if (compressor != null) 142 compressor.dispose(); 143 if (verifier != null) 144 verifier.dispose(); 145 } 146 147 private Property[] cat(Property[] a, Property[] b) { 148 if (a.length == 0) 149 return b; 150 if (b.length == 0) 151 return a; 152 Property[] c = new Property[a.length + b.length]; 153 System.arraycopy(a, 0, c, 0, a.length); 154 System.arraycopy(b, 0, c, a.length, b.length); 155 return c; 156 } 157 158 @Override 159 protected void onPixelData(DicomInputStream dis, Attributes attrs) throws IOException { 160 int tag = dis.tag(); 161 VR vr = dis.vr(); 162 int len = dis.length(); 163 BufferedImage bi = null; 164 this.imageParams = new ImageParams(dataset); 165 if (decompressor != null) 166 imageParams.decompress(attrs, tsType); 167 168 if (decompressor == null || tsType == TransferSyntaxType.RLE) 169 bi = BufferedImageUtils.createBufferedImage(imageParams, compressTsType); 170 171 imageParams.compress(attrs, compressTsType); 172 coerceAttributes.coerce(attrs).writeTo(out); 173 attrs.clear(); 174 out.writeHeader(Tag.PixelData, VR.OB, -1); 175 out.writeHeader(Tag.Item, null, 0); 176 177 if (len == -1) { 178 if (!tsType.isPixeldataEncapsulated()) { 179 throw new IOException("Unexpected encapsulated Pixel Data"); 180 } 181 decompressFrames(dis, imageParams, bi); 182 } else { 183 if (tsType.isPixeldataEncapsulated()) 184 throw new IOException("Pixel Data not encapsulated"); 185 int padding = len - imageParams.getLength(); 186 if (padding < 0) 187 throw new IllegalArgumentException( 188 "Pixel data too short: " + len + " instead " 189 + imageParams.getLength() + " bytes"); 190 191 byte[] buf = null; 192 DataBuffer dataBuffer = bi.getRaster().getDataBuffer(); 193 if (dataBuffer.getDataType() != DataBuffer.TYPE_BYTE) 194 buf = new byte[imageParams.getFrameLength()]; 195 196 for (int i = 0; i < imageParams.getFrames(); i++) { 197 readFrame(dis, dataBuffer, buf); 198 writeFrame(bi); 199 } 200 dis.skipFully(padding); 201 } 202 out.writeHeader(Tag.SequenceDelimitationItem, null, 0); 203 pixeldataProcessed = true; 204 } 205 206 private void readFrame(DicomInputStream dis, DataBuffer db, byte[] buf) throws IOException { 207 switch (db.getDataType()) { 208 case DataBuffer.TYPE_BYTE: 209 byte[][] data = ((DataBufferByte) db).getBankData(); 210 for (byte[] bs : data) 211 dis.readFully(bs); 212 if (dis.bigEndian() && dis.vr() == VR.OW) 213 ByteUtils.swapShorts(data); 214 break; 215 case DataBuffer.TYPE_USHORT: 216 dis.readFully(buf); 217 ByteUtils.bytesToShorts(buf, 0, ((DataBufferUShort) db).getData(), 0, buf.length >> 1, dis.bigEndian()); 218 break; 219 case DataBuffer.TYPE_SHORT: 220 dis.readFully(buf); 221 ByteUtils.bytesToShorts(buf, 0, ((DataBufferShort) db).getData(), 0, buf.length >> 1, dis.bigEndian()); 222 break; 223 default: 224 throw new UnsupportedOperationException("Unsupported Datatype: " + db.getDataType()); 225 } 226 } 227 228 @Override 229 protected void writeFrame(BufferedImage bi) throws IOException { 230 if (imageParams.getBitsStored() < imageParams.getBitsAllocated()) 231 BufferedImageUtils.nullifyUnusedBits(imageParams.getBitsStored(), bi.getRaster().getDataBuffer()); 232 233 MemoryCacheImageOutputStream compressedFrame = new MemoryCacheImageOutputStream(out) { 234 235 @Override 236 public void flush() throws IOException { 237 // defer flush to close() 238 LOG.debug("Ignore invoke of MemoryCacheImageOutputStream.flush()"); 239 } 240 }; 241 compressor.setOutput(compressPatchJPEGLS != null 242 ? new PatchJPEGLSImageOutputStream(compressedFrame, compressPatchJPEGLS) 243 : compressedFrame); 244 long start = System.currentTimeMillis(); 245 compressor.write(null, new IIOImage(bi, null, null), compressParam); 246 long end = System.currentTimeMillis(); 247 int streamLength = (int) compressedFrame.getStreamPosition(); 248 if (LOG.isDebugEnabled()) 249 LOG.debug("Compressed frame #{} {}:1 in {} ms", 250 frameIndex+1, (float) BufferedImageUtils.sizeOf(bi) / streamLength, end - start); 251 verify(compressedFrame, bi); 252 out.writeHeader(Tag.Item, null, (streamLength + 1) & ~1); 253 start = System.currentTimeMillis(); 254 compressedFrame.close(); 255 if ((streamLength & 1) != 0) 256 out.write(0); 257 end = System.currentTimeMillis(); 258 LOG.debug("Flushed frame #{} from memory in {} ms", frameIndex+1, start - end); 259 frameIndex++; 260 } 261 262 private void verify(ImageInputStream iis, BufferedImage bi) throws IOException { 263 if (verifier == null) 264 return; 265 266 iis.seek(0); 267 verifier.setInput(iis); 268 verifyParam.setDestination(bi2); 269 long start = System.currentTimeMillis(); 270 bi2 = verifier.read(0, verifyParam); 271 int maxDiff = BufferedImageUtils.maxDiff(bi.getRaster(), bi2.getRaster(), avgPixelValueBlockSize); 272 long end = System.currentTimeMillis(); 273 LOG.debug("Verified compressed frame #{} in {} ms - max pixel value error: {}", 274 frameIndex+1, end - start, maxDiff); 275 if (maxDiff > maxPixelValueError) 276 throw new CompressionVerificationException(maxDiff); 277 } 278}