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}