LAUREL BRIDGE

LaurelBridge.DCFExamples.StoreSCPCallback Namespace

DICOM Connectivity Framework V3.4
The StoreSCPCallback example demonstrates how to use the DCF to implement a simple store SCP using a callback method that receives datasets and writes them to disk.
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.
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

StoreSCPCallback Sample Code
public class Program
{
    /// <summary>
    /// Main entry point for StoreSCPCallback.
    /// </summary>
    [STAThread]
    public static void Main()
    {
        try
        {
            // Specify a list of AllowedPresentationContext objects that will restrict what combinations of SOP class and transfer syntaxes will be allowed.
            IList<AllowedPresentationContext> allowed = new List<AllowedPresentationContext>();
            allowed.Add(new AllowedPresentationContext(Uids.MRImageStorage, Uids.TransferSyntax.ExplicitVRLittleEndian, Uids.TransferSyntax.ImplicitVRLittleEndian));

            // create a CallbackStoreServer that listens for associations on port 10104
            CallbackStoreServer server10104 = new CallbackStoreServer(10104, allowed);
            server10104.BeginListening();
            Console.WriteLine("listening on {0}...", 10104);
        }
        catch (Exception e)
        {
            Console.WriteLine("Error during execution: {0}", e);
            Environment.ExitCode = 1;
        }
    }

    /// <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
    {
        /// <summary>
        /// Directory where files are saved unless Discard is true.
        /// </summary>
        private static readonly string AssemblyDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()?.Location) ?? ".";

        /// <summary>
        /// Look for the presence of the file StoreSCPCallback_Discard to read and discard datasets as they are received.
        /// This lets the SCP receive C-Stores as fast as possible without disk write overhead.
        /// </summary>
        private static readonly bool Discard = File.Exists(Path.Combine(AssemblyDir, "StoreSCPCallback_Discard"));

        private readonly AssociationManager _manager;
        private readonly IList<AllowedPresentationContext> _presentationContexts;

        /// <summary>
        /// Constructs a CallbackStoreServer that will listen on the specified port and accept the allowedPresentationContexts.
        /// </summary>
        /// <remarks>
        /// See <see cref="LaurelBridge.DCF.Dicom.Store.StoreSCP"/> for a description of where the default presentation contexts are retrieved.
        /// </remarks>
        /// <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);
            if (Discard)
                Console.WriteLine("StoreSCPCallback will discard received datasets");
            else
                Console.WriteLine("StoreSCPCallback will save received datasets to {0}", AssemblyDir);
        }

        /// <summary>
        /// Start the AssociationManager for this server, and start listening for associations.
        /// </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");
            }
        }

        #region IAssociationConfigPolicyManager implementation
        /// <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)
        {
            DicomSessionSettings ss = new DicomSessionSettings();
            // If we are discarding, only log error and fatal messages
            if (Discard)
                ss.LevelFlags = LogLevelFlags.Fatal | LogLevelFlags.Error;
            return ss;
        }
        #endregion

        #region AssociationListenerAdapter overrides
        /// <summary>
        /// Override of BeginAssociation which is called during association negotiation.  Register a StoreSCP object to
        /// handle store requests for our allowed presentation contexts by delegating to our CStore method.
        /// </summary>
        /// <param name="assoc">The AssociationAcceptor for the given association</param>
        public override void BeginAssociation(AssociationAcceptor assoc)
        {
            assoc.RegisterServiceClassProvider(new StoreSCP(assoc, _presentationContexts, CStore));
        }
        #endregion

        /// <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 DIMSE message</param>
        /// <returns>A successful CStoreResponse</returns>
        private CStoreResponse CStore(AssociationAcceptor acceptor, CStoreRequest request)
        {
            CStoreResponse response = new CStoreResponse(request);

            try
            {
                string instanceUid = request.Data.GetElementStringValue(Tags.SOPInstanceUID);
                string filename = Path.Combine(AssemblyDir,  instanceUid + ".dcm");

                if (Discard)
                {
                    // read and discard streaming mode data.
                    request.Data.ExpandStreamingModeData(false);
                    // write the instance uid to console
                    // to drop all console output (faster) redirect output to nul device on command line
                    Console.WriteLine(instanceUid);
                }
                else
                {
                    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 chapter10 file
                        dfo.GetFileMetaInformation().SendingAETitle = acceptor.AssociationInfo.CallingTitle;
                        dfo.GetFileMetaInformation().ReceivingAETitle = acceptor.AssociationInfo.CalledTitle;

                        dfo.WriteDataSet(request.Data);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("CStore Failed: {0}", e);
                response.Status = DimseStatus.PROCESSING_FAILURE;
                response.ErrorComment = "Failed while trying to store. Error is: " + e.Message;
            }

            return response;
        }
    }
}