The StorageCommitmentSCP example demonstrates how to use DCF to implement the DICOM requirements of a storage requirement service class provider.
Classes
Class | Description | |
---|---|---|
Program |
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; } } }