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) 2012 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.tool.mppsscu; 040 041import java.io.File; 042import java.io.IOException; 043import java.security.GeneralSecurityException; 044import java.text.DecimalFormat; 045import java.util.ArrayList; 046import java.util.Date; 047import java.util.HashMap; 048import java.util.List; 049import java.util.Properties; 050import java.util.ResourceBundle; 051import java.util.concurrent.ExecutorService; 052import java.util.concurrent.Executors; 053import java.util.concurrent.ScheduledExecutorService; 054 055import org.apache.commons.cli.CommandLine; 056import org.apache.commons.cli.OptionBuilder; 057import org.apache.commons.cli.Options; 058import org.apache.commons.cli.ParseException; 059import org.dcm4che3.data.Attributes; 060import org.dcm4che3.data.ElementDictionary; 061import org.dcm4che3.data.Sequence; 062import org.dcm4che3.data.Tag; 063import org.dcm4che3.data.UID; 064import org.dcm4che3.data.VR; 065import org.dcm4che3.net.ApplicationEntity; 066import org.dcm4che3.net.Association; 067import org.dcm4che3.net.Connection; 068import org.dcm4che3.net.Device; 069import org.dcm4che3.net.DimseRSPHandler; 070import org.dcm4che3.net.IncompatibleConnectionException; 071import org.dcm4che3.net.Status; 072import org.dcm4che3.net.pdu.AAssociateRQ; 073import org.dcm4che3.net.pdu.PresentationContext; 074import org.dcm4che3.tool.common.CLIUtils; 075import org.dcm4che3.tool.common.DicomFiles; 076import org.dcm4che3.util.DateUtils; 077 078/** 079 * @author Gunter Zeilinger <gunterze@gmail.com> 080 * @author Michael Backhaus <michael.backhaus@agfa.com> 081 */ 082public class MppsSCU { 083 084 public static final class MppsWithIUID { 085 086 public String iuid; 087 public Attributes mpps; 088 089 MppsWithIUID(String iuid, Attributes mpps) { 090 this.iuid = iuid; 091 this.mpps = mpps; 092 } 093 } 094 095 private static ResourceBundle rb = 096 ResourceBundle.getBundle("org.dcm4che3.tool.mppsscu.messages"); 097 private static final ElementDictionary dict = 098 ElementDictionary.getStandardElementDictionary(); 099 private static final String IN_PROGRESS = "IN PROGRESS"; 100 private static final String COMPLETED = "COMPLETED"; 101 private static final String DISCONTINUED = "DISCONTINUED"; 102 private static final int[] MPPS_TOP_LEVEL_ATTRS = { 103 Tag.SpecificCharacterSet, 104 Tag.Modality, 105 Tag.ProcedureCodeSequence, 106 Tag.ReferencedPatientSequence, 107 Tag.PatientName, 108 Tag.PatientID, 109 Tag.IssuerOfPatientID, 110 Tag.IssuerOfPatientIDQualifiersSequence, 111 Tag.PatientBirthDate, 112 Tag.PatientSex, 113 Tag.StudyID, 114 Tag.AdmissionID, 115 Tag.IssuerOfAdmissionIDSequence, 116 Tag.ServiceEpisodeID, 117 Tag.IssuerOfServiceEpisodeID, 118 Tag.ServiceEpisodeDescription, 119 Tag.PerformedProcedureStepStartDate, 120 Tag.PerformedProcedureStepStartTime, 121 Tag.PerformedProcedureStepID, 122 Tag.PerformedProcedureStepDescription, 123 Tag.PerformedProtocolCodeSequence, 124 Tag.CommentsOnThePerformedProcedureStep, 125 }; 126 127 private static final int[] MPPS_TOP_LEVEL_TYPE_2_ATTRS = { 128 Tag.ProcedureCodeSequence, 129 Tag.ReferencedPatientSequence, 130 Tag.PatientName, 131 Tag.PatientID, 132 Tag.PatientBirthDate, 133 Tag.PatientSex, 134 Tag.StudyID, 135 Tag.PerformedStationName, 136 Tag.PerformedLocation, 137 Tag.PerformedProcedureStepDescription, 138 Tag.PerformedProcedureTypeDescription, 139 Tag.PerformedProtocolCodeSequence, 140 }; 141 142 private static final int[] CREATE_MPPS_TOP_LEVEL_EMPTY_ATTRS = { 143 Tag.PerformedProcedureStepEndDate, 144 Tag.PerformedProcedureStepEndTime, 145 Tag.PerformedProcedureStepDiscontinuationReasonCodeSequence, 146 Tag.PerformedSeriesSequence 147 }; 148 149 private static final int[] FINAL_MPPS_TOP_LEVEL_ATTRS = { 150 Tag.SpecificCharacterSet, 151 Tag.PerformedProcedureStepEndDate, 152 Tag.PerformedProcedureStepEndTime, 153 Tag.PerformedProcedureStepStatus, 154 Tag.PerformedProcedureStepDiscontinuationReasonCodeSequence, 155 Tag.PerformedSeriesSequence 156 }; 157 158 private static final int[] SSA_ATTRS = { 159 Tag.AccessionNumber, 160 Tag.IssuerOfAccessionNumberSequence, 161 Tag.ReferencedStudySequence, 162 Tag.StudyInstanceUID, 163 Tag.RequestedProcedureDescription, 164 Tag.RequestedProcedureCodeSequence, 165 Tag.ScheduledProcedureStepDescription, 166 Tag.ScheduledProtocolCodeSequence, 167 Tag.ScheduledProcedureStepID, 168 Tag.OrderPlacerIdentifierSequence, 169 Tag.OrderFillerIdentifierSequence, 170 Tag.RequestedProcedureID, 171 Tag.PlacerOrderNumberImagingServiceRequest, 172 Tag.FillerOrderNumberImagingServiceRequest, 173 }; 174 175 private static final int[] SSA_TYPE_2_ATTRS = { 176 Tag.AccessionNumber, 177 Tag.ReferencedStudySequence, 178 Tag.StudyInstanceUID, 179 Tag.RequestedProcedureDescription, 180 Tag.RequestedProcedureID, 181 Tag.ScheduledProcedureStepDescription, 182 Tag.ScheduledProtocolCodeSequence, 183 Tag.ScheduledProcedureStepID, 184 }; 185 186 private static final int[] PERF_SERIES_ATTRS = { 187 Tag.SeriesDescription, 188 Tag.PerformingPhysicianName, 189 Tag.OperatorsName, 190 Tag.ProtocolName, 191 Tag.SeriesInstanceUID 192 }; 193 194 private static final int[] PERF_SERIES_TYPE_2_ATTRS = { 195 Tag.RetrieveAETitle, 196 Tag.SeriesDescription, 197 Tag.PerformingPhysicianName, 198 Tag.OperatorsName, 199 Tag.ReferencedNonImageCompositeSOPInstanceSequence 200 }; 201 202 private static final String ppsStartDate; 203 private static final String ppsStartTime; 204 static { 205 Date now = new Date(); 206 ppsStartDate = DateUtils.formatDA(null, now); 207 ppsStartTime = DateUtils.formatTM(null, now); 208 } 209 210 211 212 public interface RSPHandlerFactory { 213 214 DimseRSPHandler createDimseRSPHandlerForNCreate(MppsWithIUID mppsWithUID); 215 DimseRSPHandler createDimseRSPHandlerForNSet(); 216 } 217 218 //default response handler 219 private RSPHandlerFactory rspHandlerFactory = new RSPHandlerFactory(){ 220 221 @Override 222 public DimseRSPHandler createDimseRSPHandlerForNCreate(final MppsWithIUID mppsWithUID) { 223 224 return new DimseRSPHandler(as.nextMessageID()) { 225 226 @Override 227 public void onDimseRSP(Association as, Attributes cmd, Attributes data) { 228 switch(cmd.getInt(Tag.Status, -1)) { 229 case Status.Success: 230 case Status.AttributeListError: 231 case Status.AttributeValueOutOfRange: 232 mppsWithUID.iuid = cmd.getString( 233 Tag.AffectedSOPInstanceUID, mppsWithUID.iuid); 234 addCreatedMpps(mppsWithUID); 235 } 236 super.onDimseRSP(as, cmd, data); 237 } 238 }; 239 } 240 241 @Override 242 public DimseRSPHandler createDimseRSPHandlerForNSet() { 243 244 return new DimseRSPHandler(as.nextMessageID()); 245 } 246 }; 247 248 //Response handler is settable from outside 249 public void setRspHandlerFactory(RSPHandlerFactory rspHandlerFactory) { 250 this.rspHandlerFactory = rspHandlerFactory; 251 } 252 253 254 private final ApplicationEntity ae; 255 private final Connection remote; 256 private final AAssociateRQ rq = new AAssociateRQ(); 257 private Attributes attrs; 258 private String uidSuffix; 259 private boolean newPPSID; 260 private int serialNo = (int) (System.currentTimeMillis() & 0x7FFFFFFFL); 261 private String ppsuid; 262 private String ppsid; 263 private DecimalFormat ppsidFormat = new DecimalFormat("PPS-0000000000"); 264 private String protocolName = "UNKNOWN"; 265 private String archiveRequested; 266 private String finalStatus = COMPLETED; 267 private Attributes discontinuationReason; 268 269 private Properties codes; 270 private HashMap<String,MppsWithIUID> map = new HashMap<String,MppsWithIUID>(); 271 private ArrayList<MppsWithIUID> created = new ArrayList<MppsWithIUID>(); 272 private Association as; 273 private Device device; 274 275 public MppsSCU(ApplicationEntity ae) throws IOException { 276 this.remote = new Connection(); 277 this.ae = ae; 278 this.device = ae.getDevice(); 279 } 280 281 public Connection getRemoteConnection() { 282 return remote; 283 } 284 285 public AAssociateRQ getAAssociateRQ() { 286 return rq; 287 } 288 289 public void addCreatedMpps (MppsWithIUID mpps) 290 { 291 created.add(mpps); 292 } 293 294 public final void setUIDSuffix(String uidSuffix) { 295 this.uidSuffix = uidSuffix; 296 } 297 298 public final void setPPSUID(String ppsuid) { 299 this.ppsuid = ppsuid; 300 } 301 302 public final void setPPSID(String ppsid) { 303 this.ppsid = ppsid; 304 } 305 306 public final void setPPSIDStart(int ppsidStart) { 307 this.serialNo = ppsidStart; 308 } 309 310 public final void setPPSIDFormat(String ppsidFormat) { 311 this.ppsidFormat = new DecimalFormat(ppsidFormat); 312 } 313 314 public final void setNewPPSID(boolean newPPSID) { 315 this.newPPSID = newPPSID; 316 } 317 318 public final void setProtocolName(String protocolName) { 319 this.protocolName = protocolName; 320 } 321 322 public final void setArchiveRequested(String archiveRequested) { 323 this.archiveRequested = archiveRequested; 324 } 325 326 public final void setFinalStatus(String finalStatus) { 327 this.finalStatus = finalStatus; 328 } 329 330 public final void setCodes(Properties codes) { 331 this.codes = codes; 332 } 333 334 public void setAttributes(Attributes attrs) { 335 this.attrs = attrs; 336 } 337 338 public final void setDiscontinuationReason(String codeValue) { 339 if (codes == null) 340 throw new IllegalStateException("codes not initialized"); 341 String codeMeaning = codes.getProperty(codeValue); 342 if (codeMeaning == null) 343 throw new IllegalArgumentException("undefined code value: " 344 + codeValue); 345 int endDesignator = codeValue.indexOf('-'); 346 Attributes attrs = new Attributes(3); 347 attrs.setString(Tag.CodeValue, VR.SH, 348 endDesignator >= 0 349 ? codeValue.substring(endDesignator + 1) 350 : codeValue); 351 attrs.setString(Tag.CodingSchemeDesignator, VR.SH, 352 endDesignator >= 0 353 ? codeValue.substring(0, endDesignator) 354 : "DCM"); 355 attrs.setString(Tag.CodeMeaning, VR.LO, codeMeaning); 356 this.discontinuationReason = attrs; 357 } 358 359 public void setTransferSyntaxes(String[] tss) { 360 rq.addPresentationContext( 361 new PresentationContext(1, UID.VerificationSOPClass, 362 UID.ImplicitVRLittleEndian)); 363 rq.addPresentationContext( 364 new PresentationContext(3, 365 UID.ModalityPerformedProcedureStepSOPClass, 366 tss)); 367 } 368 369 @SuppressWarnings("unchecked") 370 public static void main(String[] args) { 371 try { 372 CommandLine cl = parseComandLine(args); 373 Device device = new Device("mppsscu"); 374 Connection conn = new Connection(); 375 device.addConnection(conn); 376 ApplicationEntity ae = new ApplicationEntity("MPPSSCU"); 377 device.addApplicationEntity(ae); 378 ae.addConnection(conn); 379 final MppsSCU main = new MppsSCU(ae); 380 configureMPPS(main, cl); 381 CLIUtils.configureConnect(main.remote, main.rq, cl); 382 CLIUtils.configureBind(conn, main.ae, cl); 383 CLIUtils.configure(conn, cl); 384 main.remote.setTlsProtocols(conn.tlsProtocols()); 385 main.remote.setTlsCipherSuites(conn.getTlsCipherSuites()); 386 main.setTransferSyntaxes(CLIUtils.transferSyntaxesOf(cl)); 387 main.setAttributes(new Attributes()); 388 CLIUtils.addAttributes(main.attrs, cl.getOptionValues("s")); 389 main.setUIDSuffix(cl.getOptionValue("uid-suffix")); 390 List<String> argList = cl.getArgList(); 391 boolean echo = argList.isEmpty(); 392 if (!echo) { 393 System.out.println(rb.getString("scanning")); 394 main.scanFiles(argList, true); 395 } 396 ExecutorService executorService = 397 Executors.newSingleThreadExecutor(); 398 ScheduledExecutorService scheduledExecutorService = 399 Executors.newSingleThreadScheduledExecutor(); 400 device.setExecutor(executorService); 401 device.setScheduledExecutor(scheduledExecutorService); 402 try { 403 main.open(); 404 if (echo) 405 main.echo(); 406 else { 407 main.createMpps(); 408 main.updateMpps(); 409 } 410 } finally { 411 main.close(); 412 executorService.shutdown(); 413 scheduledExecutorService.shutdown(); 414 } 415 } catch (ParseException e) { 416 System.err.println("mppsscu: " + e.getMessage()); 417 System.err.println(rb.getString("try")); 418 System.exit(2); 419 } catch (Exception e) { 420 System.err.println("mppsscu: " + e.getMessage()); 421 e.printStackTrace(); 422 System.exit(2); 423 } 424 } 425 426 public void scanFiles(List<String> fnames, boolean printout) throws IOException { 427 428 if (printout) System.out.println(rb.getString("scanning")); 429 DicomFiles.scan(fnames, printout, new DicomFiles.Callback() { 430 431 @Override 432 public boolean dicomFile(File f, Attributes fmi, 433 long dsPos, Attributes ds) { 434 if (UID.ModalityPerformedProcedureStepSOPClass.equals( 435 fmi.getString(Tag.MediaStorageSOPClassUID))) { 436 return addMPPS( 437 fmi.getString(Tag.MediaStorageSOPInstanceUID), 438 ds); 439 } 440 return addInstance(ds); 441 } 442 }); 443 } 444 445 private static void configureMPPS(MppsSCU main, CommandLine cl) 446 throws Exception { 447 main.setNewPPSID(cl.hasOption("ppsid-new")); 448 main.setPPSUID(cl.getOptionValue("ppsuid")); 449 main.setPPSID(cl.getOptionValue("ppsid")); 450 if (cl.hasOption("ppsid-start")) 451 main.setPPSIDStart(Integer.parseInt(cl.getOptionValue("ppsid-start"))); 452 main.setPPSIDFormat(cl.getOptionValue("ppsid-format", "PPS-0000000000")); 453 main.setProtocolName(cl.getOptionValue("protocol", "UNKNOWN")); 454 if (cl.hasOption("archive")) 455 main.setArchiveRequested(cl.getOptionValue("archive")); 456 main.setCodes(CLIUtils.loadProperties( 457 cl.getOptionValue("code-config", "resource:code.properties"), 458 null)); 459 if (cl.hasOption("dc")) 460 main.setFinalStatus(DISCONTINUED); 461 if (cl.hasOption("dc-reason")) 462 main.setDiscontinuationReason(cl.getOptionValue("dc-reason")); 463 } 464 465 private static CommandLine parseComandLine(String[] args) 466 throws ParseException{ 467 Options opts = new Options(); 468 CLIUtils.addTransferSyntaxOptions(opts); 469 CLIUtils.addConnectOption(opts); 470 CLIUtils.addBindOption(opts, "MPPSSCU"); 471 CLIUtils.addAEOptions(opts); 472 CLIUtils.addResponseTimeoutOption(opts); 473 CLIUtils.addCommonOptions(opts); 474 addMPPSOptions(opts); 475 return CLIUtils.parseComandLine(args, opts, rb, MppsSCU.class); 476 } 477 478 @SuppressWarnings("static-access") 479 private static void addMPPSOptions(Options opts) { 480 opts.addOption(null, "ppsid-new", false, rb.getString("ppsid-new")); 481 opts.addOption(OptionBuilder 482 .hasArg() 483 .withArgName("uid") 484 .withDescription(rb.getString("ppsuid")) 485 .withLongOpt("ppsuid") 486 .create()); 487 opts.addOption(OptionBuilder 488 .hasArg() 489 .withArgName("id") 490 .withDescription(rb.getString("ppsid")) 491 .withLongOpt("ppsid") 492 .create()); 493 opts.addOption(OptionBuilder 494 .hasArg() 495 .withArgName("num") 496 .withDescription(rb.getString("ppsid-start")) 497 .withLongOpt("ppsid-start") 498 .create()); 499 opts.addOption(OptionBuilder 500 .hasArg() 501 .withArgName("pattern") 502 .withDescription(rb.getString("ppsid-format")) 503 .withLongOpt("ppsid-format") 504 .create()); 505 opts.addOption(OptionBuilder 506 .hasArg() 507 .withArgName("name") 508 .withDescription(rb.getString("protocol")) 509 .withLongOpt("protocol") 510 .create()); 511 opts.addOption(OptionBuilder 512 .hasArg() 513 .withArgName("YES|NO") 514 .withDescription(rb.getString("archive")) 515 .withLongOpt("archive") 516 .create()); 517 opts.addOption(null, "dc", false, rb.getString("dc")); 518 opts.addOption(OptionBuilder 519 .hasArg() 520 .withArgName("code-value") 521 .withDescription(rb.getString("dc-reason")) 522 .withLongOpt("dc-reason") 523 .create()); 524 opts.addOption(OptionBuilder 525 .hasArg() 526 .withArgName("file|url") 527 .withDescription(rb.getString("code-config")) 528 .withLongOpt("code-config") 529 .create()); 530 opts.addOption(OptionBuilder 531 .hasArgs() 532 .withArgName("[seq/]attr=value") 533 .withValueSeparator('=') 534 .withDescription(rb.getString("set")) 535 .create("s")); 536 opts.addOption(OptionBuilder 537 .hasArg() 538 .withArgName("suffix") 539 .withDescription(rb.getString("uid-suffix")) 540 .withLongOpt("uid-suffix") 541 .create(null)); 542 } 543 544 public void open() throws IOException, InterruptedException, 545 IncompatibleConnectionException, GeneralSecurityException { 546 as = ae.connect(remote, rq); 547 } 548 549 public void close() throws IOException, InterruptedException { 550 if (as != null) { 551 as.waitForOutstandingRSP(); 552 as.release(); 553 as.waitForSocketClose(); 554 } 555 } 556 557 public void echo() throws IOException, InterruptedException { 558 as.cecho().next(); 559 } 560 561 public void createMpps() throws IOException, InterruptedException { 562 for (MppsWithIUID mppsWithUID : map.values()) 563 createMpps(mppsWithUID); 564 as.waitForOutstandingRSP(); 565 } 566 567 private void createMpps(final MppsWithIUID mppsWithUID) 568 throws IOException, InterruptedException { 569 final String iuid = mppsWithUID.iuid; 570 Attributes mpps = mppsWithUID.mpps; 571 mppsWithUID.mpps = new Attributes(mpps, FINAL_MPPS_TOP_LEVEL_ATTRS); 572 mpps.setString(Tag.PerformedProcedureStepStatus, VR.CS, IN_PROGRESS); 573 for (int tag : CREATE_MPPS_TOP_LEVEL_EMPTY_ATTRS) 574 mpps.setNull(tag, dict.vrOf(tag)); 575 576 as.ncreate(UID.ModalityPerformedProcedureStepSOPClass, 577 iuid, mpps, null, rspHandlerFactory.createDimseRSPHandlerForNCreate(mppsWithUID)); 578 } 579 580 public void updateMpps() throws IOException, InterruptedException { 581 for (MppsWithIUID mppsWithIUID : created) 582 setMpps(mppsWithIUID); 583 } 584 585 private void setMpps(MppsWithIUID mppsWithIUID) 586 throws IOException, InterruptedException { 587 as.nset(UID.ModalityPerformedProcedureStepSOPClass, 588 mppsWithIUID.iuid, mppsWithIUID.mpps, null, rspHandlerFactory.createDimseRSPHandlerForNSet()); 589 } 590 591 public boolean addInstance(Attributes inst) { 592 CLIUtils.updateAttributes(inst, attrs, uidSuffix); 593 String suid = inst.getString(Tag.StudyInstanceUID); 594 if (suid == null) 595 return false; 596 MppsWithIUID mppsWithIUID = map.get(suid); 597 if (mppsWithIUID == null) 598 map.put(suid, mppsWithIUID = new MppsWithIUID(ppsuid(null), createMPPS(inst))); 599 updateMPPS(mppsWithIUID.mpps, inst); 600 return true; 601 } 602 603 public boolean addMPPS(String iuid, Attributes mpps) { 604 map.put(iuid, new MppsWithIUID(ppsuid(iuid), mpps)); 605 return true; 606 } 607 608 private String ppsuid(String defval) { 609 if (ppsuid == null) 610 return defval; 611 612 int size = map.size(); 613 switch (size) { 614 case 0: 615 return ppsuid; 616 case 1: 617 map.values().iterator().next().iuid += ".1"; 618 } 619 return ppsuid + '.' + (size + 1); 620 } 621 622 623 private String mkPPSID() { 624 if (ppsid != null) 625 return ppsid; 626 String id = ppsidFormat.format(serialNo); 627 if (++serialNo < 0) 628 serialNo = 0; 629 return id; 630 } 631 632 private Attributes createMPPS(Attributes inst) { 633 Attributes mpps = new Attributes(); 634 mpps.setString(Tag.PerformedStationAETitle, VR.AE, ae.getAETitle()); 635 mpps.setString(Tag.PerformedProcedureStepStartDate, VR.DA, 636 inst.getString(Tag.StudyDate, ppsStartDate)); 637 mpps.setString(Tag.PerformedProcedureStepStartTime, VR.TM, 638 inst.getString(Tag.StudyTime, ppsStartTime)); 639 for (int tag : MPPS_TOP_LEVEL_TYPE_2_ATTRS) 640 mpps.setNull(tag, dict.vrOf(tag)); 641 mpps.addSelected(inst, MPPS_TOP_LEVEL_ATTRS); 642 if (newPPSID || !mpps.containsValue(Tag.PerformedProcedureStepID)) 643 mpps.setString(Tag.PerformedProcedureStepID, VR.CS, mkPPSID()); 644 mpps.setString(Tag.PerformedProcedureStepEndDate, VR.DA, 645 mpps.getString(Tag.PerformedProcedureStepStartDate)); 646 mpps.setString(Tag.PerformedProcedureStepEndTime, VR.TM, 647 mpps.getString(Tag.PerformedProcedureStepStartTime)); 648 mpps.setString(Tag.PerformedProcedureStepStatus, VR.CS, finalStatus); 649 Sequence dcrSeq = mpps.newSequence(Tag.PerformedProcedureStepDiscontinuationReasonCodeSequence, 1); 650 if (discontinuationReason != null) 651 dcrSeq.add(new Attributes(discontinuationReason)); 652 653 Sequence raSeq = inst.getSequence(Tag.RequestAttributesSequence); 654 if (raSeq == null || raSeq.isEmpty()) { 655 Sequence ssaSeq = 656 mpps.newSequence(Tag.ScheduledStepAttributesSequence, 1); 657 Attributes ssa = new Attributes(); 658 ssaSeq.add(ssa); 659 for (int tag : SSA_TYPE_2_ATTRS) 660 ssa.setNull(tag, dict.vrOf(tag)); 661 ssa.addSelected(inst, SSA_ATTRS); 662 } else { 663 Sequence ssaSeq = 664 mpps.newSequence(Tag.ScheduledStepAttributesSequence, raSeq.size()); 665 for (Attributes ra : raSeq) { 666 Attributes ssa = new Attributes(); 667 ssaSeq.add(ssa); 668 for (int tag : SSA_TYPE_2_ATTRS) 669 ssa.setNull(tag, dict.vrOf(tag)); 670 ssa.addSelected(inst, SSA_ATTRS); 671 ssa.addSelected(ra, SSA_ATTRS); 672 } 673 } 674 mpps.newSequence(Tag.PerformedSeriesSequence, 1); 675 return mpps ; 676 } 677 678 private void updateMPPS(Attributes mpps, Attributes inst) { 679 String endTime = inst.getString(Tag.AcquisitionTime); 680 if (endTime == null) { 681 endTime = inst.getString(Tag.ContentTime); 682 if (endTime == null) 683 endTime = inst.getString(Tag.SeriesTime); 684 } 685 if (endTime != null && endTime.compareTo( 686 mpps.getString(Tag.PerformedProcedureStepEndTime)) > 0) 687 mpps.setString(Tag.PerformedProcedureStepEndTime, VR.TM, endTime); 688 Sequence prefSeriesSeq = mpps.getSequence(Tag.PerformedSeriesSequence); 689 Attributes prefSeries = getPerfSeries(prefSeriesSeq, inst); 690 Sequence refSOPSeq = prefSeries.getSequence(Tag.ReferencedImageSequence); 691 Attributes refSOP = new Attributes(); 692 refSOPSeq.add(refSOP); 693 refSOP.setString(Tag.ReferencedSOPClassUID, VR.UI, 694 inst.getString(Tag.SOPClassUID)); 695 refSOP.setString(Tag.ReferencedSOPInstanceUID, VR.UI, 696 inst.getString(Tag.SOPInstanceUID)); 697 } 698 699 private Attributes getPerfSeries(Sequence prefSeriesSeq, Attributes inst) { 700 String suid = inst.getString(Tag.SeriesInstanceUID); 701 for (Attributes prefSeries : prefSeriesSeq) { 702 if (suid.equals(prefSeries.getString(Tag.SeriesInstanceUID))) 703 return prefSeries; 704 } 705 Attributes prefSeries = new Attributes(); 706 prefSeriesSeq.add(prefSeries); 707 for (int tag : PERF_SERIES_TYPE_2_ATTRS) 708 prefSeries.setNull(tag, dict.vrOf(tag)); 709 prefSeries.setString(Tag.ProtocolName, VR.LO, protocolName); 710 prefSeries.addSelected(inst, PERF_SERIES_ATTRS); 711 prefSeries.newSequence(Tag.ReferencedImageSequence, 10); 712 if (archiveRequested != null) 713 prefSeries.setString(Tag.ArchiveRequested, VR.CS, archiveRequested); 714 return prefSeries; 715 } 716}