|
@@ -0,0 +1,307 @@
|
|
|
+package com.zskk.service;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
+import java.security.GeneralSecurityException;
|
|
|
+import java.text.MessageFormat;
|
|
|
+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.Option.Builder;
|
|
|
+import org.apache.commons.cli.Options;
|
|
|
+import org.apache.commons.cli.Option;
|
|
|
+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.ElementDictionary;
|
|
|
+import org.dcm4che3.data.VR;
|
|
|
+import org.dcm4che3.io.DicomInputStream;
|
|
|
+import org.dcm4che3.net.ApplicationEntity;
|
|
|
+import org.dcm4che3.net.Association;
|
|
|
+import org.dcm4che3.net.Connection;
|
|
|
+import org.dcm4che3.net.Device;
|
|
|
+import org.dcm4che3.net.DimseRSPHandler;
|
|
|
+import org.dcm4che3.net.IncompatibleConnectionException;
|
|
|
+import org.dcm4che3.net.pdu.AAssociateRQ;
|
|
|
+import org.dcm4che3.net.pdu.ExtendedNegotiation;
|
|
|
+import org.dcm4che3.net.pdu.PresentationContext;
|
|
|
+import org.dcm4che3.tool.common.CLIUtils;
|
|
|
+import org.dcm4che3.util.SafeClose;
|
|
|
+import org.dcm4che3.util.StringUtils;
|
|
|
+
|
|
|
+public class DicomCMoveService extends Device{
|
|
|
+
|
|
|
+ private static enum InformationModel {
|
|
|
+ PatientRoot(UID.PatientRootQueryRetrieveInformationModelMOVE, "STUDY"),
|
|
|
+ StudyRoot(UID.StudyRootQueryRetrieveInformationModelMOVE, "STUDY"),
|
|
|
+ PatientStudyOnly(UID.PatientStudyOnlyQueryRetrieveInformationModelMOVERetired, "STUDY"),
|
|
|
+ CompositeInstanceRoot(UID.CompositeInstanceRootRetrieveMOVE, "IMAGE"),
|
|
|
+ HangingProtocol(UID.HangingProtocolInformationModelMOVE, null),
|
|
|
+ ColorPalette(UID.ColorPaletteQueryRetrieveInformationModelMOVE, null);
|
|
|
+
|
|
|
+ final String cuid;
|
|
|
+ final String level;
|
|
|
+
|
|
|
+ InformationModel(String cuid, String level) {
|
|
|
+ this.cuid = cuid;
|
|
|
+ this.level = level;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static ResourceBundle rb =
|
|
|
+ ResourceBundle.getBundle("org.dcm4che3.tool.movescu.messages");
|
|
|
+
|
|
|
+ private static final int[] DEF_IN_FILTER = {
|
|
|
+ Tag.SOPInstanceUID,
|
|
|
+ Tag.StudyInstanceUID,
|
|
|
+ Tag.SeriesInstanceUID
|
|
|
+ };
|
|
|
+
|
|
|
+ private final ApplicationEntity ae = new ApplicationEntity("cspacsFIR");
|
|
|
+ private final Connection conn = new Connection();
|
|
|
+ private final Connection remote = new Connection();
|
|
|
+ private final AAssociateRQ rq = new AAssociateRQ();
|
|
|
+ private int priority;
|
|
|
+ private String destination;
|
|
|
+ private InformationModel model;
|
|
|
+ private Attributes keys = new Attributes();
|
|
|
+ private int[] inFilter = DEF_IN_FILTER;
|
|
|
+ private Association as;
|
|
|
+
|
|
|
+// public DicomCMoveService() throws IOException {
|
|
|
+// super("movescu");
|
|
|
+// addConnection(conn);
|
|
|
+// addApplicationEntity(ae);
|
|
|
+// ae.addConnection(conn);
|
|
|
+// }
|
|
|
+
|
|
|
+ public final void setPriority(int priority) {
|
|
|
+ this.priority = priority;
|
|
|
+ }
|
|
|
+
|
|
|
+ public final void setInformationModel(InformationModel model, String[] tss,
|
|
|
+ boolean relational) {
|
|
|
+ this.model = model;
|
|
|
+ rq.addPresentationContext(new PresentationContext(1, model.cuid, tss));
|
|
|
+ if (relational)
|
|
|
+ rq.addExtendedNegotiation(new ExtendedNegotiation(model.cuid, new byte[]{1}));
|
|
|
+ if (model.level != null)
|
|
|
+ addLevel(model.level);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void addLevel(String s) {
|
|
|
+ keys.setString(Tag.QueryRetrieveLevel, VR.CS, s);
|
|
|
+ }
|
|
|
+
|
|
|
+ public final void setDestination(String destination) {
|
|
|
+ this.destination = destination;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void addKey(int tag, String... ss) {
|
|
|
+ VR vr = ElementDictionary.vrOf(tag, keys.getPrivateCreator(tag));
|
|
|
+ keys.setString(tag, vr, ss);
|
|
|
+ }
|
|
|
+
|
|
|
+ public final void setInputFilter(int[] inFilter) {
|
|
|
+ this.inFilter = inFilter;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static CommandLine parseComandLine(String[] args)
|
|
|
+ throws ParseException {
|
|
|
+ Options opts = new Options();
|
|
|
+ addServiceClassOptions(opts);
|
|
|
+ addKeyOptions(opts);
|
|
|
+ addRetrieveLevelOption(opts);
|
|
|
+ addDestinationOption(opts);
|
|
|
+ CLIUtils.addConnectOption(opts);
|
|
|
+ CLIUtils.addBindOption(opts, "cspacsFIR");
|
|
|
+ CLIUtils.addAEOptions(opts);
|
|
|
+ CLIUtils.addRetrieveTimeoutOption(opts);
|
|
|
+ CLIUtils.addPriorityOption(opts);
|
|
|
+ CLIUtils.addCommonOptions(opts);
|
|
|
+ return CLIUtils.parseComandLine(args, opts, rb, DicomCMoveService.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("static-access")
|
|
|
+ private static void addRetrieveLevelOption(Options opts) {
|
|
|
+ opts.addOption(Option.builder("L")
|
|
|
+ .hasArg()
|
|
|
+ .argName("PATIENT|STUDY|SERIES|IMAGE|FRAME")
|
|
|
+ .desc(rb.getString("level"))
|
|
|
+ .build());
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("static-access")
|
|
|
+ private static void addDestinationOption(Options opts) {
|
|
|
+ opts.addOption(Option.builder()
|
|
|
+ .longOpt("dest")
|
|
|
+ .hasArg()
|
|
|
+ .argName("aet")
|
|
|
+ .desc(rb.getString("dest"))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("static-access")
|
|
|
+ private static void addKeyOptions(Options opts) {
|
|
|
+ opts.addOption(Option.builder("m")
|
|
|
+ .hasArgs()
|
|
|
+ .argName("attr=value")
|
|
|
+ .valueSeparator('=')
|
|
|
+ .desc(rb.getString("match"))
|
|
|
+ .build());
|
|
|
+ opts.addOption(Option.builder("i")
|
|
|
+ .hasArgs()
|
|
|
+ .argName("attr")
|
|
|
+ .desc(rb.getString("in-attr"))
|
|
|
+ .build());
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("static-access")
|
|
|
+ private static void addServiceClassOptions(Options opts) {
|
|
|
+ opts.addOption(Option.builder("M")
|
|
|
+ .hasArg()
|
|
|
+ .argName("name")
|
|
|
+ .desc(rb.getString("model"))
|
|
|
+ .build());
|
|
|
+ CLIUtils.addTransferSyntaxOptions(opts);
|
|
|
+ opts.addOption(null, "relational", false, rb.getString("relational"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public String doMove(String studyuid) {
|
|
|
+ try {
|
|
|
+ addConnection(conn);
|
|
|
+ addApplicationEntity(ae);
|
|
|
+ ae.addConnection(conn);
|
|
|
+
|
|
|
+ String[]str = new String[]{"-c", "cspacsFIR@192.10.10.108:2104","-m","StudyInstanceUID=" + studyuid,"--dest","PACSONLINE"};
|
|
|
+ CommandLine cl = parseComandLine(str);
|
|
|
+
|
|
|
+ CLIUtils.configureConnect(this.remote, this.rq, cl);
|
|
|
+ CLIUtils.configureBind(conn, ae, cl);
|
|
|
+ CLIUtils.configure(conn, cl);
|
|
|
+ this.remote.setTlsProtocols(conn.getTlsProtocols());
|
|
|
+ this.remote.setTlsCipherSuites(conn.getTlsCipherSuites());
|
|
|
+ configureServiceClass(this, cl);
|
|
|
+ configureKeys(this, cl);
|
|
|
+ this.setPriority(CLIUtils.priorityOf(cl));
|
|
|
+ this.setDestination(destinationOf(cl));
|
|
|
+ ExecutorService executorService =
|
|
|
+ Executors.newSingleThreadExecutor();
|
|
|
+ ScheduledExecutorService scheduledExecutorService =
|
|
|
+ Executors.newSingleThreadScheduledExecutor();
|
|
|
+ this.setExecutor(executorService);
|
|
|
+ this.setScheduledExecutor(scheduledExecutorService);
|
|
|
+ try {
|
|
|
+
|
|
|
+ this.open();
|
|
|
+
|
|
|
+ List<String> argList = cl.getArgList();
|
|
|
+ if (argList.isEmpty())
|
|
|
+ this.retrieve();
|
|
|
+ else
|
|
|
+ for (String arg : argList)
|
|
|
+ this.retrieve(new File(arg));
|
|
|
+ } finally {
|
|
|
+ this.close();
|
|
|
+ executorService.shutdown();
|
|
|
+ scheduledExecutorService.shutdown();
|
|
|
+ }
|
|
|
+ return "succeed";
|
|
|
+ } catch (ParseException e) {
|
|
|
+ System.err.println("movescu error: " + e.getMessage());
|
|
|
+ System.err.println(rb.getString("try"));
|
|
|
+// System.exit(2);
|
|
|
+ return "movescu: " + e.getMessage()+rb.getString("try");
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.err.println("movescu error: " + e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ return "movescu: " + e.getMessage();
|
|
|
+
|
|
|
+// System.exit(2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void configureServiceClass(DicomCMoveService main, CommandLine cl) throws ParseException {
|
|
|
+ main.setInformationModel(informationModelOf(cl),
|
|
|
+ CLIUtils.transferSyntaxesOf(cl), cl.hasOption("relational"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String destinationOf(CommandLine cl) throws ParseException {
|
|
|
+ if (cl.hasOption("dest"))
|
|
|
+ return cl.getOptionValue("dest");
|
|
|
+ throw new ParseException(rb.getString("missing-dest"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void configureKeys(DicomCMoveService main, CommandLine cl) {
|
|
|
+ if (cl.hasOption("m")) {
|
|
|
+ String[] keys = cl.getOptionValues("m");
|
|
|
+ for (int i = 1; i < keys.length; i++, i++)
|
|
|
+ main.addKey(CLIUtils.toTag(keys[i - 1]), StringUtils.split(keys[i], '/'));
|
|
|
+ }
|
|
|
+ if (cl.hasOption("L"))
|
|
|
+ main.addLevel(cl.getOptionValue("L"));
|
|
|
+ if (cl.hasOption("i"))
|
|
|
+ main.setInputFilter(CLIUtils.toTags(cl.getOptionValues("i")));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static InformationModel informationModelOf(CommandLine cl) throws ParseException {
|
|
|
+ try {
|
|
|
+ return cl.hasOption("M")
|
|
|
+ ? InformationModel.valueOf(cl.getOptionValue("M"))
|
|
|
+ : InformationModel.StudyRoot;
|
|
|
+ } catch(IllegalArgumentException e) {
|
|
|
+ throw new ParseException(MessageFormat.format(
|
|
|
+ rb.getString("invalid-model-name"),
|
|
|
+ cl.getOptionValue("M")));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void open() throws IOException, InterruptedException,
|
|
|
+ IncompatibleConnectionException, GeneralSecurityException {
|
|
|
+ as = ae.connect(conn, remote, rq);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void close() throws IOException, InterruptedException {
|
|
|
+ if (as != null && as.isReadyForDataTransfer()) {
|
|
|
+ as.waitForOutstandingRSP();
|
|
|
+ as.release();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void retrieve(File f) throws IOException, InterruptedException {
|
|
|
+ Attributes attrs = new Attributes();
|
|
|
+ DicomInputStream dis = null;
|
|
|
+ try {
|
|
|
+ attrs.addSelected(new DicomInputStream(f).readDataset(-1, -1), inFilter);
|
|
|
+ } finally {
|
|
|
+ SafeClose.close(dis);
|
|
|
+ }
|
|
|
+ attrs.addAll(keys);
|
|
|
+ retrieve(attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void retrieve() throws IOException, InterruptedException {
|
|
|
+ retrieve(keys);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void retrieve(Attributes keys) throws IOException, InterruptedException {
|
|
|
+ DimseRSPHandler rspHandler = new DimseRSPHandler(as.nextMessageID()) {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onDimseRSP(Association as, Attributes cmd,
|
|
|
+ Attributes data) {
|
|
|
+ super.onDimseRSP(as, cmd, data);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ as.cmove(model.cuid, priority, keys, null, destination, rspHandler);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|