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
Class | Description | |
---|---|---|
Options |
Command line options class for parsing user options for StoreSCPExtended.
| |
Program |
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. | |
ProgramExtendedStoreServer |
ExtendedSCPStoreServer handles associations by creating a custom SCP that extends StoreSCP to handle the C-STORE requests on the association.
| |
ProgramMyExtendedStoreScp |
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; } } }