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}