LAUREL BRIDGE

LaurelBridge.DCFExamples.StorageCommitmentSCP Namespace

DICOM Connectivity Framework V3.4
The StorageCommitmentSCP example demonstrates how to use DCF to implement the DICOM requirements of a storage requirement service class provider.
Classes

  ClassDescription
Public classProgram
Storage commitment service class provider example which should be run with the StorageCommitmentSCU example.
Remarks

Supported OS Platforms:

  • Windows - .Net Framework 4.7.2 64-bit and 32-bit
  • Windows - .Net Core 2.1 64-bit and 32-bit
  • Linux - .Net Core 2.1 64-bit

Examples

StorageCommitmentSCP Sample Code
public class Program
{
    private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

    /// <summary>
    /// Main entry point for StorageCommitmentSCP.
    /// </summary>
    [STAThread]
    public static void Main()
    {
        try
        {
            // Create the Storage Commitment server, which constitutes the main SCP functionality
            CallbackStorageCommitmentServer server = new CallbackStorageCommitmentServer();

            // Create the association manager that will handle incoming associations from the SCU
            AssociationManager mgr = new AssociationManager();
            mgr.ServerTcpPort = 10104;
            mgr.AssociationConfigPolicyMgr = server;
            mgr.AddAssociationListener(server);

            // AssociationManager.run() blocks, so start him listening for connections on a separate thread
            Thread t = new Thread(mgr.Run);
            t.Start();
            if (!mgr.WaitForRunning(2000))
            {
                throw new TimeoutException("AssociationManager did not start in an acceptable amount of time");
            }

            Console.WriteLine("listening on {0}...", 10104);
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception caught during execution: {0}", e);
            Environment.ExitCode = 1;
        }
    }

    /// <summary>
    /// Class used to handle communication with the StorageCommitment SCU. 
    /// </summary>
    internal class CallbackStorageCommitmentServer : AssociationListenerAdapter, IAssociationConfigPolicyManager
    {
        readonly IList<AllowedPresentationContext> _presentationContexts;

        /// <summary>
        /// Constructs a CallbackStorageCommitmentServer.
        /// </summary>
        public CallbackStorageCommitmentServer()
            : this(null)
        {
        }

        /// <summary>
        /// Constructs a CallbackStoreServer using the list of allowed 
        /// presentation contexts for incoming associations.
        /// </summary>
        /// <param name="allowedPresentationContexts">The list of presentation contexts that will be allowed.</param>
        public CallbackStorageCommitmentServer(IList<AllowedPresentationContext> allowedPresentationContexts)
        {
            _presentationContexts = allowedPresentationContexts;
        }

        /// <summary>
        /// Returns a DicomSessionSettings object to be used for the association.
        /// </summary>
        /// <param name="assoc">The AssociationAcceptor for the given association.</param>
        /// <returns>A default DicomSessionSettings object.</returns>
        public DicomSessionSettings GetSessionSettings(AssociationAcceptor assoc)
        {
            return new DicomSessionSettings();
        }

        /// <summary>
        /// Creates a StorageCommitmentNActionSCP object to handle the association that caused this 'beginAssociation' method to be called.
        /// </summary>
        /// <param name="assoc">The AssociationAcceptor for the given association.</param>
        public override void BeginAssociation(AssociationAcceptor assoc)
        {
            assoc.RegisterServiceClassProvider(new StorageCommitmentNActionSCP(assoc, _presentationContexts, NAction));
        }

        /// <summary>
        /// Handle the incoming NAction request for a storage commitment.
        /// </summary>
        /// <param name="acceptor">The AssociationAcceptor for the given association.</param>
        /// <param name="request">The inbound NActionRequest</param>
        /// <returns>The successful NActionResponse.</returns>
        public NActionResponse NAction(AssociationAcceptor acceptor, NActionRequest request)
        {
            Logger.InfoFormat("Incoming NAction Request: {0}{1}", Environment.NewLine, request);
            NActionResponse response = new NActionResponse(request);

            // Perform the actual store commit on another thread. This is the thread that
            // will send the NEventReportRequest back to the SCU when the store commit has finished.
            // Note that this NEventReportRequest does not have to be on the same association the NActionRequest 
            // came on because of the fact the SCU is not obligated to keep the association active.
            Thread scThread = new Thread(() => SendNEventReportRequest(acceptor, request));
            scThread.Start();

            Logger.InfoFormat("Outgoing NAction Response: {0}{1}", Environment.NewLine, response);
            return response;
        }

        /// <summary>
        /// Here is where the actual storage commitment occurs. Once completed, the NEventReportReq 
        /// is sent to the SCU using the association information from the given association acceptor.
        /// </summary>
        /// <param name="acceptor">The association acceptor from the NAction request, containing the information needed
        /// to send a request back to the StorageCommitment SCU.</param>
        /// <param name="request">The incoming NActionRequest, containing the data to be stored.</param>
        private void SendNEventReportRequest(AssociationAcceptor acceptor, NActionRequest request)
        {
            StorageCommitmentRequest scRequest = new StorageCommitmentRequest(request.Data);
            StorageCommitmentResult scResult = new StorageCommitmentResult();

            StorageCommitmentEventType eventTypeId = PerformStorageCommitment(scRequest, scResult);

            NEventReportRequest reportRequest = new StorageCommitmentNEventReportRequest(scResult.DataSet, eventTypeId);
            AssociationInfo nEventAInfo = CreateNEventAssociationInfo(acceptor);
            reportRequest.ContextId = nEventAInfo.RequestedPresentationContextList[0].Id;
            AssociationRequester requester = null;

            try
            {
                requester = new AssociationRequester(nEventAInfo);
                requester.RequestAssociation();
                Logger.InfoFormat("Outgoing NEventReportRequest: {0}{1}", Environment.NewLine, reportRequest);

                requester.SendDimseMessage(reportRequest, -1);

                DimseMessage reportResponse = requester.ReceiveDimseMessage(nEventAInfo.AcceptedPresentationContextList[0].Id, -1);
                Logger.InfoFormat("Incoming NEventReportResponse: {0}{1}", Environment.NewLine, reportResponse);
            }
            finally
            {
                if (requester != null && requester.Connected)
                    requester.ReleaseAssociation();
            }
        }

        /// <summary>
        /// Perform the requested storage commitment.
        /// </summary>
        /// <param name="request">The StorageCommitmentRequest message received from the NAction request.</param>
        /// <param name="result">Fill in this StorageCommitmentResult object with the results of the commit.</param>
        /// <returns>The event type id. 1 for all instances committed, 2 if there were any failures.</returns>
        private StorageCommitmentEventType PerformStorageCommitment(StorageCommitmentRequest request, StorageCommitmentResult result)
        {
            StorageCommitmentEventType eventTypeId = StorageCommitmentEventType.RequestCompleteFailuresExist; // because we are adding a failed instance below

            List<ReferencedSopSequence> successSops = new List<ReferencedSopSequence>();
            List<FailedSopSequence> failedSops = new List<FailedSopSequence>();

            // Run the list of Referenced SOP Sequences that were in the N-ACTION-RQ and commit them.
            for (int i = 0; i < request.GetReferencedSopSequenceCount(); i++)
            {
                ReferencedSopSequence seq = request.ReferencedSopSequence(i);
                string refSopUid = seq.ReferencedSopClassUid.Trim(MiscUtil.DicomTrimChars);
                string refSopInstanceUid = seq.ReferencedSopInstanceUid.Trim(MiscUtil.DicomTrimChars);

                // Here is where the actual Store would occur. Had we actually attempted to store here,
                // we could have checked the file was successfully written to disk, and report those sops that failed.
                if (refSopUid.StartsWith("1.2.840"))
                    successSops.Add(seq);
                else // add our bogus image to the list of failures
                {
                    FailedSopSequence failedSeq = new FailedSopSequence();
                    failedSeq.ReferencedSopClassUid = refSopUid;
                    failedSeq.ReferencedSopInstanceUid = refSopInstanceUid;
                    failedSops.Add(failedSeq);
                }
            }

            if (successSops.Count > 0)
            {
                result.ReferencedSopSequence(successSops.ToArray());
            }

            if (failedSops.Count > 0)
            {
                result.FailedSopSequence(failedSops.ToArray());
            }

            // don't forget to set the transaction uid in the result
            result.TransactionUid = request.TransactionUid;
            return eventTypeId;
        }

        /// <summary>
        /// Create the AssociationInfo to be used to send the NEventReq to the SCU notifying of the completion of
        /// the storage commitment action.
        /// </summary>
        /// <param name="acceptor">The incoming AssociationAcceptor for the NActionReq containing information from the SCU.</param>
        /// <returns>The AssociationInfo to be used to communicate with the SCU on a new association.</returns>
        private static AssociationInfo CreateNEventAssociationInfo(AssociationAcceptor acceptor)
        {
            string callingTitle = acceptor.AssociationInfo.CallingTitle;
            AEEntry defaultAEEntry = new AEEntry(new DicomAddress("localhost", 10105, callingTitle), new DicomSessionSettings());
            IAEEntry aeEntry = DataDictionary.GetAEEntryForName(callingTitle, defaultAEEntry);

            AssociationInfo nEventAInfo = new AssociationInfo();
            nEventAInfo.CallingTitle = acceptor.AssociationInfo.CalledTitle;
            nEventAInfo.CalledTitle = aeEntry.AETitle;
            nEventAInfo.CalledPresentationAddress = aeEntry.Host + ":" + aeEntry.Port;

            RequestedPresentationContext ctx = new RequestedPresentationContext(
                1,
                Uids.StorageCommitmentPushModelSOPClass,
                Uids.ELE, Uids.ILE);
            nEventAInfo.AddRequestedPresentationContext(ctx);
            return nEventAInfo;
        }
    }
}