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
Class | Description | |
---|---|---|
Program |
Basic store service class provider example which should be run with the StoreSCU.
| |
ProgramCallbackStoreServer |
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; } } }