LAUREL BRIDGE

LaurelBridge.DCFExamples.StoreSCPExtended Namespace

DICOM Connectivity Framework V3.4
The StoreSCPExtended example demonstrates how to use the DCF to implement a simple store SCP by extending the stock StoreSCP that receives datasets and writes them to disk.
Classes

  ClassDescription
Public classOptions
Command line options class for parsing user options for StoreSCPExtended.
Public classProgram
Basic store service class provider example which should be run with the StoreSCU.

This example creates a class that extends the StoreSCP class and overrides the CStoreRq method in order to handle C-Store messages.

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.
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

StoreSCPExtended Sample Code
public class Program
{
    /// <summary>
    /// Main entry point for StoreSCPExtended.
    /// </summary>
    /// <param name="args">Program arguments.</param>
    [STAThread]
    public static void Main(string[] args)
    {
        try
        {
            Options options;
            if (!Options.TryParse(args, out options))
            {
                throw new ArgumentException("bad command line parameters");
            }

            if (string.IsNullOrEmpty(options.ImageStorageDirectory))
            {
                options.ImageStorageDirectory =
                    Path.Combine(
                        Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) ??
                        Environment.CurrentDirectory, "StoreSCPImages");
            }

            if (!Directory.Exists(options.ImageStorageDirectory))
                Directory.CreateDirectory(options.ImageStorageDirectory);

            // create an ExtendedSCPStoreServer that uses the specified port and will write to the specified image storage directory
            ExtendedStoreServer server = new ExtendedStoreServer(options.Port, options.ImageStorageDirectory);
            server.BeginListening();
            Console.WriteLine("Listening on {0}...", options.Port);
            Console.WriteLine("Received instances will be written to directory '{0}'", options.ImageStorageDirectory);
        }
        catch (Exception e)
        {
            Console.WriteLine("Error during execution: {0}", e);
            Environment.ExitCode = 1;
        }
        finally
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey();
            }
        }
    }

    /// <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;
        private readonly string _imageStorageDirectory;

        /// <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="imageStorageDirectory">The directory where images should be stored.</param>
        /// <param name="allowedPresentationContexts">The list of presentation contexts that will be allowed.</param>
        public ExtendedStoreServer(int port, string imageStorageDirectory, IList<AllowedPresentationContext> allowedPresentationContexts = null)
        {
            _imageStorageDirectory = imageStorageDirectory;
            _presentationContexts = allowedPresentationContexts;
            _manager = new AssociationManager();
            _manager.ServerHostAddress = "0.0.0.0";
            _manager.ServerTcpPort = port;
            _manager.AssociationConfigPolicyMgr = this;
            _manager.AddAssociationListener(this);
        }

        /// <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");
            }
        }

        #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)
        {
            return new DicomSessionSettings();
        }
        #endregion

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

    /// <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 : StoreSCP
    {
        private readonly string _imageStorageDirectory;

        /// <summary>
        /// Create a store handler with the given acceptor and presentation contexts.
        /// </summary>
        /// <remarks>
        /// See <see cref="LaurelBridge.DCF.Dicom.Store.StoreSCP"/> for a description of where the default presentation contexts are retrieved.
        /// </remarks>
        /// <param name="imageStorageDirectory">The directory where images should be stored. It will be created if it does not exist.</param>
        /// <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(string imageStorageDirectory, AssociationAcceptor acceptor, IList<AllowedPresentationContext> allowedPresentationContexts = null)
            : base(acceptor, allowedPresentationContexts, null)
        {
            _imageStorageDirectory = imageStorageDirectory;
        }

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

            try
            {
                string filename = Path.Combine(_imageStorageDirectory, request.Data.GetElementStringValue(Tags.SOPInstanceUID) + ".dcm");
                Console.WriteLine(
                    "MyExtendedStoreScp: patient's name is {0}, from {1}({2}) to {3}({4}) with transfer syntax {5}. Writing to {6}{7}",
                    request.Data.GetElementStringValue(Tags.PatientName),
                    Acceptor.AssociationInfo.CallingPresentationAddress,
                    Acceptor.AssociationInfo.CallingTitle,
                    Acceptor.AssociationInfo.CalledPresentationAddress,
                    Acceptor.AssociationInfo.CalledTitle,
                    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);
                }
            }
            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;
        }
    }
}