LAUREL BRIDGE

LaurelBridge.DCF.Examples.MWLSCP Namespace

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

  ClassDescription
Public classProgram
Basic worklist service class provider example which should be run with MWLSCU.
Public classProgramCallbackMWLServer
An MWL server that extends AssociationListenerAdapter and implements the IAssociationConfigPolicyManager.
Examples

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

    /// <summary>
    /// The main entry point for MWLSCP.
    /// </summary>
    [STAThread]
    public static void Main()
    {
        try
        {
            // By specifying null for the list of allowed presentation contexts, the SCP will get the
            // default list from the configuration file
            CallbackMWLServer mwlServer = new CallbackMWLServer(null);

            AssociationManager mgr = new AssociationManager();
            mgr.ServerTcpPort = 104;
            mgr.AssociationConfigPolicyMgr = mwlServer;
            mgr.AddAssociationListener(mwlServer);

            // AssociationManager.run() blocks, so start him listening for connections on a separate thread
            Thread t = new Thread(new ThreadStart(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}...", 104);
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception caught during execution: {0}", e);
        }
    }

    /// <summary>
    /// An MWL server that extends <see cref="AssociationListenerAdapter"/> and implements the
    /// <see cref="IAssociationConfigPolicyManager"/>.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The <see cref="IAssociationConfigPolicyManager"/> allows us to get a callback to set our
    /// session settings for the association.
    /// </para>
    /// <para>
    /// This class overrides the <see cref="AssociationListenerAdapter.BeginAssociation"/> method
    /// to install a service class provider which negotiates the association, and delegates service
    /// class specific messages back to us for processing.  Other <see cref="AssociationListenerAdapter"/>
    /// methods may be overridden to implement additional functionality.
    /// </para>
    /// </remarks>
    public class CallbackMWLServer : AssociationListenerAdapter, IAssociationConfigPolicyManager
    {
        private readonly IList<AllowedPresentationContext> _presentationContexts;

        private bool CancelRequested { get; set; }

        /// <summary>
        /// Creates a CallbackMWLServer with the specified list of allowed presentation contexts.
        /// </summary>
        /// <param name="allowedPresentationContexts">The list of presentation contexts that will be allowed.</param>
        public CallbackMWLServer(IList<AllowedPresentationContext> allowedPresentationContexts)
        {
            _presentationContexts = allowedPresentationContexts;
        }

        #region IAssociationConfigPolicyManager Overrides
        /// <summary>
        /// Returns a DicomSessionSettings object to be used for the association.
        /// </summary>
        /// <param name="assoc">The AssociationAcceptor handling the association.</param>
        /// <returns>The DicomSessionSettings for the given association.</returns>
        public DicomSessionSettings GetSessionSettings(AssociationAcceptor assoc)
        {
            return new DicomSessionSettings() { SessionName = String.Format("MWLSCP: {0}", assoc.DicomSocket.ConnectionID) };
        }
        #endregion

        #region AssociationListener Overrides
        /// <summary>
        /// Creates the MWLSCP object to handle the association that caused this method to be called.
        /// </summary>
        /// <param name="assoc">The association acceptor for the given association</param>
        public override void BeginAssociation(AssociationAcceptor assoc)
        {
            Logger.InfoFormat("beginAssociation: connection id = {1}{0}AssociationInfo = {0}{2}",
                Environment.NewLine, assoc.ConnectionID, assoc.AssociationInfo);
            Dicom.Worklist.MWLSCP scp = new DCF.Dicom.Worklist.MWLSCP(assoc, _presentationContexts, CFind, CCancel);
            assoc.RegisterServiceClassProvider(scp);
        }
        #endregion

        #region Helper Delegates
        /// <summary>
        /// Create and send some canned responses to the SCU.
        /// </summary>
        /// <param name="acceptor">The AssociationAcceptor for the given association</param>
        /// <param name="request">The inbound CFindRequest</param>
        /// <returns>A successful CStoreResponse</returns>
        private IEnumerable<CFindResponse> CFind(AssociationAcceptor acceptor, CFindRequest request)
        {
            Console.WriteLine("CallbackMWLServer.CFind: {0} to port {1}:{2}{3}",
                acceptor.AssociationInfo.CallingTitle, acceptor.AssociationInfo.CalledPresentationAddress, Environment.NewLine, request.Data);

            foreach (DicomDataSet result in FindMatchingDataSets(request.Data))
            {
                if (CancelRequested)
                    break;

                CFindPendingResponse pending = new CFindPendingResponse(request, result);
                Console.WriteLine("CallbackMWLServer: sending C-Find pending response dataset:{0}{1}", Environment.NewLine, pending);
                yield return pending;
            }

            CFindFinalResponse finalResponse = new CFindFinalResponse(request);
            if (CancelRequested)
                finalResponse.Status = DimseStatus.CANCEL;

            Console.WriteLine("CallbackMWLServer: sending the C-Find final response dataset:{0}{1}", Environment.NewLine, finalResponse);
            yield return finalResponse;
        }

        private void CCancel(AssociationAcceptor acceptor, CCancelRequest request)
        {
            Console.WriteLine("CallbackMWLServer.CCancel: got a cancel request from ae({0}) at address({1})",
                acceptor.AssociationInfo.CallingTitle, acceptor.AssociationInfo.CalledPresentationAddress);
            CancelRequested = true;
        }

        /// <summary>
        /// This method is where you would find your matching datasets based upon the query request.
        /// </summary>
        /// <remarks>
        /// An MWLServer will have a backing database or service that provides the information that
        /// is returned by the server.
        /// </remarks>
        /// <remarks>
        /// <para>
        /// Please note that the implementation of this method in this example has been designed for simplicity as it
        /// pertains to the user of the DCF running the examples without needing to set up / configure a database or some
        /// other backing store. This example implementation should NOT be used in a real world use case.
        /// </para>
        /// <para>
        /// One of the reasons this implementation should NOT be used is because it loads full DicomDataSet objects
        /// into memory, including the pixel data via the line 'dds.ExpandStreamingModeData(true);'.
        /// </para>
        /// <para>
        /// In addtion, the dataset items that are being returned here are not actually worklist items.  A real implementation
        /// would be listening to HL7 feeds, or using other methods to populate the worklist entries that would be searched
        /// by the worklist query.
        /// </para>
        /// <para>
        /// A more real-world implementation would return not full DicomDataSets, but information that represented records
        /// from a database or some other backing store; for example, IEnumerable&lt;ModalityWorklistRecord&gt;, where ModalityWorklistRecord
        /// objects were defined by the application developer and contained the information necessary to populate the
        /// CFindResponse objects.
        /// </para>
        /// <para>
        /// See the DICOM specification, chapter 4, annex C for Query/Retrieve information. Specifically, see section C.6 for
        /// a description of the unique, required, and optional tags.
        /// </para>
        /// </remarks>
        /// <param name="query">The CFind query request.</param>
        /// <returns>An enumeration of DicomDataSet objects that match the query.</returns>
        private IEnumerable<DicomDataSet> FindMatchingDataSets(DicomDataSet query)
        {
            // TODO: replace with code to find matching datasets using the query dataset
            string location = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? ".";
            foreach (string fileName in Directory.GetFiles(location, "*.dcm"))
            {
                using (DicomFileInput dfi = new DicomFileInput(fileName))
                {
                    DicomDataSet dds = dfi.ReadDataSet();
                    // since we are forcing the DicomFileInput to close (ala using) expand streaming mode data here
                    dds.ExpandStreamingModeData(true);

                    // Use the DicomDataSet Compare method in Query Mode (2) 
                    if (dds.Compare(query, 2))
                    {
                        yield return dds;
                    }
                }
            }
        }
        #endregion
    }
}