The MWLSCP example demonstrates how to use DCF to implement the DICOM requirements of a modality worklist service class provider.
Classes
Class | Description | |
---|---|---|
Program |
Basic worklist service class provider example which should be run with MWLSCU.
| |
ProgramCallbackMWLServer |
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<ModalityWorklistRecord>, 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 } }