/* ***** 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 https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* 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.dcm4che3.tool.ihe.modality;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.security.GeneralSecurityException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.UID;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Sequence;
import org.dcm4che3.data.VR;
import org.dcm4che3.net.ApplicationEntity;
import org.dcm4che3.net.Connection;
import org.dcm4che3.net.Device;
import org.dcm4che3.net.IncompatibleConnectionException;
import org.dcm4che3.tool.common.CLIUtils;
import org.dcm4che3.tool.common.DicomFiles;
import org.dcm4che3.tool.mkkos.MkKOS;
import org.dcm4che3.tool.mppsscu.MppsSCU;
import org.dcm4che3.tool.stgcmtscu.StgCmtSCU;
import org.dcm4che3.tool.storescu.StoreSCU;
import org.dcm4che3.util.UIDUtils;
/**
* @author Michael Backhaus <michael.backhaus@agfa.com>
* @author Gunter Zeilinger <gunterze@gmail.com>
*/
public class Modality {
private static ResourceBundle rb =
ResourceBundle.getBundle("org.dcm4che3.tool.ihe.modality.messages");
static BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
private static String calledAET;
@SuppressWarnings({ "unchecked" })
public static void main(String[] args) {
try {
CommandLine cl = parseComandLine(args);
if(cl.getArgList().isEmpty())
throw new MissingOptionException(
rb.getString("missing-i-file"));
final Device device = new Device("modality");
final Connection conn = new Connection();
final ApplicationEntity ae = new ApplicationEntity("MODALITY");
checkOptions(cl);
CLIUtils.configureBind(conn, ae, cl);
CLIUtils.configure(conn, cl);
device.addConnection(conn);
device.addApplicationEntity(ae);
ae.addConnection(conn);
final MppsSCU mppsscu = new MppsSCU(ae);
final StoreSCU storescu = new StoreSCU(ae);
final StgCmtSCU stgcmtscu = new StgCmtSCU(ae);
CLIUtils.configureConnect(mppsscu.getRemoteConnection(), mppsscu.getAAssociateRQ(), cl);
CLIUtils.configureConnect(stgcmtscu.getRemoteConnection(), stgcmtscu.getAAssociateRQ(), cl);
CLIUtils.configureConnect(storescu.getRemoteConnection(), storescu.getAAssociateRQ(), cl);
calledAET = storescu.getAAssociateRQ().getCalledAET();
mppsscu.setTransferSyntaxes(CLIUtils.transferSyntaxesOf(cl));
mppsscu.setCodes(CLIUtils.loadProperties(
cl.getOptionValue("code-config", "resource:code.properties"), null));
if (cl.hasOption("dc"))
mppsscu.setFinalStatus("DISCONTINUED");
if (cl.hasOption("dc-reason"))
mppsscu.setDiscontinuationReason(cl.getOptionValue("dc-reason"));
stgcmtscu.setTransferSyntaxes(CLIUtils.transferSyntaxesOf(cl));
stgcmtscu.setStorageDirectory(StgCmtSCU.getStorageDirectory(cl));
StoreSCU.configureRelatedSOPClass(storescu, cl);
storescu.setUIDSuffix(StoreSCU.uidSuffixOf(cl));
Attributes attrs = new Attributes();
CLIUtils.addAttributes(attrs, cl.getOptionValues("s"));
mppsscu.setAttributes(attrs);
storescu.setAttributes(attrs);
stgcmtscu.setAttributes(attrs);
setTlsParams(mppsscu.getRemoteConnection(), conn);
setTlsParams(storescu.getRemoteConnection(), conn);
setTlsParams(stgcmtscu.getRemoteConnection(), conn);
String tmpPrefix = "iocmtest-";
String tmpSuffix = null;
File tmpDir = null;
configureTmpFile(storescu, tmpPrefix, tmpSuffix, tmpDir, cl);
String mppsiuid = UIDUtils.createUID();
mppsscu.setPPSUID(mppsiuid);
if(cl.hasOption("kos-title")) {
List<String> fname = Arrays.asList(mkkos(cl));
scanFiles(fname, tmpPrefix, tmpSuffix, tmpDir, mppsscu, storescu, stgcmtscu);
} else {
stgcmtscu.setUIDSuffix(cl.getOptionValue("uid-suffix"));
storescu.setUIDSuffix(cl.getOptionValue("uid-suffix"));
mppsscu.setUIDSuffix(cl.getOptionValue("uid-suffix"));
scanFiles(cl.getArgList(), tmpPrefix, tmpSuffix, tmpDir, mppsscu, storescu, stgcmtscu);
}
ExecutorService executorService =
Executors.newCachedThreadPool();
ScheduledExecutorService scheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
device.setExecutor(executorService);
device.setScheduledExecutor(scheduledExecutorService);
device.bindConnections();
try {
boolean sendMpps = cl.hasOption("mpps");
boolean sendLateMpps = cl.hasOption("mpps-late");
if (sendMpps || sendLateMpps) {
sendMpps(mppsscu, sendMpps);
addReferencedPerformedProcedureStepSequence(mppsiuid, storescu);
} else {
nullifyReferencedPerformedProcedureStepSequence(storescu);
}
sendObjects(storescu);
if (sendLateMpps)
sendMppsNSet(mppsscu);
if (cl.hasOption("stgcmt"))
sendStgCmt(stgcmtscu);
} finally {
if (conn.isListening()) {
device.waitForNoOpenConnections();
device.unbindConnections();
}
executorService.shutdown();
scheduledExecutorService.shutdown();
}
} catch (ParseException e) {
System.err.println(e.getMessage());
System.err.println(rb.getString("try"));
System.exit(2);
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
System.exit(2);
}
}
private static void checkOptions(CommandLine cl) throws ParseException {
if (!cl.hasOption("b"))
throw new MissingOptionException(
CLIUtils.rb.getString("missing-bind-opt"));
if (!cl.hasOption("c"))
throw new MissingOptionException(
CLIUtils.rb.getString("missing-connect-opt"));
if (cl.hasOption("mpps") && cl.hasOption("mpps-late"))
throw new ParseException(rb.getString("mpps-error"));
}
public static void setTlsParams(Connection remote, Connection conn) {
remote.setTlsProtocols(conn.getTlsProtocols());
remote.setTlsCipherSuites(conn.getTlsCipherSuites());
}
private static void addReferencedPerformedProcedureStepSequence(String mppsiuid,
StoreSCU storescu) {
Attributes attrs = storescu.getAttributes();
Sequence seq = attrs.newSequence(Tag.ReferencedPerformedProcedureStepSequence, 1);
Attributes item = new Attributes(2);
item.setString(Tag.ReferencedSOPClassUID, VR.UI, UID.ModalityPerformedProcedureStepSOPClass);
item.setString(Tag.ReferencedSOPInstanceUID, VR.UI, mppsiuid);
seq.add(item);
}
private static void nullifyReferencedPerformedProcedureStepSequence(StoreSCU storescu) {
Attributes attrs = storescu.getAttributes();
attrs.setNull(Tag.ReferencedPerformedProcedureStepSequence, VR.SQ);
}
@SuppressWarnings("unchecked")
private static String mkkos(CommandLine cl) throws Exception {
printNextStepMessage("Will now generate a Key Object for files in " + cl.getArgList());
final MkKOS mkkos = new MkKOS();
mkkos.setUIDSuffix(cl.getOptionValue("uid-suffix"));
mkkos.setCodes(CLIUtils.loadProperties(
cl.getOptionValue("code-config", "resource:code.properties"),
null));
mkkos.setDocumentTitle(mkkos.toCodeItem(documentTitleOf(cl)));
mkkos.setKeyObjectDescription(cl.getOptionValue("desc"));
mkkos.setSeriesNumber(cl.getOptionValue("series-no", "999"));
mkkos.setInstanceNumber(cl.getOptionValue("inst-no", "1"));
mkkos.setOutputFile(MkKOS.outputFileOf(cl));
mkkos.setNoFileMetaInformation(cl.hasOption("F"));
mkkos.setTransferSyntax(cl.getOptionValue("t", UID.ExplicitVRLittleEndian));
mkkos.setEncodingOptions(CLIUtils.encodingOptionsOf(cl));
DicomFiles.scan(cl.getArgList(), new DicomFiles.Callback() {
@Override
public boolean dicomFile(File f, Attributes fmi,
long dsPos, Attributes ds) {
return mkkos.addInstance(ds);
}
});
System.out.println();
mkkos.writeKOS();
System.out.println(MessageFormat.format(rb.getString("stored"), mkkos.getFname()));
return mkkos.getFname();
}
private static String documentTitleOf(CommandLine cl) throws MissingOptionException {
if (!cl.hasOption("kos-title"))
throw new MissingOptionException(rb.getString("missing-title"));
return cl.getOptionValue("kos-title");
}
private static void sendStgCmt(StgCmtSCU stgcmtscu) throws IOException,
InterruptedException, IncompatibleConnectionException, GeneralSecurityException {
printNextStepMessage("Will now send Storage Commitment to " + calledAET);
try {
stgcmtscu.open();
stgcmtscu.sendRequests();
} finally {
stgcmtscu.close();
}
}
private static void sendMpps(MppsSCU mppsscu, boolean sendNSet) throws IOException,
InterruptedException, IncompatibleConnectionException, GeneralSecurityException {
try {
printNextStepMessage("Will now send MPPS N-CREATE to " + calledAET);
mppsscu.open();
mppsscu.createMpps();
if (sendNSet) {
printNextStepMessage("Will now send MPPS N-SET to " + calledAET);
mppsscu.updateMpps();
}
} finally {
mppsscu.close();
}
}
private static void sendMppsNSet(MppsSCU mppsscu) throws IOException, InterruptedException,
IncompatibleConnectionException, GeneralSecurityException {
try {
printNextStepMessage("Will now send MPPS N-SET to " + calledAET);
mppsscu.open();
mppsscu.updateMpps();
} finally {
mppsscu.close();
}
}
private static void printNextStepMessage(String message) throws IOException {
System.out.println("===========================================================");
System.out.println(message + ". Press <enter> to continue.");
System.out.println("===========================================================");
bufferedReader.read();
}
private static void sendObjects(StoreSCU storescu) throws IOException,
InterruptedException, IncompatibleConnectionException, GeneralSecurityException {
printNextStepMessage("Will now send DICOM object(s) to " + calledAET);
try {
storescu.open();
storescu.sendFiles();
} finally {
storescu.close();
}
}
private static CommandLine parseComandLine(String[] args)
throws ParseException{
Options opts = new Options();
CLIUtils.addTransferSyntaxOptions(opts);
CLIUtils.addConnectOption(opts);
CLIUtils.addAEOptions(opts);
CLIUtils.addResponseTimeoutOption(opts);
CLIUtils.addCommonOptions(opts);
CLIUtils.addBindOption(opts, "IOCMTEST");
StoreSCU.addTmpFileOptions(opts);
StoreSCU.addUIDSuffixOption(opts);
addOptions(opts);
return CLIUtils.parseComandLine(args, opts, rb, Modality.class);
}
@SuppressWarnings("static-access")
private static void addOptions(Options opts) {
opts.addOption(OptionBuilder
.hasArg()
.withArgName("code-value")
.withDescription(rb.getString("kos-title"))
.withLongOpt("kos-title")
.create());
opts.addOption(OptionBuilder
.hasArg()
.withArgName("file")
.withDescription(rb.getString("o-file"))
.create("o"));
opts.addOption(OptionBuilder
.hasArg()
.withArgName("file")
.withDescription(rb.getString("code-config"))
.withLongOpt("code-config")
.create());
OptionGroup mpps = new OptionGroup();
mpps.addOption(OptionBuilder
.withDescription(rb.getString("mpps-late"))
.withLongOpt("mpps-late")
.create());
mpps.addOption(OptionBuilder
.withDescription(rb.getString("mpps"))
.withLongOpt("mpps")
.create());
opts.addOptionGroup(mpps);
opts.addOption(OptionBuilder
.withDescription(rb.getString("stgcmt"))
.withLongOpt("stgcmt")
.create());
opts.addOption(null, "dc", false, rb.getString("dc"));
opts.addOption(OptionBuilder
.hasArg()
.withArgName("code-value")
.withDescription(rb.getString("dc-reason"))
.withLongOpt("dc-reason")
.create());
opts.addOption(OptionBuilder
.hasArgs()
.withArgName("[seq/]attr=value")
.withValueSeparator('=')
.withDescription(rb.getString("set"))
.create("s"));
}
private static void scanFiles(List<String> fnames, String tmpPrefix, String tmpSuffix,
File tmpDir, final MppsSCU mppsscu, final StoreSCU storescu, final StgCmtSCU stgcmtscu)
throws IOException {
printNextStepMessage("Will now scan files in " + fnames);
File tmpFile = File.createTempFile(tmpPrefix, tmpSuffix, tmpDir);
tmpFile.deleteOnExit();
final BufferedWriter fileInfos = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(tmpFile)));
try {
DicomFiles.scan(fnames, new DicomFiles.Callback() {
@Override
public boolean dicomFile(File f, Attributes fmi, long dsPos,
Attributes ds) throws Exception {
return mppsscu.addInstance(ds)
&& storescu.addFile(fileInfos, f, dsPos, fmi, ds)
&& stgcmtscu.addInstance(ds);
}
});
storescu.setTmpFile(tmpFile);
} finally {
fileInfos.close();
}
System.out.println(" Done");
}
private static void configureTmpFile(StoreSCU storescu, String tmpPrefix, String tmpSuffix,
File tmpDir, CommandLine cl) {
if (cl.hasOption("tmp-file-dir")) {
tmpDir = new File(cl.getOptionValue("tmp-file-dir"));
storescu.setTmpFileDirectory(tmpDir);
}
tmpPrefix = cl.getOptionValue("tmp-file-prefix", "iocmtest-");
storescu.setTmpFilePrefix(tmpPrefix);
tmpSuffix = cl.getOptionValue("tmp-file-suffix");
storescu.setTmpFileSuffix(tmpSuffix);
}
}