dicom-rs
dicom-rs copied to clipboard
Add findscu tool
It would be great, if a findscu tool be added, along with the existing storescu and echoscu tools. That way, there would be example code demonstrating how to perform query/retrieve (C-Find) operations with the library, and that's something I'm currently struggling with.
Thank you for reporting. Yes, it makes sense to continue extending the project's network tool repertoire. And that reminds me that these tools could be accompanied with a high-level API.
Nevertheless, should you have specific questions regarding a potential implementation of the find SCU in DICOM-rs, please let me know.
Thanks. I'm at the following stage in my code. As you can see from the concluding comments, I don't know how to proceed building a C-Find request.
use std::path::Path;
use dicom_ul::association::client::ClientAssociationOptions;
use dicom::object::open_file;
// Purpose: Open a file, and see if we can find it in the PACS.
fn main() {
let myhomedir = home::home_dir().unwrap();
let subdir = "dicom/dicom-example/mod/0000000000019826.dcm";
let fullpath = Path::new(&myhomedir).join(subdir);
let pacs_addr_port = "pacshost:11112";
let pacs_scp_aet = "PACSAET";
let pacs_scu_aet = "FINDSCU";
let retrieve_level = "STUDY"; // Expecting this will be needed later
let obj = open_file(fullpath).unwrap();
let patient_name = obj.element_by_name("PatientName").unwrap().to_str().unwrap();
let siuid = obj.element_by_name("StudyInstanceUID").unwrap().to_str().unwrap();
let mut association = ClientAssociationOptions::new()
.with_abstract_syntax("1.2.840.10008.5.1.4.1.2.2.1") // Study Root Query/Retrieve Information Model – FIND
.calling_ae_title(pacs_scu_aet)
.called_ae_title(pacs_scp_aet)
.establish(&pacs_addr_port).unwrap();
let pc_selected_id = association.presentation_contexts().first().unwrap().id;
println!("patient name: {:?}", patient_name);
println!("siuid: {:?}", siuid);
println!("assoc: {:?}", association);
println!("pc_selected_id: {:?}", pc_selected_id);
let writer = association.send_pdata(pc_selected_id);
// Here I'm lost: It looks like a PDU with PData needs to be buit. And that PDU is then
// to be used with the writer.
// PData is a vector of PDataValue elements, but how are those
// defined?
// https://docs.rs/dicom-ul/0.3.0/dicom_ul/pdu/enum.Pdu.html
}
So, the way to fulfil the C-FIND protocol from the SCU end would be:
- Send a C-FIND-RQ message http://dicom.nema.org/medical/dicom/current/output/chtml/part07/sect_9.3.2.html
- Send a DICOM query in compliance with the DIMSE-C C-FIND service http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_Q.4.html
- In a loop, read PDUs from the Find SCP until matching is complete or some error occurs. For as long as the status code is PENDING (either
0xFF00
or0xFF01
), then the SCP will continue sending a matched DICOM object after the C-FIND-RSP message. - Release association
Steps 1 and 2 can be done by sending separate PDU's, one for each, in a similar way as in C-STORE. The command follows a strict set of attributes, and should always fit in a single PData value. The second object contains the attributes to match against the data indexed by the provider. In either case, creating and sending a single PData
manually would be enough. send_pdata
provides an API that is more convenient when sending a large amount of data, which may need to be split into multiple PDUs.
let pdu = Pdu::PData {
data: vec![PDataValue {
presentation_context_id,
value_type: PDataValueType::Command,
is_last: true,
data: cmd_data,
}],
};
scu.send(&pdu)?;
let pdu = Pdu::PData {
data: vec![PDataValue {
presentation_context_id: pc_selected_id,
value_type: PDataValueType::Data,
is_last: true,
data: iod_data,
}],
};
scu.send(&pdu)?;
(where cmd_data
and iod_data
are DICOM objects already encoded into Vec<u8>
)
I managed to scribble a rather crude implementation of the Find SCU. It does not handle some edge cases, but it could be a starting point for a more complete findscu tool. In this case, the file input is the query object, which can be constructed with the help of other tools. For example, a valid input can be made by taking the dump below and using DCMTK's dump2dcm
to convert it to DICOM.
(0008,0052) CS [PATIENT] # QueryRetrieveLevel
(0010,0010) PN [] # PatientsName
(0010,0020) LO [] # PatientID
Thanks, Eduardo! I'm afraid it's still very low-level compared to what I'm used to (Python's pynetdicom module), and I'm not sure I'll be able to be successful. But I'll try.