LAUREL BRIDGE

LaurelBridge.DCF.Examples.QuerySCU Namespace

DICOM Connectivity Framework V3.4
The QuerySCU example demonstrates how to use DCF to implement a query service class user.
Classes

  ClassDescription
Public classOptions
Command line options class for parsing user options for QuerySCU.
Public classProgram
Basic query service class user example which should be run with a Query SCP as well as a StoreSCP.

This example requires a Query SCP to be running. The Query SCP should be able to receive requests from this QuerySCU. Also, a Store SCP should be running to receive images requested to be moved by the Query SCU. The Query SCP should be set up to respond to MOVE requests by sending instances to this Store SCP. These SCPs may be run by using the QuerySCPExtended and StoreSCPExtended code.

StoreSCPExtended -p 105 -i path-to-ImagesReceivedForQuerySCU-folder
QuerySCPExtended -p 104 -i path-to-QuerySCPImages-folder -mp 105
After the above servers are running, a FIND for a patient or a MOVE of a Study Instance UID may then be performed.
QuerySCU -p 104 -u true FIND "Stephen^Miner"
QuerySCU -p 104 MOVE 1.2.123.4.454.62341.246.2

Examples

QuerySCU Sample Code
public class Program : IQueryListener
{
    private QRSCU _scu;
    private AssociationInfo _ainfo;
    private readonly bool _relationalRetrieve = true;

    private List<string> _studiesFoundList = new List<string>();

    /// <summary>
    /// The main entry point for QuerySCU.
    /// </summary>
    /// <param name="args">Program arguments.</param>
    [STAThread]
    public static void Main(string[] args)
    {
        try
        {
            Options options;
            if (!Options.TryParse(args, out options))
                return;


            Console.WriteLine("Expecting a QRSCP to be listening on {0}...", options.Port);

            Program queryScu = new Program();

            if (options.DoMove)
            {
                Console.WriteLine("Performing a MOVE using Study Instance UID (0020,000D) = {0}", options.MatchingAttribute);
                queryScu.ExecuteMove(options.Port, !options.ReturnEnumerable, options.MatchingAttribute);
                Console.WriteLine();
                Console.WriteLine("You may want to delete any instances just moved.");
            }
            else
            {
                Console.WriteLine("Performing a FIND using Patient's Name (0010,0010) = {0}", options.MatchingAttribute);
                queryScu.ExecuteQuery(options.Port, !options.ReturnEnumerable, options.MatchingAttribute);
            }

        }
        catch (Exception e)
        {
            Console.WriteLine("Error during execution: {0}", e);
        }
        finally
        {
            if (Debugger.IsAttached)
            {
                Console.Write("Press any key to continue . . . ");
                Console.ReadKey();
            }
        }
    }

    /// <summary>
    /// Execute a c-find request.
    /// </summary>
    /// <param name="qrScpPort">The port on which the Q/R SCP is expected to be listening.</param>
    /// <param name="useListener">
    /// Whether to use the FIND or MOVE method type that uses an IQueryListener or that directly returns an IEnumerable.
    /// When <c>True</c>, use an IQueryListener; when <c>False</c>, return an IEnumerable.
    /// </param>
    /// <param name="patientName">The string to send in a C-FIND request for the Patient's Name (0010,0010) to match.</param>
    /// <remarks>
    /// We take the simple approach in this example of specifying an ISO_IR 100 character set for the
    /// encoding of our query request, which is probably fine for 90% of the Western languages.  By a strict
    /// reading of the spec, the character set should only be specified if we actually require it in our
    /// query.  Note that the SCP is free to ignore and/or not support our character set.
    /// <para>
    /// If you need to encode additional and/or multibyte character sets, you may use the EncodingUtils.AnalyzeEncodings
    /// method in the CharacterSets namespace.
    /// </para>
    /// </remarks>
    public void ExecuteQuery(int qrScpPort, bool useListener, string patientName)
    {
        try
        {
            _ainfo = new AssociationInfo();
            _ainfo.CallingTitle = "SCU";
            _ainfo.CalledTitle = "SCP";
            _ainfo.CalledPresentationAddress = string.Format("localhost:{0}", qrScpPort);

            // Add the requested presentation context
            RequestedPresentationContext ctx = new RequestedPresentationContext(
                1,
                Uids.StudyRootQueryRetrieveInformationModelFIND,
                Uids.ELE, Uids.ILE);
            if (_relationalRetrieve)
            {
                byte[] extNeg = new byte[1];
                extNeg[0] = 1;
                ctx.SOPSpecificData = extNeg;
            }
            _ainfo.AddRequestedPresentationContext(ctx);

            // Make the SCU here
            _scu = new QRSCU(_ainfo);
            _scu.MaxReturnedResults = 100;
            _scu.QueryTimeoutSeconds = -1; // We only use the progress timer, not the absolute timer
            _scu.SendDimseTimeoutSeconds = 30;
            _scu.ReceiveDimseTimeoutSeconds = 30;
            _scu.ProgressTimeoutSeconds = 30;

            // Make the query identifier used in the C-Find
            QRIdentifier query = new QRIdentifier();
            // Fields to query on.
            query.PatientsName = patientName;
            query.QueryRetrieveLevel = "STUDY";

            // Fields to return.
            query.PatientId = "";
            query.PatientsSex = "";
            query.Modality = "";
            query.StudyDate = "";
            query.StudyInstanceUid = "";  // The value returned here is recorded for use later for a C-Move

            // Specify that this request contains ISO_IR 100 encoded data
            query.DataSet.Insert(Tags.SpecificCharacterSet, EncodingUtils.GetCharacterSetName(CharacterSetID.ISO_IR_100));

            Console.WriteLine("Query Data set is:{0}{1}{0}", Environment.NewLine, query.DataSet);

            _scu.RequestAssociation();
            if (useListener)
            {
                Console.WriteLine("Performing callback style C-Find ...{0}", Environment.NewLine);
                _scu.CFind(query.DataSet, this, true);
            }
            else
            {
                Console.WriteLine("Performing iterator style C-Find ...{0}", Environment.NewLine);
                IEnumerable<CFindResponse> results = _scu.CFindWithResults(query.DataSet);
                foreach (CFindResponse cFindResponse in results)
                {
                    if (cFindResponse is CFindPendingResponse)
                    {
                        ProcessPendingOrFinalResponse(cFindResponse);
                    }
                    else
                    {
                        ProcessOperationCompleteStatus(cFindResponse.Status);
                    }
                }
            }
            _scu.ReleaseAssociation();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error executing query: {0}{1}{1}Please verify the Query SCP is up and running.", ex, Environment.NewLine);
        }
    }

    private void ExecuteMove(int qrScpPort, bool useListener, string studyInstanceUid)
    {
        try
        {
            string moveDestination = "STORE_SCP";  // Populating with an example value; A Store SCP should be listening on port localhost:105 and accept this AE

            _ainfo = new AssociationInfo();
            _ainfo.CallingTitle = "SCU";
            _ainfo.CalledTitle = "SCP";
            _ainfo.CalledPresentationAddress = string.Format("localhost:{0}", qrScpPort);

            // Add the requested presentation context
            RequestedPresentationContext ctx = new RequestedPresentationContext(
                1,
                Uids.StudyRootQueryRetrieveInformationModelMOVE,
                Uids.ELE, Uids.ILE);
            if (_relationalRetrieve)
            {
                byte[] extNeg = new byte[1];
                extNeg[0] = 1;
                ctx.SOPSpecificData = extNeg;
            }
            _ainfo.AddRequestedPresentationContext(ctx);

            // Make the SCU here
            _scu = new QRSCU(_ainfo);
            _scu.MaxReturnedResults = 100;
            _scu.QueryTimeoutSeconds = -1; // We only use the progress timer, not the absolute timer
            _scu.SendDimseTimeoutSeconds = 30;
            _scu.ReceiveDimseTimeoutSeconds = 30;
            _scu.ProgressTimeoutSeconds = 30;

            // Make the query identifier used in the C-Move
            QRIdentifier query = new QRIdentifier();
            // Add C-Move level
            query.QueryRetrieveLevel = "STUDY";

            // Add C-Move Unique Key
            query.StudyInstanceUid = studyInstanceUid;

            Console.WriteLine("Query Data set is:{0}{1}{0}", Environment.NewLine, query.DataSet);
            Console.WriteLine("Move Destination is:{0}{1}", moveDestination, Environment.NewLine);


            _scu.RequestAssociation();
            if (useListener)
            {
                Console.WriteLine("Performing callback style C-Move ...{0}", Environment.NewLine);
                _scu.CMove(query.DataSet, moveDestination, this, true);
            }
            else
            {
                Console.WriteLine("Performing iterator style C-Move ...{0}", Environment.NewLine);
                IEnumerable<CMoveResponse> results = _scu.CMoveWithResults(query.DataSet, moveDestination);
                foreach (CMoveResponse cMoveResponse in results)
                {
                    if (cMoveResponse is CMovePendingResponse)
                    {
                        ProcessPendingOrFinalResponse(cMoveResponse);
                    }
                    else
                    {
                        ProcessOperationCompleteStatus(cMoveResponse.Status);
                    }
                }
            }
            _scu.ReleaseAssociation();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error executing query: {0}{1}{1}Please verify the Query SCP is up and running.", ex, Environment.NewLine);
        }
    }

    #region QueryListener Members
    /// <summary>
    /// This method is called for all incoming DIMSE response messages.
    /// </summary>
    /// <param name="rsp">The DIMSE response messages.</param>
    public virtual void QueryEvent(DimseMessage rsp)
    {
        ProcessPendingOrFinalResponse(rsp);
    }

    /// <summary>
    /// This method is called at the completion of the query.
    /// </summary>
    /// <param name="status">The DIMSE status of the final response.</param>
    public virtual void QueryComplete(int status)
    {
        ProcessOperationCompleteStatus(status);
    }
    #endregion

    private void ProcessPendingOrFinalResponse(DimseMessage rsp)
    {
        // This may be either a pending response or a final response
        Console.WriteLine("Got a reply:{0}{1}{0}", Environment.NewLine, rsp);

        string studyFound = rsp.Data.GetElementStringValue(Tags.StudyInstanceUID, string.Empty);

        if (!string.IsNullOrEmpty(studyFound))
        {
            _studiesFoundList.Add(studyFound);
        }
    }

    private void ProcessOperationCompleteStatus(int status)
    {
        Console.WriteLine("Received queryComplete event status = {0}", status);

        switch (status)
        {
            default:
            {
                Console.WriteLine("Operation status({0})", status);
                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_SUCCESS:
            {
                Console.WriteLine("Operation finished with no errors.");
                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_CANCELED:
            {
                Console.WriteLine("Operation was Canceled successfully");
                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_WARNING:
            {
                Console.WriteLine("Warning: some or all subops failed for C-MOVE");
                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_ERROR:
            {
                Console.WriteLine("Failure.");
                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_INTERNAL_ERROR:
            {
                Console.WriteLine("Internal Error. Association may still be connected. Aborting");

                if (_scu.Connected)
                {
                    _scu.AbortAssociation();
                }

                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_TIMEOUT:
            {
                Console.WriteLine("Operation timed out");

                if (_scu.Connected)
                {
                    _scu.AbortAssociation();
                }

                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_SUCCESS_MAX_RETURNED_RESULTS_REACHED:
            {
                Console.WriteLine("Success, Max Returned Results Reached");

                if (_scu.Connected)
                {
                    _scu.AbortAssociation();
                }

                break;
            }
            case QueryListenerStatus.QUERY_LISTENER_DIMSE_RECEIVE_TIMEOUT:
            {
                Console.WriteLine("Operation DIMSE receive timed out");

                if (_scu.Connected)
                {
                    _scu.AbortAssociation();
                }

                break;
            }
        }
    }

}