Java tutorial
/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at http://sourceforge.net/projects/dcm4che. * * The Initial Developer of the Original Code is * Gunter Zeilinger, Huetteldorferstr. 24/10, 1150 Vienna/Austria/Europe. * Portions created by the Initial Developer are Copyright (C) 2002-2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Gunter Zeilinger <gunterze@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4che2.tool.dcmwado; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingOptionException; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dcm4che2.data.BasicDicomObject; import org.dcm4che2.data.DicomObject; import org.dcm4che2.data.Tag; import org.dcm4che2.io.DicomInputStream; import org.dcm4che2.io.StopTagInputHandler; import org.dcm4che2.util.CloseUtils; /** * @author gunter zeilinger(gunterze@gmail.com) * @version $Revision: 13911 $ $Date: 2010-08-20 10:12:53 +0200 (Fri, 20 Aug 2010) $ * @since Oct 13, 2005 */ public class DcmWado { private static final int KB = 1024; private static final int MB = KB * KB; private static final String USAGE = "dcmwado [Options] <base-url> (-uid <uids>) or (<file>|<directory>[...])"; private static final String DESCRIPTION = "Invokes single or multiple HTTP GET request(s) according DICOM Part 18: " + "Web Access to DICOM Persistent Objects (WADO) on WADO server specified " + "by <base-url>.\n\n" + "Options:"; private static final String EXAMPLE = "\nExample 1: dcmwado http://localhost:8080/wado -dcm \\" + "\n -uid 1.2.3.4:1.2.3.4.5:1.2.3.4.5.6 -dir /tmp/wado \n" + "=> Request single DICOM Object with specified uids from the local WADO " + "server listening on port 8080, and store it in directory /tmp/wado" + "\nExample 2: dcmwado http://localhost:8080/wado -dcm \\" + "\n /cdrom/DICOM -nostore\n" + "=> Scan all DICOM files under directory /cdrom/DICOM and request for " + "each file the corresponding DICOM Object from the local WADO " + "server listening on port 8080, without storing the response to disk."; private String baseurl; private String requestType = "WADO"; private String[] psuid; private String[] tsuid; private boolean tsfile = false; private ArrayList<String> contentType = new ArrayList<String>(); private String[] charset; private boolean anonymize = false; private String[] annotation; private int rows; private int columns; private int frameNumber; private String[] region; private String[] window; private int imageQuality; private boolean noKeepAlive; private boolean followsRedirect = true; private File dir = new File("."); private File outfile; private ArrayList<String[]> uids = new ArrayList<String[]>(); private byte[] buffer = new byte[8 * KB]; private long totalSize = 0L; private static CommandLine parse(String[] args) { Options opts = new Options(); OptionBuilder.withArgName("suid:Suid:iuid"); OptionBuilder.hasArgs(3); OptionBuilder.withValueSeparator(':'); OptionBuilder.withDescription( "Retrieve object with given Study " + "Instance UID, Series Instance UID and SOP Instance UID."); opts.addOption(OptionBuilder.create("uid")); OptionBuilder.withArgName("Suid:iuid"); OptionBuilder.hasArgs(2); OptionBuilder.withValueSeparator(':'); OptionBuilder.withDescription("Series Instance UID and SOP Instance UID " + "of the presentation state storage object to be applied to the " + "image."); opts.addOption(OptionBuilder.create("pr")); opts.addOption("dcm", false, "Request DICOM object. (MIME type: application/dicom)"); opts.addOption("jpeg", false, "Request JPEG image. (MIME type: image/jpeg)"); opts.addOption("gif", false, "Request GIF image. (MIME type: image/gif)"); opts.addOption("png", false, "Request PNG image. (MIME type: image/png)"); opts.addOption("jp2", false, "Request JPEG 2000 image. (MIME type: image/jp2)"); opts.addOption("mpeg", false, "Request MPEG video. (MIME type: video/mpeg)"); opts.addOption("txt", false, "Request plain text document. (MIME type: text/plain)"); opts.addOption("html", false, "Request HTML document. (MIME type: text/html)"); opts.addOption("xml", false, "Request XML document. (MIME type: text/xml)"); opts.addOption("rtf", false, "Request RTF document. (MIME type: text/rtf)"); opts.addOption("pdf", false, "Request PDF document. (MIME type: application/pdf)"); opts.addOption("cda1", false, "Request CDA Level 1 document. " + "(MIME type: application/x-hl7-cda-level-one+xml)"); OptionBuilder.withArgName("type"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Request document with the specified MIME type." + "Alternative MIME types can be specified by additional -mime options."); opts.addOption(OptionBuilder.create("mime")); OptionBuilder.withArgName("uid"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Returned object shall be encoded with " + "the specified Transfer Syntax. Alternative Transfer Syntaxes " + "can be specified by additional -ts options."); opts.addOption(OptionBuilder.create("ts")); OptionBuilder.withArgName("name"); OptionBuilder.hasArg(); OptionBuilder.withDescription( "Returned object shall be encoded with " + "specified Character set. Alternative Character sets " + "can be specified by additional -charset options."); opts.addOption(OptionBuilder.create("charset")); opts.addOption("anonymize", false, "Remove all patient identification" + "information from returned DICOM Object"); OptionBuilder.withArgName("type"); OptionBuilder.hasArg(); OptionBuilder.withDescription( "Burn in patient information" + "(-annotation=patient) and/or technique information " + "(-annotation=technique) in returned pixel data."); opts.addOption(OptionBuilder.create("annotation")); OptionBuilder.withArgName("num"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Maximal number of pixel rows in returned image."); opts.addOption(OptionBuilder.create("rows")); OptionBuilder.withArgName("num"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Maximal number of pixel columns in returned image."); opts.addOption(OptionBuilder.create("columns")); OptionBuilder.withArgName("num"); OptionBuilder.hasArg(); OptionBuilder .withDescription("Return single frame with that number " + "within a multi-frame image object."); opts.addOption(OptionBuilder.create("frame")); OptionBuilder.withArgName("x1:y1:x2:y2"); OptionBuilder.hasArgs(4); OptionBuilder.withValueSeparator(':'); OptionBuilder.withDescription("Return rectangular region of image " + "matrix specified by top left (x1,y1) and bottom right (x2,y2) " + "corner in relative coordinates within the range 0.0 to 1.0."); opts.addOption(OptionBuilder.create("window")); OptionBuilder.withArgName("center/width"); OptionBuilder.hasArgs(2); OptionBuilder.withValueSeparator('/'); OptionBuilder .withDescription("Specifies center and width of the " + "VOI window to be applied to the image."); opts.addOption(OptionBuilder.create("window")); OptionBuilder.withArgName("num"); OptionBuilder.hasArg(); OptionBuilder.withDescription( "Quality of the image to be returned " + "within the range 1 to 100, 100 being the best quality."); opts.addOption(OptionBuilder.create("quality")); OptionBuilder.withArgName("path"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Directory to store retrieved objects, " + "working directory by default"); opts.addOption(OptionBuilder.create("dir")); OptionBuilder.withArgName("dirpath"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Directory to store retrieved objects, " + "working directory by default"); opts.addOption(OptionBuilder.create("dir")); OptionBuilder.withArgName("file"); OptionBuilder.hasArg(); OptionBuilder.withDescription("Store retrieved object to specified file, " + "use SOP Instance UID + format specific file extension as " + "file name by default."); opts.addOption(OptionBuilder.create("o")); opts.addOption("nostore", false, "Do not store retrieved objects to files."); opts.addOption("nokeepalive", false, "Close TCP connection after each response."); opts.addOption("noredirect", false, "Disable HTTP redirects."); OptionBuilder.withArgName("kB"); OptionBuilder.hasArg(); OptionBuilder.withDescription( "Size of byte buffer in KB " + "used for copying the retrieved object to disk, 8 KB by default."); opts.addOption(OptionBuilder.create("buffersize")); opts.addOption("h", "help", false, "print this message"); opts.addOption("V", "version", false, "print the version information and exit"); CommandLine cl = null; try { cl = new GnuParser().parse(opts, args); } catch (MissingOptionException e) { exit("dcmwado: Missing required option " + e.getMessage()); throw new RuntimeException("unreachable"); } catch (ParseException e) { exit("dcmwado: " + e.getMessage()); throw new RuntimeException("unreachable"); } if (cl.hasOption('V')) { Package p = DcmWado.class.getPackage(); System.out.println("dcmwado v" + p.getImplementationVersion()); System.exit(0); } if (cl.hasOption('h') || cl.getArgList().isEmpty()) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp(USAGE, DESCRIPTION, opts, EXAMPLE); System.exit(0); } int narg = cl.getArgList().size(); if (narg == 0) exit("Missing url of WADO server"); if (narg == 1) { if (!cl.hasOption("uid")) { exit("You must either option -uid <uids> or <file>|<directory> specify"); } } else { if (cl.hasOption("uid")) { exit("You may not specify option -uid <uids> together with " + "<file>|<directory>."); } } return cl; } private static int parseInt(String s, String errPrompt, int min, int max) { try { int i = Integer.parseInt(s); if (i >= min && i <= max) return i; } catch (NumberFormatException e) { // parameter is not a valid integer; fall through to exit } throw new IllegalArgumentException(errPrompt); } private static void checkFloats(String[] a, String errPrompt, int size, float min, float max) { if (a.length != size) { throw new IllegalArgumentException(errPrompt); } for (int i = 0; i < a.length; i++) { parseFloat(a[i], errPrompt, min, max); } } private static float parseFloat(String s, String errPrompt, float min, float max) { try { float f = Integer.parseInt(s); if (f >= min && f <= max) return f; } catch (NumberFormatException e) { // parameter is not a valid integer; fall through to exit } throw new IllegalArgumentException(errPrompt); } public static void main(String[] args) { DcmWado dcmwado = new DcmWado(); try { CommandLine cl = parse(args); if (cl.hasOption("pr")) { dcmwado.setPresentation(cl.getOptionValues("pr")); } if (cl.hasOption("dcm")) { dcmwado.addContentType("application/dicom"); } if (cl.hasOption("jpeg")) { dcmwado.addContentType("image/jpeg"); } if (cl.hasOption("gif")) { dcmwado.addContentType("image/gif)"); } if (cl.hasOption("png")) { dcmwado.addContentType("image/png"); } if (cl.hasOption("jp2")) { dcmwado.addContentType("image/jp2"); } if (cl.hasOption("mpeg")) { dcmwado.addContentType("video/mpeg"); } if (cl.hasOption("txt")) { dcmwado.addContentType("text/plain"); } if (cl.hasOption("html")) { dcmwado.addContentType("text/html"); } if (cl.hasOption("xml")) { dcmwado.addContentType("text/xml"); } if (cl.hasOption("rtf")) { dcmwado.addContentType("text/rtf"); } if (cl.hasOption("pdf")) { dcmwado.addContentType("application/pdf"); } if (cl.hasOption("cda1")) { dcmwado.addContentType("application/x-hl7-cda-level-one+xml"); } if (cl.hasOption("mime")) { dcmwado.addContentType(cl.getOptionValues("mime")); } if (cl.hasOption("ts")) { dcmwado.setTransferSyntax(cl.getOptionValues("ts")); } dcmwado.setTransferSyntaxSameAsFile(cl.hasOption("tsfile")); if (cl.hasOption("charset")) { dcmwado.setCharset(cl.getOptionValues("charset")); } dcmwado.setAnonymize(cl.hasOption("anonymize")); if (cl.hasOption("annotation")) { dcmwado.setAnnotation(cl.getOptionValues("annotation")); } if (cl.hasOption("rows")) { dcmwado.setRows(parseInt(cl.getOptionValue("h"), "Invalid value of -h", 1, Integer.MAX_VALUE)); } if (cl.hasOption("columns")) { dcmwado.setColumns(parseInt(cl.getOptionValue("w"), "Invalid value of -w", 1, Integer.MAX_VALUE)); } if (cl.hasOption("frame")) { dcmwado.setFrameNumber( parseInt(cl.getOptionValue("f"), "Invalid value of -f", 1, Integer.MAX_VALUE)); } if (cl.hasOption("region")) { dcmwado.setRegion(cl.getOptionValues("reg")); } if (cl.hasOption("window")) { dcmwado.setWindow(cl.getOptionValues("voi")); } if (cl.hasOption("quality")) { dcmwado.setImageQuality( parseInt(cl.getOptionValue("q"), "Invalid value of -q", 1, Integer.MAX_VALUE)); } if (cl.hasOption("dir")) { dcmwado.setDirectory(new File(cl.getOptionValue("dir"))); } if (cl.hasOption("o")) { dcmwado.setOutput(new File(cl.getOptionValue("o"))); } if (cl.hasOption("nostore")) { dcmwado.setDirectory(null); } dcmwado.setNoKeepAlive(cl.hasOption("nokeepalive")); dcmwado.setFollowsRedirect(!cl.hasOption("noredirect")); if (cl.hasOption("buffersize")) { dcmwado.setBufferSize(parseInt(cl.getOptionValue("bs"), "Invalid value of -bs", 1, 1000) * KB); } List argList = cl.getArgList(); dcmwado.setBaseURL((String) argList.get(0)); if (cl.hasOption("uid")) { dcmwado.setUIDs(cl.getOptionValues("uid")); } else { System.out.println("Scanning files for uids"); long t1 = System.currentTimeMillis(); for (int i = 1, n = argList.size(); i < n; i++) { dcmwado.addFile(new File((String) argList.get(i))); } long t2 = System.currentTimeMillis(); System.out.println( "\nScanned " + dcmwado.getNumberOfRequests() + " files in " + ((t2 - t1) / 1000F) + "s"); } } catch (Exception e) { exit(e.getMessage()); } long t1 = System.currentTimeMillis(); dcmwado.fetchObjects(); long t2 = System.currentTimeMillis(); float seconds = (t2 - t1) / 1000F; System.out.println( "\nFetch " + dcmwado.getNumberOfRequests() + " objects (=" + promptBytes(dcmwado.getTotalSize()) + ") in " + seconds + "s (=" + promptBytes(dcmwado.getTotalSize() / seconds) + "/s)"); } private static String promptBytes(float totalSizeSent) { return (totalSizeSent > MB) ? ("" + (totalSizeSent / MB) + "MB") : ("" + (totalSizeSent / KB) + "KB"); } private static void exit(String msg) { System.err.println(msg); System.err.println("Try 'dcmwado -h' for more information."); System.exit(1); } private void setBufferSize(int size) { buffer = new byte[size]; } public final void setDirectory(File dir) { if (dir != null && dir.mkdirs()) System.out.println("INFO: Create directory " + dir); this.dir = dir; } public final void setOutput(File file) { this.outfile = file; } public final void setAnnotation(String[] annotation) { this.annotation = annotation; } public final void setAnonymize(boolean anonymize) { this.anonymize = anonymize; } public final void setBaseURL(String url) { checkURL(url); this.baseurl = url; } private void checkURL(String url) { try { String protocol = new URL(url).getProtocol(); if (!"http".equals(protocol) && !"https".equals(protocol)) { throw new IllegalArgumentException("Illegal base-url - " + url); } } catch (MalformedURLException e) { throw new IllegalArgumentException("Illegal base-url - " + url, e); } } public final void setCharset(String[] charset) { this.charset = charset; } public final void setColumns(int columns) { this.columns = columns; } public final void addContentType(String contentType) { this.contentType.add(contentType); } public final void addContentType(String[] contentType) { this.contentType.addAll(Arrays.asList(contentType)); } public final void setFrameNumber(int frameNumber) { this.frameNumber = frameNumber; } public final void setImageQuality(int imageQuality) { this.imageQuality = imageQuality; } public final int getNumberOfRequests() { return uids.size(); } public final void setPresentation(String[] psuid) { if (psuid.length != 2) throw new IllegalArgumentException("Illegal argument for -pr"); this.psuid = psuid; } public final void setRegion(String[] region) { checkFloats(region, "Illegal argument for -reg", 4, 0.f, 1.f); this.region = region; } public final void setRows(int rows) { this.rows = rows; } public final void setTransferSyntax(String[] tsuids) { this.tsuid = tsuids; } public final void setTransferSyntaxSameAsFile(boolean tsfile) { this.tsfile = tsfile; } public final void setFollowsRedirect(boolean followsRedirect) { this.followsRedirect = followsRedirect; } public final void setNoKeepAlive(boolean noKeepAlive) { this.noKeepAlive = noKeepAlive; } public void setUIDs(String[] uid) { uids.add(uid); } public final void setWindow(String[] window) { if (window.length != 2) throw new IllegalArgumentException("Illegal argument for -voi"); parseFloat(window[0], "Illegal argument for -voi", Float.MIN_VALUE, Float.MAX_VALUE); parseFloat(window[1], "Illegal argument for -voi", 0, Float.MAX_VALUE); this.window = window; } public final long getTotalSize() { return totalSize; } public void addFile(File f) { if (f.isDirectory()) { File[] fs = f.listFiles(); for (int i = 0; i < fs.length; i++) addFile(fs[i]); return; } DicomObject dcmObj = new BasicDicomObject(); DicomInputStream in = null; try { in = new DicomInputStream(f); in.setHandler(new StopTagInputHandler(Tag.StudyID));//use StudyID to have seriesIUID included! in.readDicomObject(dcmObj, -1); String[] uid = new String[tsfile ? 4 : 3]; uid[0] = dcmObj.getString(Tag.StudyInstanceUID); uid[1] = dcmObj.getString(Tag.SeriesInstanceUID); uid[2] = dcmObj.getString(Tag.SOPInstanceUID); if (tsfile) { uid[3] = in.getTransferSyntax().uid(); } uids.add(uid); } catch (IOException e) { e.printStackTrace(); System.err.println("WARNING: Failed to parse " + f + " - skipped."); System.out.print('F'); return; } finally { CloseUtils.safeClose(in); } System.out.print('.'); } public void fetchObjects() { for (Iterator<String[]> iter = uids.iterator(); iter.hasNext();) { fetch(iter.next()); } } private void fetch(String[] uids) { URL url = makeURL(uids); try { HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setInstanceFollowRedirects(followsRedirect); con.setRequestProperty("Connection", "Keep-Alive"); con.connect(); InputStream in = null; OutputStream out = null; try { in = con.getInputStream(); if (dir != null) { out = new FileOutputStream( outfile != null ? (outfile.isAbsolute() ? outfile : new File(dir, outfile.getPath())) : new File(dir, uids[2] + toFileExt(con.getContentType()))); } copy(in, out); } finally { CloseUtils.safeClose(out); CloseUtils.safeClose(in); if (noKeepAlive) con.disconnect(); } System.out.print('.'); } catch (Exception e) { System.err.println("ERROR: Failed to GET " + url + " - " + e.getMessage()); e.printStackTrace(); System.out.print('F'); } } private static String toFileExt(String mimeType) { if ("image/jpeg".equals(mimeType)) return ".jpeg"; if ("application/dicom".equals(mimeType)) return ".dcm"; if ("text/html".equals(mimeType)) return ".html"; if ("application/xhtml+xml".equals(mimeType)) return ".xhtml"; if ("text/xml".equals(mimeType)) return ".xml"; if ("text/plain".equals(mimeType)) return ".txt"; if ("video/mpeg".equals(mimeType)) return ".mpeg"; return ""; } private void copy(InputStream in, OutputStream out) throws IOException { int read; while ((read = in.read(buffer)) != -1) { totalSize += read; if (out != null) { out.write(buffer, 0, read); } } } private URL makeURL(String[] uids) { StringBuffer sb = new StringBuffer(256); sb.append(baseurl).append("?requestType=").append(requestType); sb.append("&studyUID=").append(uids[0]); sb.append("&seriesUID=").append(uids[1]); sb.append("&objectUID=").append(uids[2]); if (!contentType.isEmpty()) { sb.append("&contentType="); append(contentType, sb); } if (charset != null) { sb.append("&charset="); append(charset, sb); } if (anonymize) { sb.append("&anonymize=yes"); } if (annotation != null) { sb.append("&annotation="); append(annotation, sb); } if (rows > 0) { sb.append("&rows=").append(rows); } if (columns > 0) { sb.append("&columns=").append(columns); } if (frameNumber > 0) { sb.append("&frameNumber=").append(frameNumber); } if (imageQuality > 0) { sb.append("&imageQuality=").append(imageQuality); } if (region != null) { sb.append("®ion="); append(region, sb); } if (window != null) { sb.append("&windowCenter=").append(window[0]); sb.append("&windowWidth=").append(window[1]); } if (psuid != null) { sb.append("&presentationSeriesUID=").append(psuid[0]); sb.append("&presentationUID=").append(psuid[1]); } if (tsfile || tsuid != null) { sb.append("&transferSyntax="); if (tsfile) sb.append(uids[3]); else append(tsuid, sb); } try { return new URL(sb.toString()); } catch (MalformedURLException e) { throw new AssertionError(e); } } private static void append(List<String> ss, StringBuffer sb) { for (String s : ss) sb.append(s).append(','); sb.setLength(sb.length() - 1); } private static void append(String[] ss, StringBuffer sb) { for (String s : ss) sb.append(s).append(','); sb.setLength(sb.length() - 1); } }