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.imageio.codec;
040
041import org.dcm4che3.conf.core.api.ConfigurableClass;
042import org.dcm4che3.conf.core.api.ConfigurableProperty;
043import org.dcm4che3.conf.core.api.LDAP;
044import org.dcm4che3.data.UID;
045import org.dcm4che3.imageio.codec.jpeg.PatchJPEGLS;
046import org.dcm4che3.util.ResourceLocator;
047import org.dcm4che3.util.SafeClose;
048import org.dcm4che3.util.StringUtils;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import javax.imageio.ImageIO;
053import javax.imageio.ImageReader;
054import java.io.File;
055import java.io.IOException;
056import java.io.InputStream;
057import java.io.Serializable;
058import java.net.MalformedURLException;
059import java.net.URL;
060import java.util.Iterator;
061import java.util.Map;
062import java.util.Map.Entry;
063import java.util.Properties;
064import java.util.TreeMap;
065
066/**
067 * Provides Image Readers for different DICOM transfer syntaxes and MIME types.
068 * 
069 * @author Gunter Zeilinger <gunterze@gmail.com>
070 * @author Hermann Czedik-Eysenberg <hermann-agfa@czedik.net>
071 */
072@LDAP(objectClasses = "dcmImageReaderFactory")
073@ConfigurableClass
074public class ImageReaderFactory implements Serializable {
075
076    private static final Logger LOG = LoggerFactory.getLogger(ImageReaderFactory.class);
077
078    private static final long serialVersionUID = -2881173333124498212L;
079
080    @LDAP(objectClasses = "dcmImageReader")
081    @ConfigurableClass
082    public static class ImageReaderParam implements Serializable {
083
084        private static final long serialVersionUID = 6593724836340684578L;
085
086        @ConfigurableProperty(name = "dcmIIOFormatName")
087        public String formatName;
088
089        @ConfigurableProperty(name = "dcmJavaClassName")
090        public String className;
091
092        @ConfigurableProperty(name = "dcmPatchJPEGLS")
093        public PatchJPEGLS patchJPEGLS;
094
095        public ImageReaderParam() {
096        }
097
098        public ImageReaderParam(String formatName, String className,
099                String patchJPEGLS) {
100            this.formatName = formatName;
101            this.className = nullify(className);
102            this.patchJPEGLS = patchJPEGLS != null && !patchJPEGLS.isEmpty() ? PatchJPEGLS
103                    .valueOf(patchJPEGLS) : null;
104        }
105
106        public String getFormatName() {
107            return formatName;
108        }
109
110        public void setFormatName(String formatName) {
111            this.formatName = formatName;
112        }
113
114        public String getClassName() {
115            return className;
116        }
117
118        public void setClassName(String className) {
119            this.className = className;
120        }
121
122        public PatchJPEGLS getPatchJPEGLS() {
123            return patchJPEGLS;
124        }
125
126        public void setPatchJPEGLS(PatchJPEGLS patchJPEGLS) {
127            this.patchJPEGLS = patchJPEGLS;
128        }
129    }
130
131    private static String nullify(String s) {
132        return s == null || s.isEmpty() || s.equals("*") ? null : s;
133    }
134
135    private static ImageReaderFactory defaultFactory;
136
137    @LDAP(distinguishingField = "dicomTransferSyntax", noContainerNode = true)
138    @ConfigurableProperty(
139            name="dicomImageReaderMap",
140            label = "Image Readers by Transfer Syntax",
141            description = "Image readers by Transfer Syntax"
142    )
143    private Map<String, ImageReaderParam> mapTransferSyntaxUIDs = new TreeMap<String, ImageReaderParam>();
144    
145    @ConfigurableProperty(
146            name="dicomImageReaderMapMime",
147            label = "Image Readers by MIME type",
148            description = "Image readers by MIME type"
149    )
150    private Map<String, ImageReaderParam> mapMimeTypes = new TreeMap<String, ImageReaderParam>();
151
152    public Map<String, ImageReaderParam> getMapTransferSyntaxUIDs() {
153        return mapTransferSyntaxUIDs;
154    }
155
156    public void setMapTransferSyntaxUIDs(Map<String, ImageReaderParam> mapTransferSyntaxUIDs) {
157        this.mapTransferSyntaxUIDs = mapTransferSyntaxUIDs;
158    }
159
160    public Map<String, ImageReaderParam> getMapMimeTypes() {
161        return mapMimeTypes;
162    }
163
164    public void setMapMimeTypes(Map<String, ImageReaderParam> mapMimeTypes) {
165        this.mapMimeTypes = mapMimeTypes;
166    }
167
168    public static ImageReaderFactory getDefault() {
169        if (defaultFactory == null)
170            defaultFactory = initDefault();
171
172        return defaultFactory;
173    }
174
175    public static void resetDefault() {
176        defaultFactory = null;
177    }
178
179    public static void setDefault(ImageReaderFactory factory) {
180        if (factory == null)
181            throw new NullPointerException();
182
183        defaultFactory = factory;
184    }
185
186    private static ImageReaderFactory initDefault() {
187        ImageReaderFactory factory = new ImageReaderFactory();
188        String name = System.getProperty(ImageReaderFactory.class.getName(),
189                "org/dcm4che3/imageio/codec/ImageReaderFactory.properties");
190        try {
191            factory.load(name);
192        } catch (Exception e) {
193            throw new RuntimeException(
194                    "Failed to load Image Reader Factory configuration from: "
195                            + name, e);
196        }
197
198        factory.init();
199
200        return factory;
201    }
202
203    public void init() {
204        if (LOG.isDebugEnabled()) {
205            StringBuilder sb = new StringBuilder();
206            sb.append("Image Readers:\n");
207            for (Entry<String, ImageReaderParam> entry : mapTransferSyntaxUIDs.entrySet()) {
208                String tsUid = entry.getKey();
209                sb.append(' ').append(tsUid);
210                sb.append(" (").append(UID.nameOf(tsUid)).append("): ");
211                sb.append(getImageReaderName(entry.getValue())).append('\n');
212            }
213            for (Entry<String, ImageReaderParam> entry : mapMimeTypes.entrySet()) {
214                sb.append(' ').append(entry.getKey()).append(": ");
215                sb.append(getImageReaderName(entry.getValue())).append('\n');
216            }
217            LOG.debug(sb.toString());
218        }
219    }
220
221    private String getImageReaderName(ImageReaderParam imageReaderParam) {
222        ImageReader imageReader = null;
223        try {
224            imageReader = getImageReader(imageReaderParam);
225        } catch (RuntimeException e) {
226            // none found
227        }
228        return imageReader != null ? imageReader.getClass().getName() : "null";
229    }
230
231    public void load(String name) throws IOException {
232        URL url;
233        try {
234            url = new URL(name);
235        } catch (MalformedURLException e) {
236            url = ResourceLocator.getResourceURL(name, this.getClass());
237            if (url == null) {
238                File f = new File(name);
239                if(f.exists() && f.isFile()) {
240                    url = f.toURI().toURL();
241                } else {
242                    throw new IOException("No such resource: " + name);
243                }
244            }
245        }
246        InputStream in = url.openStream();
247        try {
248            load(in);
249        } finally {
250            SafeClose.close(in);
251        }
252    }
253
254    public void load(InputStream in) throws IOException {
255        Properties props = new Properties();
256        props.load(in);
257        for (Map.Entry<Object, Object> entry : props.entrySet()) {
258            String key = (String) entry.getKey();
259
260            String[] ss = StringUtils.split((String) entry.getValue(), ':');
261            String formatName = ss[0];
262            String className = ss[1];
263            String patchJPEGLS = ss[2];
264
265            if (key.contains("/")) { // mime type
266                mapMimeTypes.put(key, new ImageReaderParam(formatName, className, patchJPEGLS));
267            } else { // transfer syntax uid
268                mapTransferSyntaxUIDs.put(key, new ImageReaderParam(formatName, className, patchJPEGLS));
269            }
270        }
271    }
272
273    private ImageReaderParam getForTransferSyntaxUID(String tsuid) {
274        return mapTransferSyntaxUIDs.get(tsuid);
275    }
276
277    private ImageReaderParam getForMimeType(String mimeType) {
278        return mapMimeTypes.get(mimeType);
279    }
280
281    private boolean containsTransferSyntaxUID(String tsuid) {
282        return mapTransferSyntaxUIDs.containsKey(tsuid);
283    }
284
285    public static ImageReaderParam getImageReaderParam(String tsuid) {
286        return getDefault().getForTransferSyntaxUID(tsuid);
287    }
288
289    public static boolean canDecompress(String tsuid) {
290        return getDefault().containsTransferSyntaxUID(tsuid);
291    }
292
293    public static ImageReader getImageReader(ImageReaderParam param) {
294
295        Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(param.formatName);
296
297        while (readers.hasNext()) {
298            ImageReader reader = readers.next();
299
300            if (param.className == null || param.className.equals(reader.getClass().getName())) {
301                LOG.debug("Using Image Reader {}", reader.getClass());
302                return reader;
303            }
304        }
305
306        throw new RuntimeException("No matching Image Reader for format: " + param.formatName + " (Class: " + ((param.className == null) ? "*" : param.className) + ") registered");
307    }
308
309    public static ImageReader getImageReaderForMimeType(String mimeType) {
310        ImageReaderParam imageReaderParam = getDefault().getForMimeType(mimeType);
311
312        if (imageReaderParam != null) {
313            // configured mime type
314            return getImageReader(imageReaderParam);
315        } else {
316            // not configured mime type, fallback to first ImageIO reader for this mime type
317            ImageReader reader = ImageIO.getImageReadersByMIMEType(mimeType).next();
318            LOG.debug("Using Image Reader {}", reader.getClass());
319            return reader;
320        }
321    }
322
323}