Converting from DicomQuery to DicomConnection for Q/R
Note: This page applies only to the COM version of DicomObjects - the threading model of the equivalent DicomAssociation object in the .NET version is very different
All the DicomQuery-based operations can also be performed using a DicomConnection object, and doing so has several benefits:
- It allows finer control over the exact presentation contexts offered
- Multiple operations can occur on one association
- Perhaps most importantly, if the DicomConnection is asynchronous, then it provides the best solution to using C-MOVE in DicomObjects, avoiding the problems of deadlocking or lack of return status which affect some of the alternatives.
- It provides better error reporting generally, with full access to all the returned status information.
The equivalent methods are as shown in this table:
DICOM Operation | DicomQuery | DicomConnection |
---|---|---|
C-FIND | DoQuery & DoRawQuery | Find |
C-GET | GetImages | Get |
C-MOVE | MoveImages, MoveSync & GetUsingMove | Move |
When used on an asynchronous DicomConnection these methods send out a C-MOVE request and return immediately. For C-MOVE it is important that an asynchronous DicomConneciton is used, and a separate article DicomConnection.Move gives more details, but for those simply wishing to get more information from C-FIND or C-GET. then a synchronous DicomConnection object, simply created using ‘New DicomConnection’ - will suffice.
There are several steps in converting a simple DicomQuery based operation to one based on DicomConnection:
Create a DicomConnection object using either of the methods below:
Synchronous
connection = new DicomConnection
connection = [DicomViewer or DicomServer object].New("DicomConnection")
Add the presentation contexts you wish to use
This is the section which needs a little thinking about, as it replaces work that is done for you internally in DicomQuery, but it is not difficult! Simply pick the SOP class(es) you wish to use from the table below. If using VB or any other language which properly uses COM constants then the “names” are easiest and clearest in your code but in other languages (e.g. those from Borland) you may need to use the string version of the UID instead:
Operation | Root | Name | UID |
---|---|---|---|
C-FIND | PATIENT | doSOP PatientRootQR FIND | 1.2.840.10008.5.1.4.1.2.1.1 |
C-MOVE | PATIENT | doSOP PatientRootQR MOVE | 1.2.840.10008.5.1.4.1.2.1.2 |
C-GET | PATIENT | doSOP PatientRootQR GET | 1.2.840.10008.5.1.4.1.2.1.3 |
C-FIND | STUDY | doSOP StudyRootQR FIND | 1.2.840.10008.5.1.4.1.2.2.1 |
C-MOVE | STUDY | doSOP StudyRootQR MOVE | 1.2.840.10008.5.1.4.1.2.2.2 |
C-GET | STUDY | doSOP StudyRootQR GET | 1.2.840.10008.5.1.4.1.2.2.3 |
Having decided which to use, add them to the DicomConnection.Contexts collection - e.g.
connection.Contexts.Add doSOP_PatientRootQR_FIND
connection.Contexts.Add doSOP_StudyRootQR_FIND
(you may add as many as you wish up to a maximum of 128!)
Open the association
In the COM Version this is done using the SetDestination method, and in the .NET version, the better-named Open method, both of which take 4 parameters, corresponding to the Node, Port, CallingAET & CalledAET properties of the DicomQuery. e.g.
connection.SetDestination RemoteIP, RemotePort, MyAET, RemoteAET
Make the request dataset
Rather than specifying what you wish the SCP to do in terms of properties of the DicomQuery, the DicomConnection-based methods require a DicomDataSet containing the query/retrival parameters.
Those using DoRawQuery will already have a suitable dataset, but for those new to this, it takes a little bit of thinking about. It is important to realise just how different the contents are for querying (C-FIND) and retrieval (C-MOVE & C-GET). They are therefore covered in separate sections:
Retrieval (C-MOVE & C-GET)
The dataset can contain only 5 different attributes, not all of which are needed for all retrievals:
- The Level (0008,0052) which must be “PATIENT”, “STUDY”, “SERIES” or “IMAGE”
- PatientID
- Study UID
- Series UID
- Instance UID
No others are permitted
The level must always be present, and of the others, those ‘between’ the root and the level (inclusive) must be present - e.g.:
- A Patient root, image level operation must contain all four.
- A Study root, series level operation must contain only Study UID & Series UID
So, for this second example, the code would be like this:
Dim dataset as new DicomDataSet
Set dataset.Attributes.Add &H8, &H52, "SERIES"
dataset.StudyUID = ...
dataset.SeriesUID = ...
Querying (C-FIND)
There is a lot more flexibility in what is allowed in the dataset for a C-FIND query, including:
- Unique keys (as specified above for retrieve operations) for levels above the query level (up to and including the root level)
- Matching keys - attributes which are used to select the return values. These can include wildcards.
- Return keys - sent as blank values - instructing the SCP to return these values to you.
Most of the complexity here is normally hidden by using a DicomQuery object, including the choice of a suitable set of blank return keys, to ensure that you have the data you require returned to you. To avoid everyone needing to replicate all this work, DicomObjects provides the QueryDataSet method to access the dataset it would have used itself, and those first converting to using a DicomConnection based query are strongly advised to use that method to avoid the pitfalls of making their own from scratch. The only downside is that you must still create and set up a DicomQuery object ‘as if’ you were going to use it for DoQuery, but in a conversion scenario you are likely to have it anyway! Setting up the dataset then simply becomes:
Set dataset = QueryObject.QueryDataSet()
Of course, you can also create and populate it yourself from scratch, but if doing so, remember that you need to add the level (0008,0052) yourself (as in the retrieve example above).
Perform the operation
This is simple, just use the appropriate command as below:
connection.Find Root, dataset
OR
connection.Get Root, dataset
OR
connection.Move Root, DestinationAET, dataset
Note that none of these method actually return any data - this is to allow them to be used synchronously (as well as synchronously)
Access your results
The place where you find your results (once the operation is complete) depends on the operation performed:
Find
The connection’s ReturnedDataSets collection
Get The connection’s ReturnedImages collection
Move The images/objects will have been received via DicomServer.InstanceReceived event.
In all cases however, you can also access the connection’s ReturnedResponses collection, the last of which contains the ‘final’ response in which useful attributes such as 0000,1020-0000,1023 (numbers of successful, failed images etc.) and 0000,0901/2 (error details) may be found.
Below is sample vb6 code to demonstrate a simple DicomConnection.Find. This is an example of a study root, series level query, but all other combinations are possible by changing the items in bold, and adding/removing identifiers to/from the dataset.
Close the connection
connection.Close()
or you could if you prefer do move operations on it before closing.
Summary
All the above may sound complicated, but in practice, the changes are quite simple! e.g. assuming that a DicomQuery object has been set up, and the following line is currently used for a Study root query:
Set results = query.DoQuery
Dim connection As New DicomConnection
Dim dataset As DicomDataSet
connection.Contexts.Add doSOP_StudyRootQR_FIND 'This is Study Root C-FIND SOP Class
connection.SetDestination query.Node, query.Port, query.CallingAET, query.CalledAET
Set dataset = query.QueryDataSet
connection.Find "STUDY", dataset
Set results = connection.ReturnedDataSets
connection.Close