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.*; 043import org.dcm4che3.imageio.codec.jpeg.PatchJPEGLS; 044import org.dcm4che3.imageio.codec.jpeg.PatchJPEGLSImageInputStream; 045import org.dcm4che3.imageio.stream.SegmentedImageInputStream; 046import org.dcm4che3.io.DicomInputHandler; 047import org.dcm4che3.io.DicomInputStream; 048import org.dcm4che3.io.DicomOutputStream; 049import org.dcm4che3.util.ByteUtils; 050import org.dcm4che3.util.StreamUtils; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import javax.imageio.ImageReadParam; 055import javax.imageio.ImageReader; 056import javax.imageio.stream.MemoryCacheImageInputStream; 057import java.awt.image.*; 058import java.io.IOException; 059 060/** 061 * @author Gunter Zeilinger <gunterze@gmail.com> 062 * @since Jan 2015. 063 * 064 * @deprecated This is prototype code. StreamDecompressor will be replaced by a Transcoder that supports both stream 065 * compression and decompression. For now you can continue using {@link Decompressor} for non-stream decompression. 066 */ 067@Deprecated 068public class StreamDecompressor implements CoerceAttributes { 069 070 private static final Logger LOG = LoggerFactory.getLogger(StreamDecompressor.class); 071 072 protected final DicomInputStream in; 073 protected final DicomOutputStream out; 074 protected final String tsuid; 075 protected final TransferSyntaxType tsType; 076 protected final Attributes dataset; 077 protected ImageReader decompressor; 078 protected PatchJPEGLS patchJPEGLS; 079 protected boolean pixeldataProcessed; 080 protected CoerceAttributes coerceAttributes = this; 081 082 public StreamDecompressor(DicomInputStream in, String tsuid, DicomOutputStream out) { 083 this.in = in; 084 this.out = out; 085 this.tsuid = tsuid; 086 this.tsType = TransferSyntaxType.forUID(tsuid); 087 if (tsType == null) 088 throw new IllegalArgumentException("Unknown Transfer Syntax: " + tsuid); 089 if (tsType.isPixeldataEncapsulated()) { 090 ImageReaderFactory.ImageReaderParam param = ImageReaderFactory.getImageReaderParam(tsuid); 091 if (param == null) 092 throw new IllegalArgumentException("Unsupported Transfer Syntax: " + tsuid); 093 this.decompressor = ImageReaderFactory.getImageReader(param); 094 LOG.debug("Decompressor: {}", decompressor.getClass().getName()); 095 this.patchJPEGLS = param.getPatchJPEGLS(); 096 } 097 this.dataset = new Attributes(in.bigEndian(), 64); 098 } 099 100 public CoerceAttributes getCoerceAttributes() { 101 return coerceAttributes; 102 } 103 104 public void setCoerceAttributes(CoerceAttributes coerceAttributes) { 105 if (coerceAttributes == null) 106 throw new NullPointerException(); 107 108 this.coerceAttributes = coerceAttributes; 109 } 110 111 @Override 112 public Attributes coerce(Attributes attrs) { 113 return attrs; 114 } 115 116 public boolean decompress() throws IOException { 117 in.setDicomInputHandler(handler); 118 in.readAttributes(dataset, -1, -1); 119 (pixeldataProcessed ? dataset : coerceAttributes.coerce(dataset)).writeTo(out); 120 return pixeldataProcessed && decompressor != null; 121 } 122 123 public void dispose() { 124 if (decompressor != null) 125 decompressor.dispose(); 126 } 127 128 protected void onPixelData(DicomInputStream dis, Attributes attrs) throws IOException { 129 int tag = dis.tag(); 130 VR vr = dis.vr(); 131 int len = dis.length(); 132 if (len == -1) { 133 if (!tsType.isPixeldataEncapsulated()) { 134 throw new IOException("Unexpected encapsulated Pixel Data"); 135 } 136 BufferedImage bi = null; 137 ImageParams imageParams = new ImageParams(dataset); 138 imageParams.decompress(attrs, tsType); 139 if (tsType == TransferSyntaxType.RLE) 140 bi = BufferedImageUtils.createBufferedImage(imageParams, null); 141 coerceAttributes.coerce(attrs).writeTo(out); 142 attrs.clear(); 143 out.writeHeader(Tag.PixelData, VR.OW, imageParams.getEncodedLength()); 144 decompressFrames(dis, imageParams, bi); 145 if (imageParams.paddingNull()) 146 out.write(0); 147 } else { 148 if (tsType.isPixeldataEncapsulated()) 149 throw new IOException("Pixel Data not encapsulated"); 150 coerceAttributes.coerce(attrs).writeTo(out); 151 attrs.clear(); 152 out.writeHeader(tag, vr, len); 153 StreamUtils.copy(dis, out, len); 154 } 155 pixeldataProcessed = true; 156 } 157 158 protected void decompressFrames(DicomInputStream dis, ImageParams imageParams, BufferedImage bi) 159 throws IOException { 160 dis.readHeader(); 161 dis.skipFully(dis.length()); 162 long pos = dis.getPosition(); 163 MemoryCacheImageInputStream iis = new MemoryCacheImageInputStream(dis); 164 byte[] header = new byte[8]; 165 boolean singleFrame = imageParams.getFrames() == 1; 166 for (int i = 0; i < imageParams.getFrames(); i++) { 167 iis.readFully(header); 168 SegmentedImageInputStream siis = new SegmentedImageInputStream( 169 iis, iis.getStreamPosition(), ByteUtils.bytesToIntLE(header, 4), singleFrame); 170 decompressor.setInput(patchJPEGLS != null 171 ? new PatchJPEGLSImageInputStream(siis, patchJPEGLS) 172 : siis); 173 ImageReadParam readParam = decompressor.getDefaultReadParam(); 174 readParam.setDestination(bi); 175 long start = System.currentTimeMillis(); 176 bi = decompressor.read(0, readParam); 177 long lastSegmentEnd = siis.getLastSegmentEnd(); 178 iis.seek(lastSegmentEnd); 179 iis.flushBefore(lastSegmentEnd); 180 long end = System.currentTimeMillis(); 181 if (LOG.isDebugEnabled()) 182 LOG.debug("Decompressed frame #{} 1:{} in {} ms", 183 i + 1, (float) BufferedImageUtils.sizeOf(bi) / siis.getStreamPosition(), end - start ); 184 writeFrame(bi); 185 } 186 iis.readFully(header); 187 dis.setPosition(pos + iis.getStreamPosition()); 188 } 189 190 protected void writeFrame(BufferedImage bi) throws IOException { 191 BufferedImageUtils.writeTo(bi, out); 192 } 193 194 private final DicomInputHandler handler = new DicomInputHandler() { 195 @Override 196 public void readValue(DicomInputStream dis, Attributes attrs) throws IOException { 197 if (dis.tag() == Tag.PixelData && dis.level() == 0) 198 onPixelData(dis, attrs); 199 else 200 dis.readValue(dis, attrs); 201 } 202 203 @Override 204 public void readValue(DicomInputStream dis, Sequence seq) throws IOException { 205 dis.readValue(dis, seq); 206 } 207 208 @Override 209 public void readValue(DicomInputStream dis, Fragments frags) throws IOException { 210 throw new UnsupportedOperationException(); 211 } 212 213 @Override 214 public void startDataset(DicomInputStream dis) throws IOException {} 215 216 @Override 217 public void endDataset(DicomInputStream dis) throws IOException {} 218 }; 219}