LAUREL BRIDGE

LaurelBridge.DCF.Examples.StoreSCP Namespace

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

  ClassDescription
Public classProgram
Basic store service class provider example which should be run with the StoreSCU.
Public classProgramCallbackStoreServer
CallbackStoreServer handles C-STORE requests on the associations by registering a callback that StoreSCP will call when the C-STORE request is received.
Public classProgramExtendedStoreServer
ExtendedSCPStoreServer handles associations by creating a custom SCP that extends StoreSCP to handle the C-STORE requests on the association.
Public classProgramMyExtendedStoreScp
MyExtendedStoreScp extends StoreSCP and overrides the CStoreRq method, which is called anytime a C-STORE request DIMSE message is received.
Examples

StoreSCP Sample Code
public class Program
{
    /// <summary>
    /// Main entry point for StoreSCP.
    /// </summary>
    [STAThread]
    public static void Main()
    {
        try
        {
            // create a CallbackStoreServer that listens for associations on port 104
            CallbackStoreServer server104 = new CallbackStoreServer(104);
            server104.BeginListening();
            Console.WriteLine("listening on {0}...", 104);

            // create a CallbackStoreServer that listens for associations on port 105.
            // only accept MR images that are ELE or ILE for this server
            IList<AllowedPresentationContext> allowed = new List<AllowedPresentationContext>();
            allowed.Add(new AllowedPresentationContext(Uids.MRImageStorage, new[] { Uids.TransferSyntax.ExplicitVRLittleEndian, Uids.TransferSyntax.ImplicitVRLittleEndian }));
            CallbackStoreServer server105 = new CallbackStoreServer(105, allowed);
            server105.BeginListening();
            Console.WriteLine("listening on {0}...", 105);

            // create an ExtendedSCPStoreServer that listens for associations on port 106
            ExtendedStoreServer server106 = new ExtendedStoreServer(106);
            server106.BeginListening();
            Console.WriteLine("listening on {0}...", 106);
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception caught during execution: {0}", e);
        }
    }

    /// <summary>
    /// CallbackStoreServer handles C-STORE requests on the associations by registering a callback that StoreSCP will call when the C-STORE request is received.
    /// </summary>
    public class CallbackStoreServer : AssociationListenerAdapter, IAssociationConfigPolicyManager
    {
        readonly AssociationManager _manager;
        readonly IList<AllowedPresentationContext> _presentationContexts;

        /// <summary>
        /// Constructs an CallbackStoreServer that listens on the specified port.
        /// </summary>
        /// <param name="port">The port number where we will listen for association requests</param>
        /// <param name="allowedPresentationContexts">The list of presentation contexts that will be allowed, or null to use defaults.</param>
        public CallbackStoreServer(int port, IList<AllowedPresentationContext> allowedPresentationContexts = null)
        {
            _presentationContexts = allowedPresentationContexts;
            _manager = new AssociationManager();
            _manager.ServerTcpPort = port;
            _manager.AssociationConfigPolicyMgr = this;
            _manager.AddAssociationListener(this);
        }

        /// <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>
        /// Start listening for associations via the AssociationManager.
        /// </summary>
        public void BeginListening()
        {
            Thread t = new Thread(_manager.Run);
            t.Start();
            if (!_manager.WaitForRunning(2000))
            {
                throw new TimeoutException("AssociationManager did not start in an acceptable amount of time");
            }
        }

        /// <summary>
        /// Creates a StoreSCP object to potentially 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 Dicom.Store.StoreSCP(assoc, _presentationContexts, CStore));
        }

        /// <summary>
        /// Writes some information from the incoming dataset to the console.
        /// </summary>
        /// <param name="acceptor">The AssociationAcceptor for the given association</param>
        /// <param name="request">The inbound CStoreRequest</param>
        /// <returns>A successful CStoreResponse</returns>
        private CStoreResponse CStore(AssociationAcceptor acceptor, CStoreRequest request)
        {
            CStoreResponse response = new CStoreResponse(request);
            string dir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) ?? ".";
            string filename = Path.Combine(dir, request.Data.GetElementStringValue(Tags.SOPInstanceUID) + ".dcm");
            Console.WriteLine("CallbackStoreServer: patient's name is {0} from {1} to port {2} with transfer syntax {3}. Writing to {4}{5}",
                request.Data.GetElementStringValue(Tags.PatientName),
                acceptor.AssociationInfo.CallingTitle,
                acceptor.AssociationInfo.CalledPresentationAddress,
                acceptor.AssociationInfo.GetAcceptedPresentationContext(request.ContextId).TransferSyntaxUid,
                filename,
                Environment.NewLine);

            // write the dataset out as a chapter 10 file to the same location as this example's executable
            using (DicomFileOutput dfo = new DicomFileOutput(filename, acceptor.AssociationInfo.GetAcceptedPresentationContext(request.ContextId).TransferSyntaxUid))
            {
                // persist the AE titles from the network connection in the ch10 file
                dfo.GetFileMetaInformation().SendingAETitle = acceptor.AssociationInfo.CallingTitle;
                dfo.GetFileMetaInformation().ReceivingAETitle = acceptor.AssociationInfo.CalledTitle;

                dfo.WriteDataSet(request.Data);
            }

            return response;
        }
    }

    /// <summary>
    /// ExtendedSCPStoreServer handles associations by creating a custom SCP that extends StoreSCP to handle the C-STORE requests on the association.
    /// </summary>
    public class ExtendedStoreServer : AssociationListenerAdapter, IAssociationConfigPolicyManager
    {
        private readonly AssociationManager _manager;
        private readonly IList<AllowedPresentationContext> _presentationContexts;

        /// <summary>
        /// Constructs an ExtendedSCPStoreServer that listens on the specified port.
        /// </summary>
        /// <param name="port">The port number where we will listen for association requests</param>
        /// <param name="allowedPresentationContexts">The list of presentation contexts that will be allowed</param>
        public ExtendedStoreServer(int port, IList<AllowedPresentationContext> allowedPresentationContexts = null)
        {
            _presentationContexts = allowedPresentationContexts;
            _manager = new AssociationManager();
            _manager.ServerTcpPort = port;
            _manager.AssociationConfigPolicyMgr = this;
            _manager.AddAssociationListener(this);
        }

        /// <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>
        /// Start listening for associations via the AssociationManager.
        /// </summary>
        public void BeginListening()
        {
            Thread t = new Thread(_manager.Run);
            t.Start();
            if (!_manager.WaitForRunning(2000))
            {
                throw new TimeoutException("AssociationManager did not start in an acceptable amount of time");
            }
        }

        /// <summary>
        /// Creates a MyExtendedStoreScp object to potentially 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 MyExtendedStoreScp(assoc, _presentationContexts));
        }
    }

    /// <summary>
    /// MyExtendedStoreScp extends StoreSCP and overrides the CStoreRq method, which is called anytime a C-STORE request DIMSE message is received.
    /// </summary>
    public class MyExtendedStoreScp : Dicom.Store.StoreSCP
    {
        /// <summary>
        /// Create a store handler with the given acceptor and presentation contexts.
        /// </summary>
        /// <param name="acceptor">The association acceptor.</param>
        /// <param name="allowedPresentationContexts">The allowed presentation contexts which my be null to specify the default storage classes.</param>
        public MyExtendedStoreScp(AssociationAcceptor acceptor, IList<AllowedPresentationContext> allowedPresentationContexts = null)
            : base(acceptor, allowedPresentationContexts, null)
        {
        }

        /// <summary>
        /// Writes some information from the incoming dataset to the console.
        /// </summary>
        /// <param name="request">The inbound DimseMessage</param>
        /// <returns>A DimseMessage response</returns>
        public override CStoreResponse CStoreRq(CStoreRequest request)
        {
            CStoreResponse response = new CStoreResponse(request);

            string dir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) ?? ".";
            string filename = Path.Combine(dir, request.Data.GetElementStringValue(Tags.SOPInstanceUID) + ".dcm");
            Console.WriteLine("MyExtendedStoreScp: patient's name is {0} from {1} to port {2} with transfer syntax {3}. Writing to {4}{5}",
                request.Data.GetElementStringValue(Tags.PatientName),
                Acceptor.AssociationInfo.CallingTitle,
                Acceptor.AssociationInfo.CalledPresentationAddress,
                Acceptor.AssociationInfo.GetAcceptedPresentationContext(request.ContextId).TransferSyntaxUid,
                filename,
                Environment.NewLine);

            // write the dataset out as a chapter 10 file to the same location as this example's executable
            using (DicomFileOutput dfo = new DicomFileOutput(filename, Acceptor.AssociationInfo.GetAcceptedPresentationContext(request.ContextId).TransferSyntaxUid))
            {
                // persist the AE titles from the network connection in the ch10 file
                dfo.GetFileMetaInformation().SendingAETitle = Acceptor.AssociationInfo.CallingTitle;
                dfo.GetFileMetaInformation().ReceivingAETitle = Acceptor.AssociationInfo.CalledTitle;

                dfo.WriteDataSet(request.Data);
            }
            return response;
        }
    }
}