LAUREL BRIDGE

LaurelBridge.DCF.Examples.SampleVNA Namespace

DICOM Connectivity Framework V3.4
The SampleVNA example demonstrates how to use the DCF to provide multiple services on the same port.
Classes

  ClassDescription
Public classDatabase
Performs all the communication to/from the database.
Public classDatabaseRecord
Parent class representing the various database record types.
Public classImageRecord
A simple wrapper class that represents an image record in the database.
Public classPatientRecord
A simple wrapper class that represents a patient record in the database.
Public classProgram
The SampleVNA server is an example of providing services that would be required for a vendor neutral archive implementation.
Public classProgramSampleVNAServer
SampleVNAServer handles associations by creating and registering SCPs for the various types of DIMSE messages that will arrive.
Public classRemoteDevice
A simple wrapper class that represents remote devices that are allowed to communicate with the SampleVNA server, or destinations that the SampleVNA server will communicate with as a function of a C-Move request.
Public classSampleVNAQuerySCP
Sample VNA Query implementation.
Public classSampleVNAStoreSCP
SampleVNAStoreSCP writes received datasets to disk at the location specified by Program.SampleVNAServer.ImagePath, and inserts relevant (see the (Patient|Study|Series|Image)Record classes for what is relevant) tags into a four table patient/study/series/image database.
Public classSampleVNAVerificationSCP
Sample VNA Verification SCP implementation.
Public classSeriesRecord
A simple wrapper class that represents a series record in the database.
Public classStudyRecord
A simple wrapper class that represents a study record in the database.
Examples

SampleVNA Sample Code
public class Program
{
    /// <summary>
    /// Main entry point for SampleVNA.
    /// </summary>
    [STAThread]
    public static void Main()
    {
        try
        {
            // Create an SampleVNAServer that listens for associations on port 106
            SampleVNAServer sampleVNAServer = new SampleVNAServer(106);
            sampleVNAServer.BeginListening();
            Console.WriteLine("listening on {0}...", 106);

        }
        catch (Exception e)
        {
            Console.WriteLine("Error during execution: {0}", e);
            Environment.ExitCode = 1;
        }
    }

    /// <summary>
    /// SampleVNAServer handles associations by creating and registering SCPs for the various types of DIMSE messages that will arrive.
    /// </summary>
    public class SampleVNAServer : AssociationListenerAdapter, IAssociationConfigPolicyManager
    {
        private readonly AssociationManager _manager;
        private readonly IList<AllowedPresentationContext> _verificationPresentationContexts;
        private readonly IList<AllowedPresentationContext> _storePresentationContexts;
        private readonly IList<AllowedPresentationContext> _queryPresentationContexts;

        /// <summary>
        /// Gets the path to the images.
        /// </summary>
        public static string ImagePath
        {
            get
            {
                return Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) ?? ".",
                                    "SampleVNAImages");
            }
        }

        /// <summary>
        /// Constructor with port only
        /// </summary>
        /// <param name="port">The port to listen for connections.</param>
        public SampleVNAServer(int port)
            : this(port, null, null, null)
        {
            Database.ConnectionDictionary = new Dictionary<string, SqlCeConnection>();
            if (!Directory.Exists(ImagePath))
                Directory.CreateDirectory(ImagePath);
        }

        /// <summary>
        /// Constructor with port and allowed presentation contexts.
        /// </summary>
        /// <param name="port">Server port</param>
        /// <param name="allowedVerificationPresentationContexts">list of allowed verification presentation contexts</param>
        /// <param name="allowedStorePresentationContexts">list of allowed verification presentation contexts</param>
        /// <param name="allowedQueryPresentationContexts">list of allowed verification presentation contexts</param>
        public SampleVNAServer(int port, IList<AllowedPresentationContext> allowedVerificationPresentationContexts,
            IList<AllowedPresentationContext> allowedStorePresentationContexts,
            IList<AllowedPresentationContext> allowedQueryPresentationContexts)
        {
            _verificationPresentationContexts = allowedVerificationPresentationContexts;
            _storePresentationContexts = allowedStorePresentationContexts;
            _queryPresentationContexts = allowedQueryPresentationContexts;
            _manager = new AssociationManager();
            _manager.ServerTcpPort = port;
            _manager.AddAssociationListener(this);
            _manager.AssociationConfigPolicyMgr = this;
        }

        /// <summary>
        /// Returns a DicomSessionSettings object to be used for the association.
        /// </summary>
        /// <param name="assoc">The AssociationAcceptor for the given association</param>
        /// <returns>the DicomSessionSettings for the association</returns>
        public DicomSessionSettings GetSessionSettings(AssociationAcceptor assoc)
        {
            Console.WriteLine("Received request from {0}:{1} calling={2} called={3}", assoc.AssociationInfo.GetCallingHost(), assoc.AssociationInfo.GetCallingPort(), assoc.AssociationInfo.CallingTitle, assoc.AssociationInfo.CalledTitle);
            Exception retval;
            try
            {
                SqlCeConnection conn = new SqlCeConnection(Database.ConnectionString);
                conn.Open();
                Database.ConnectionDictionary[assoc.ConnectionID] = conn;

                IList<RemoteDevice> acceptableDevices = Database.GetRemoteDevices(conn);
                RemoteDevice device = acceptableDevices.FirstOrDefault(
                    x => x.CalledAE.Equals(assoc.AssociationInfo.CalledTitle) &&
                         x.CallingAE.Equals(assoc.AssociationInfo.CallingTitle) &&
                         x.Ip.Equals(assoc.AssociationInfo.GetCallingHost()));

                if (device != null)
                    return new DicomSessionSettings();

                if (!acceptableDevices.Any(x => x.CalledAE.Equals(assoc.AssociationInfo.CalledTitle))) // we didn't have an exact match, so see if any of the devices from the db match the called AE title
                    retval = new AssociationRejectedException(PduAssociateReject.Result.Permanent, PduAssociateReject.Source.ServiceUser, PduAssociateReject.Reason.BadCalled);
                else if (!acceptableDevices.Any(x => x.CallingAE.Equals(assoc.AssociationInfo.CallingTitle))) // see if any of the devices match the calling AE title
                    retval = new AssociationRejectedException(PduAssociateReject.Result.Permanent, PduAssociateReject.Source.ServiceUser, PduAssociateReject.Reason.BadCalling);
                else // the problem must be the ip address. Note that "NoReason" is the only acceptable reason when rejecting based on the ip address.
                    retval = new AssociationRejectedException(PduAssociateReject.Result.Transient, PduAssociateReject.Source.ServiceUser, PduAssociateReject.Reason.NoReason);
            }
            catch (SqlCeException e)
            {
                Console.WriteLine("Trouble trying to talk to the database: {0}{1}", Environment.NewLine, e);
                retval = e;
            }

            if (Database.ConnectionDictionary.ContainsKey(assoc.ConnectionID))
                Database.ConnectionDictionary.Remove(assoc.ConnectionID);

            throw retval;
        }

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

        /// <summary>
        /// Stop receiving any new associations.
        /// </summary>
        public void Stop()
        {
            _manager.Stop();
        }

        /// <summary>
        /// Callback for beginning the association.
        /// </summary>
        /// <param name="assoc">the AssociationAcceptor</param>
        public override void BeginAssociation(AssociationAcceptor assoc)
        {
            assoc.RegisterServiceClassProvider(new SampleVNAVerificationSCP(assoc, _verificationPresentationContexts));
            assoc.RegisterServiceClassProvider(new SampleVNAStoreSCP(assoc, _storePresentationContexts));
            assoc.RegisterServiceClassProvider(new SampleVNAQuerySCP(assoc, _queryPresentationContexts));
        }

        /// <summary>
        /// Callback for ending the association.
        /// </summary>
        /// <param name="assoc">the AssociationAcceptor</param>
        public override void EndAssociation(AssociationAcceptor assoc)
        {
            Database.ConnectionDictionary[assoc.ConnectionID].Close();
        }
    }
}