background:
The series is part of the "Network Transfer" section of the DICOM protocol, with the first two series of articles describing the implementation of the DICOM standard by the DCMTK and fo-dicom Open source libraries (http://blog.csdn.net/zssureqh/article/ details/41016091), as well as a simple implementation of the Fo-dicom library for C-echo and C-Store (http://blog.csdn.net/zssureqh/article/details/41250973). This blog post is a supplement to the previous article, as well as the analysis of the DICOM3.0 standard way, the fo-dicom library to C-find and c-move implementation examples.
DIMSE Protocol and associate Protocol:
The two protocols are described in detail in the 8th chapter of part 7th of the DICOM3.0 Agreement:
DIMSE Protocol:
Dimse developed the process and coding rules for building messages to transfer request and response instructions between two Dicom service consumer (for example, two dicom entities). The process (procedures) specifies the transport rules for request and response instruction messages, which are used to interpret the many fields in the instruction message. However, it does not specify how the requesting initiator and the executor will handle the message. The DIMSE protocol indicates that the message (Messages) may be fragmented (fragmented) using the P-data service to transfer between two Dicom service users.
Associate Protocol:
The establishment of the Connection (association) consists of two Dicom service users. One is called the Connection requester (requester), one is called the Connection Receiver (acceptor), and both parties use the A-associate service to establish the connection. In the A-associate service, the parameters required by both parties are referred to as the "Application context (Application context)", which gives the relevant rules for establishing the connection of the two ends of dicom application entities. (Detailed introduction in Appendix A and Appendix D to part 7th)
After an overview of the protocols required for network transmission, we begin to introduce the specific implementations of the C-find and C-Store services.
C-find's fo-dicom implementation:
1) c-find parameter description:
C-find is a confirmation service (confirmed service) that matches the properties of each other's series of composite SOP instances. The parameters required for this service directive are as follows:
The values used to match the properties of a series of composite SOP instances are given by identifier. In a nutshell, identifier contains the various properties that need to be queried in the requester message (C-FIND-RQ), and in the responder message (C-FIND-RSP), the identifier is the result of the query returned. Note: status is always in the pending state when the query is sent, and when the query results are sent, the status in the last C-FIND-RSP message is success, and the message does not contain any query results .
The specific C-FIND-RQ and C-FIND-RSP encoding formats are as follows, and there are other related requests for c-find, such as C-CANCEL-FIND-RQ. (for the DICOM protocol reading method, refer to the previous article in this series http://blog.csdn.net/zssureqh/article/details/41250973):
2) C-find code example:
The code for C-find SCU and C-find SCP is given below, which contains related comments, so it is not described in detail.
The C-find SCU gives the C-find SCU code in the official readme.md of fo-dicom, as follows,
cfind SCU:
namespace CFINDScu
{
class Program
{
static void Main (string [] args)
{
// Construct the C-FIND-RQ message to be sent, if you look at the DicomCFindRequest class
// You can see that its definition is consistent with the encoding format specified in Chapter 7 of DICOM3.0 Standard
// When constructing a Study-level query, our parameter patientID will be filled into the Indentifier part of the message and used to perform matching queries on the SCP
var cfind = DicomCFindRequest.CreateStudyQuery (patientId: "12345");
// When receiving the response message played by the other party, perform the corresponding operation. [Note]: This operation is based on the DICOM3.0 protocol.
// As explained in Chapter 8 of Part 7, the DIMSE protocol does not provide for it, but should be set by the user.
cfind.OnResponseReceived = (rq, rsp) =>
{
// Here we simply output the query result to the screen
Console.WriteLine ("PatientAge: {0} PatientName: {1}", rsp.Dataset.Get <string> (DicomTag.PatientAge), rsp.Dataset.Get <string> (DicomTag.PatientName));
};
// Initiate C-FIND-RQ:
// This part is to use the A-ASSOCIATE service to establish the connection between the two parties of the DICOM entity.
var client = new DicomClient ();
client.AddRequest (cfind);
client.Send (host: "127.0.0.1", port: 12345, useTls: false, callingAe: "SCU-AE", calledAe: "SCP-AE");
Console.ReadLine ();
}
}
}
Note: Only the main functions are listed in the code, and the complete code is shown in the engineering connection given later.
c-find SCP:
// The DIMSE protocol in Part 7 of Chapter 8 of the DICOM 3.0 agreement does not specify how the requestor and the implementor perform specific operations
// The DcmCFindCallback agent defined here is implemented by the user himself after receiving the C-FIND-RQ
public delegate IList <DicomDataset> DcmCFindCallback (DicomCFindRequest request);
// To provide C-FIND SCP services, you need to inherit the DicomService class, which implements the basic framework of the DICOM protocol,
// In addition, you need to implement the IDicomCFindProvider interface, which is used to implement the specific C-FIND SCP service.
class ZSCFindSCP: DicomService, IDicomServiceProvider, IDicomCFindProvider
{
public ZSCFindSCP (Stream stream, Logger log): base (stream, log)
{
}
#region C-FIND
public static DcmCFindCallback OnZSCFindRequest;
public virtual IEnumerable <DicomCFindResponse> OnCFindRequest (DicomCFindRequest request)
{
DicomStatus status = DicomStatus.Success;
IList <DicomDataset> queries;
List <DicomCFindResponse> responses = new List <DicomCFindResponse> ();
if (OnZSCFindRequest! = null)
{
// Here, the result of the operation performed on the server is returned by the proxy, which is the matching query result in the DICOM protocol.
queries = OnZSCFindRequest (request);
if (queries! = null)
{
Logger.Info ("Queryed {0} data", queries.Count);
foreach (var item in queries)
{
// For each query matching result, there needs to be a separate C-FIND-RSP message to return to the requester
// [Note]: The status of each sending must be Pending, indicating that the query results will continue to be sent in the future.
DicomCFindResponse rsp = new DicomCFindResponse (request, DicomStatus.Pending);
rsp.Dataset = item;
responses.Add (rsp);
}
}
else
{
status = DicomStatus.QueryRetrieveOutOfResources;
}
}
// Then need to send the end status of the query, that is, Success to C-FIND SCU
responses.Add (new DicomCFindResponse (request, DicomStatus.Success));
// It looks like multiple responses are sent out together? Need to follow up to study the implementation code in DicomService
// Find out the specific sending mechanism
return responses;
}
#endregion
}
class Program
{
static void Main (string [] args)
{
// Simulate the related query operation of the local database after receiving the query request, that is, bind the DcmCFindCallback agent
ZSCFindSCP.OnZSCFindRequest = (request) =>
{
// The Identifier field in the request contains information that the SCU wishes to perform a matching query on the SCP side
// We need to simulate related operations. Here it is simply assumed that there are results that meet the conditions in the machine, and return directly
IList <DicomDataset> queries = new List <DicomDataset> ();
// We found three records this time
for (int i = 0; i <3; ++ i)
{
DicomDataset dataset = new DicomDataset ();
DicomDataset dt = new DicomDataset ();
dt.Add (DicomTag.PatientID, "20141130");
dt.Add (DicomTag.PatientName, "zsure");
dt.Add (DicomTag.PatientAge, i.ToString ());
queries.Add (dt);
}
return queries;
};
var cfindServer = new DicomServer <ZSCFindSCP> (12345);
// Console program, used to ensure that the main program can not provide DICOM C-FIND service all the time
Console.ReadLine ();
}
}
Note: Only the main functions are listed in the code, and the complete code is shown in the engineering connection given later.
actual output results:
Of course, the real C-find request requires the two-way DICOM application entity to be associated with the query and output operations in order to achieve more complex functions, just to demonstrate the entire process.
C-move's fo-dicom implementation:
C-move is similar to the C-find request, the more complicated is the C-move request will start the previous blog post introduced C-Store Sub-operation, details as follows:
1) c-move parameter description:
Unlike other operations, such as C-find, there are four more parameters about child operations (sub-operations) that indicate the number of remaining child operations (remaining and complete) and the related completion status (failure or warning).
The specific parameter encoding format is as follows:
2) C-move code example:
The C-move SCU in the fo-dicom official example does not give the implementation code for the C-Store SCP, so the default is to implement the C-Store SCP service by a third party, as shown in:
In this blog post, to reduce the number of test terminals, I implemented the C-Store SCP service directly on the C-move SCU side to receive images sent back from the C-move SCP.
c-move SCU:
// C-MOVE SCU needs to implement C-STORE SCP service
// Of course, it may not necessarily be implemented by the C-MOVE SCU side, or it may be a third party to implement the C-STORE SCP service, which means:
// A sends C-MOVE RQ to B. After receiving C-MOVE-RQ and querying the image, B initiates C-STORE-RQ to C, and then C analyzes and stores the image.
/// <summary>
/// Implement C-STORE SCP service separately, prepare for C-MOVE SCU
/// </ summary>
public delegate DicomCStoreResponse OnCStoreRequestCallback (DicomCStoreRequest request);
class CStoreSCP: DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
{
private static DicomTransferSyntax [] AcceptedTransferSyntaxes = new DicomTransferSyntax [] {
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
public CStoreSCP (Stream stream, Logger log)
: base (stream, log)
{
}
public static OnCStoreRequestCallback OnCStoreRequestCallBack;
public DicomCStoreResponse OnCStoreRequest (DicomCStoreRequest request)
{
// to do yourself
// Implement a custom storage scheme
if (OnCStoreRequestCallBack! = null)
{
return OnCStoreRequestCallBack (request);
}
return new DicomCStoreResponse (request, DicomStatus.NoSuchActionType);
}
}
class Program
{
static void Main (string [] args)
{
// Enable the C-STORE SCP service for receiving images returned by C-MOVE SCP
CStoreSCP.OnCStoreRequestCallBack = (request) =>
{
var studyUid = request.Dataset.Get <string> (DicomTag.StudyInstanceUID);
var instUid = request.SOPInstanceUID.UID;
var path = Path.GetFullPath (@ "c: \ cmove-scu");
path = Path.Combine (path, studyUid);
if (! Directory.Exists (path))
Directory.CreateDirectory (path);
path = Path.Combine (path, instUid) + ".dcm";
request.File.Save (path);
return new DicomCStoreResponse (request, DicomStatus.Success);
};
var cstoreServer = new DicomServer <CStoreSCP> (22345);
// Initiate the C-MOVE-RQ operation, and the StudyID to send the request is 12
DicomCMoveRequest req = new DicomCMoveRequest ("DEST-AE", "12");
var client = new DicomClient ();
client.NegotiateAsyncOps ();
client.AddRequest (req);
// The IP address here is the address of the C-MOVE SCP, and the port number 12345 is the port where the C-MOVE SCP provides the C-MOVE service
// The C-STORE-RQ sub-operation request issued on the C-MOVE SCP side is the C-STORE SCP we implemented on the C-MOVE SCU side, and the port bound to the C-STORE SCP is 22345
client.Send ("127.0.0.1", 12345, false, "DEST-AE", "SCP-AE");
Console.ReadLine ();
}
}
Note: Only the main functions are listed in the code, and the complete code is shown in the engineering connection given later.
c-move SCP:
// The DIMSE protocol in Part 7 of Chapter 8 of the DICOM 3.0 agreement does not specify how the requestor and the implementor perform specific operations
// The DcmCMoveCallback agent defined here is implemented by the user himself after receiving the C-MOVE-RQ
public delegate IList <DicomDataset> DcmCMoveCallback (DicomCMoveRequest request);
// To provide C-FIND SCP services, you need to inherit the DicomService class, which implements the basic framework of the DICOM protocol,
// In addition, you need to implement the IDicomCMoveProvider interface, which is used to implement the specific C-MOVE SCP service.
class ZSCMoveSCP: DicomService, IDicomServiceProvider, IDicomCMoveProvider
{
public ZSCMoveSCP (Stream stream, Logger log)
: base (stream, log)
{
}
#region C-MOVE
public static DcmCMoveCallback OnZSCMoveRequest;
public virtual IEnumerable <DicomCMoveResponse> OnCMoveRequest (DicomCMoveRequest request)
{
DicomStatus status = DicomStatus.Success;
IList <DicomCMoveResponse> rsp = new List <DicomCMoveResponse> ();
/ * ---- to do ------ * /
// Add code to query the database, that is, extract the specified image according to the conditions of the request
// then add the image information to the rsp response
// Create C-STORE-SCU and initiate C-STORE-RQ
IList <DicomDataset> queries;
DicomClient clt = new DicomClient ();
if (OnZSCMoveRequest! = null)
{
queries = OnZSCMoveRequest (request);
if (queries! = null)
{
Logger.Info ("{0} data needs to be sent", queries.Count);
int len = queries.Count;
int cnt = 0;
foreach (var item in queries)
{
// zssure:
// The clever method directly uses request to construct the same part of the response
// This part is very different from the mDCM method
var studyUid = item.Get <string> (DicomTag.StudyInstanceUID);
var instUid = item.Get <string> (DicomTag.SOPInstanceUID);
// Need to manually add the image requested by C-MOVE SCU in the c: \ cmovetest directory
// The locally constructed directory structure is,
// c: \ cmovetest \ 12 \ 0.dcm
// c: \ cmovetest \ 12 \ 1.dcm
// c: \ cmovetest \ 12 \ 2.dcm
var path = Path.GetFullPath (@ "c: \ cmovetest");
try
{
path = Path.Combine (path, studyUid);
if (! Directory.Exists (path))
Directory.CreateDirectory (path);
path = Path.Combine (path, instUid) + ".dcm";
DicomCStoreRequest cstorerq = new DicomCStoreRequest (path);
cstorerq.OnResponseReceived = (rq, rs) =>
{
if (rs.Status! = DicomStatus.Pending)
{
}
if (rs.Status == DicomStatus.Success)
{
DicomCMoveResponse rsponse = new DicomCMoveResponse (request, DicomStatus.Pending);
rsponse.Remaining = --len;
rsponse.Completed = ++ cnt;
rsponse.Warnings = 0;
rsponse.Failures = 0;
rsp.Add (rsponse);
}
};
clt.AddRequest (cstorerq);
// Note: The IP address given here is the same as the IP address requested by the C-MOVE, which means that the C-MOVE SCP needs to send a C-STORE-RQ request to the C-MOVE SCU
// Return the queried image to C-MOVE SCU
// So the IP address in the four-foot C-STORE-RQ is the same as the C-MOVE SCU, but the ports are different because the same port cannot be bound multiple times.
clt.Send ("127.0.0.1", 22345, false, this.Association.CalledAE, request.DestinationAE);
}
catch (System.Exception ex)
{
DicomCMoveResponse rs = new DicomCMoveResponse (request, DicomStatus.StorageStorageOutOfResources);
rsp.Add (rs);
return rsp;
}
}
// zssure:
// C-MOVE RESPONSE is returned after sending
// Looks like there is a problem with the response process, which needs further verification
// Note that if a DicomStatus.Success message is sent at the end, the TCP connection will not be released and resources are wasted
rsp.Add (new DicomCMoveResponse (request, DicomStatus.Success));
return rsp;
}
else
{
rsp.Add (new DicomCMoveResponse (request, DicomStatus.NoSuchObjectInstance));
return rsp;
}
}
rsp.Add (new DicomCMoveResponse (request, DicomStatus.NoSuchObjectInstance));
return rsp;
}
#endregion
}
class Program
{
static void Main (string [] args)
{
ZSCMoveSCP.OnZSCMoveRequest = (request) =>
{
List <DicomDataset> dataset = new List <DicomDataset> ();
for (int i = 0; i <3; ++ i)
{
DicomDataset dt = new DicomDataset ();
dt.Add (DicomTag.StudyInstanceUID, "12");
dt.Add (DicomTag.SOPInstanceUID, i.ToString ());
dataset.Add (dt);
}
return dataset;
};
var cmoveScp = new DicomServer <ZSCMoveSCP> (12345);
Console.ReadLine ();
}
}
Note: Only the main functions are listed in the code, and the complete code is shown in the engineering connection given later.
actual test results:
Opening our manually constructed test catalog, we can see that three images have been successfully transferred to the storage directory set at the C-move Scu end.
With regard to the need for C-move SCP to implement the C-Store SCP at the same time, I hereby explain. C-move SCP is not necessarily required to implement C-Store SCP service, C-move service itself does not require interaction between the two parties, there may be multi-party interaction. For example, a as C-move SCU to B issued C-MOVE-RQ request, at this time as the C-move SCP B in the query to the results can be sent to C C-STORE-RQ request, as long as C provides C-Store SCP service, you can receive the image sent by B. So the C-move service may be a tripartite interaction. Only two-way data transmission is c-get, the difference between C-get and C-move can be referred to the predecessor's blog http://qimo601.iteye.com/blog/1693764.
Project Code:
C-find Project: Baidu Network disk http://pan.baidu.com/s/1c0fDUP6 Github HTTPS://GITHUB.COM/ZSSURE-THU/CSDN/TREE/MASTER/CFINDSCUSCP
C-move Project: Baidu Network disk Http://pan.baidu.com/s/1mgmjlTe Github HTTPS://GITHUB.COM/ZSSURE-THU/CSDN/TREE/MASTER/CMOVESCUSCP
Follow-up Column blog introduction:
Fo-dicom building a simple dicom Server
[email protected]
time:2014-12-01
Dicom medical Image processing: C-find and c-move of fo-dicom network transmission