From 3e9f4a7b75ca5628f914dfee9b29d7dbdefdf428 Mon Sep 17 00:00:00 2001 From: Krad Date: Fri, 8 Jul 2022 10:04:23 +0800 Subject: [PATCH] Add thirdparty dir. --- .gitignore | 1 - thirdparty/CMakeLists.txt | 1 + thirdparty/dcm_network/CMakeLists.txt | 33 + thirdparty/dcm_network/dcm_find.cpp | 451 +++++++ thirdparty/dcm_network/dcm_find.h | 87 ++ thirdparty/dcm_network/dcm_move.cpp | 1102 +++++++++++++++++ thirdparty/dcm_network/dcm_move.h | 174 +++ thirdparty/dcm_network/dcm_store.cpp | 820 ++++++++++++ thirdparty/dcm_network/dcm_store.h | 111 ++ thirdparty/dcm_network/dcm_verify.cpp | 195 +++ thirdparty/dcm_network/dcm_verify.h | 41 + thirdparty/dcm_network/internal/dcm_global.h | 33 + thirdparty/dcm_network/internal/dcm_type.h | 23 + .../dcm_network/internal/dcmtk_headers.h | 11 + 14 files changed, 3082 insertions(+), 1 deletion(-) create mode 100644 thirdparty/CMakeLists.txt create mode 100644 thirdparty/dcm_network/CMakeLists.txt create mode 100644 thirdparty/dcm_network/dcm_find.cpp create mode 100644 thirdparty/dcm_network/dcm_find.h create mode 100644 thirdparty/dcm_network/dcm_move.cpp create mode 100644 thirdparty/dcm_network/dcm_move.h create mode 100644 thirdparty/dcm_network/dcm_store.cpp create mode 100644 thirdparty/dcm_network/dcm_store.h create mode 100644 thirdparty/dcm_network/dcm_verify.cpp create mode 100644 thirdparty/dcm_network/dcm_verify.h create mode 100644 thirdparty/dcm_network/internal/dcm_global.h create mode 100644 thirdparty/dcm_network/internal/dcm_type.h create mode 100644 thirdparty/dcm_network/internal/dcmtk_headers.h diff --git a/.gitignore b/.gitignore index d0ea6fe..20c03a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /build/ /.vs/ -/thirdparty/ /x64/ /.svn /cmake*/ diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt new file mode 100644 index 0000000..3172fdd --- /dev/null +++ b/thirdparty/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(dcm_network) \ No newline at end of file diff --git a/thirdparty/dcm_network/CMakeLists.txt b/thirdparty/dcm_network/CMakeLists.txt new file mode 100644 index 0000000..8713bf1 --- /dev/null +++ b/thirdparty/dcm_network/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 2.8) +set(PROJECT_NAME CACHE STRING "project" ) + +project(dcm_network) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + + + +if(MSVC) + #add_definitions(-D_DCM_NET_EXPORTS) + add_compile_definitions(_DCM_NET_EXPORTS) +endif() + +find_package(DCMTK REQUIRED) +include_directories(${DCMTK_INCLUDE_DIRS}) + + +include_directories(./internal) +add_library( + dcm_network SHARED + ./dcm_verify.cpp + ./dcm_verify.h + ./dcm_find.cpp + ./dcm_find.h + ./dcm_store.cpp + ./dcm_store.h + ./dcm_move.cpp + ./dcm_move.h +) +target_link_libraries(dcm_network ${DCMTK_LIBRARIES}) + + diff --git a/thirdparty/dcm_network/dcm_find.cpp b/thirdparty/dcm_network/dcm_find.cpp new file mode 100644 index 0000000..3ac1e20 --- /dev/null +++ b/thirdparty/dcm_network/dcm_find.cpp @@ -0,0 +1,451 @@ +#include "dcm_find.h" + +//-------------------------------------------------------------------------- +// dcm_cfind_callback + +dcm_cfind_callback::dcm_cfind_callback() + : DcmFindSCUCallback() + , extractResponse_(FEM_xmlFile) + , cancelAfterNReponses_(-1) + , outputDirectory_(".") +{ +} + +dcm_cfind_callback::dcm_cfind_callback(DcmFindSCUExtractMode extractResponse, int cancelAfterNResponse, const char *outputDirectory) + : DcmFindSCUCallback() + , extractResponse_(extractResponse) + , cancelAfterNReponses_(cancelAfterNResponse) + , outputDirectory_(outputDirectory) +{ +} + + +void dcm_cfind_callback::callback(T_DIMSE_C_FindRQ *request, int &responseCount, T_DIMSE_C_FindRSP *rsp, DcmDataset *responseIdentifiers) +{ + std::string outputFileName; + char rspIdsFileName[32]; + + if (extractResponse_ == FEM_dicomFile) + { + sprintf(rspIdsFileName, "rsp%04d.dcm", responseCount); + OFStandard::combineDirAndFilename(outputFileName, outputDirectory_, rspIdsFileName, true); + DcmFindSCU::writeToFile(outputFileName.c_str(), responseIdentifiers); + } + else if (extractResponse_ == FEM_xmlFile) + { + sprintf(rspIdsFileName, "rsp%04d.xml", responseCount); + OFStandard::combineDirAndFilename(outputFileName, outputDirectory_, rspIdsFileName, true); + DcmFindSCU::writeToXMLFile(outputFileName.c_str(), responseIdentifiers); + } + else if (extractResponse_ == FEM_singleXMLFile) + { + } +} + +std::string dcm_cfind_callback::get_outputdir() +{ + return outputDirectory_; +} + +bool dcm_cfind_callback::verify() +{ + if (extractResponse_ == DcmFindSCUExtractMode::FEM_none) + return true; + + if (!OFStandard::dirExists(outputDirectory_)) + { + // log >> output directory does not exist + return false; + } + else if (!OFStandard::isWriteable(outputDirectory_)) + { + // log >> output directory is not writtable + return false; + } + return true; +} + + + +//-------------------------------------------------------------------------- +// dcm-cfind + +////dcm_cfind::dcm_cfind() +//// : abortAssociation(false) +//// , abstractSyntax(UID_FINDPatientRootQueryRetrieveInformationModel) +//// , acse_timeout(30) +//// , blockMode(DIMSE_BLOCKING) +//// , dimse_timeout(0) +//// , maxReceivePDULength(ASC_DEFAULTMAXPDU) +//// , networkTransferSyntax(EXS_Unknown) +//// , repeatCount(1) +//// , cfind_callback(nullptr) +//// +////{ +//// peerIp_ = "127.0.0.1"; +//// peerPort_ = 104; +//// peerTitle_ = ""; +//// ourTitle_ = ""; +////} + +dcm_cfind::dcm_cfind(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTile) + : abortAssociation(false) + , abstractSyntax(UID_FINDPatientRootQueryRetrieveInformationModel) + , acse_timeout(30) + , blockMode(DIMSE_BLOCKING) + , dimse_timeout(0) + , maxReceivePDULength(ASC_DEFAULTMAXPDU) + , networkTransferSyntax(EXS_Unknown) + , repeatCount(1) + , cfind_callback(nullptr) +{ + peerIp_ = peerIp; + peerPort_ = peerPort; + peerTitle_ = peerTitle; + ourTitle_ = ourTile; +} + +dcm_cfind::~dcm_cfind() +{ +} + +void dcm_cfind::set_asce_timeout(int timeout) +{ + acse_timeout = timeout; +} + +void dcm_cfind::set_dimse_timeout(int timeout) +{ + dimse_timeout = timeout; +} + +void dcm_cfind::set_max_received_pdu_lenght(unsigned long length) +{ + maxReceivePDULength = length; +} + +void dcm_cfind::set_find_callback(dcm_cfind_callback *callback) +{ + cfind_callback = callback; +} + +void dcm_cfind::initQueryInfo() +{ + overrideKeys.clear(); + overrideKeys.push_back("0008,0020="); // StudyDate + overrideKeys.push_back("0008,0050="); // AccessionNumber + overrideKeys.push_back("0010,0010="); // PatientName + overrideKeys.push_back("0010,0020="); // PatientID + overrideKeys.push_back("0010,0030="); // PatientBirthDate + overrideKeys.push_back("0010,0040="); // PatientSex + overrideKeys.push_back("0010,1010="); // PatientAge + overrideKeys.push_back("0020,000D="); // StudyInstanceUID + overrideKeys.push_back("0020,0010="); // StudyID + overrideKeys.push_back("0032,1032="); // RequestingPhysician + overrideKeys.push_back("0020,000E="); // SeriesInstanceUID + overrideKeys.push_back("0008,0060="); // Modality + overrideKeys.push_back("0020,0011="); // SeriesNumber +} + +// for the purpose to get all the information we need, store a template DICOM file to contains all the information we need +// AND to return a int code to upper user, need to define globle erro code +int dcm_cfind::find_by_patient_id(std::string patient_id) +{ + //abstractSyntax = UID_FINDPatientRootQueryRetrieveInformationModel; + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "PATIENT"; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string patient_id_ = "0010,0020="; + patient_id_ += patient_id; + overrideKeys.push_back(patient_id_); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_patient_id_and_date(std::string patient_id, std::string startdate, std::string enddate) +{ + //abstractSyntax = UID_FINDPatientRootQueryRetrieveInformationModel; + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "PATIENT"; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string patient_id_ = "0010,0020="; + patient_id_ += patient_id; + overrideKeys.push_back(patient_id_); + + std::string study_date_ = "0008,0020="; + if (!startdate.empty()) + { + study_date_ += startdate; + study_date_ += "-"; + } + if (!enddate.empty()) + study_date_ += enddate; + overrideKeys.push_back(study_date_.c_str()); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_patient_name(std::string patient_name) +{ + //abstractSyntax = UID_FINDPatientRootQueryRetrieveInformationModel; + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "PATIENT"; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string patient_name_ = "0010,0010="; + patient_name_ += patient_name; + overrideKeys.push_back(patient_name_); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_patient_name_and_date(std::string patient_name, std::string startdate, std::string enddate) +{ + //abstractSyntax = UID_FINDPatientRootQueryRetrieveInformationModel; + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "PATIENT"; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string patient_name_ = "0010,0010="; + patient_name_ += patient_name; + overrideKeys.push_back(patient_name_); + + std::string study_date_ = "0008,0020="; + if (!startdate.empty()) + { + study_date_ += startdate; + study_date_ += "-"; + } + if (!enddate.empty()) + study_date_ += enddate; + overrideKeys.push_back(study_date_.c_str()); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_accession_no(std::string accession_no) +{ + //abstractSyntax = UID_FINDPatientRootQueryRetrieveInformationModel; + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "PATIENT"; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string accession_number = "0008,0050="; + accession_number += accession_no; + overrideKeys.push_back(accession_number); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_accession_no_and_date(std::string accession_no, std::string startdate, std::string enddate) +{ + //abstractSyntax = UID_FINDPatientRootQueryRetrieveInformationModel; + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "PATIENT"; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string accession_number = "0008,0050="; + accession_number += accession_no; + overrideKeys.push_back(accession_number); + + std::string study_date_ = "0008,0020="; + if (!startdate.empty()) + { + study_date_ += startdate; + study_date_ += "-"; + } + if (!enddate.empty()) + study_date_ += enddate; + overrideKeys.push_back(study_date_.c_str()); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_study_uid(std::string study_uid) +{ + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + //query_level += "STUDY"; + query_level += "SERIES"; + overrideKeys.push_back(query_level); + + std::string study_instance_uid_ = "0020,000D="; + study_instance_uid_ += study_uid; + overrideKeys.push_back(study_instance_uid_); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_series_uid(std::string study_uid) +{ + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + query_level += "SERIES"; + overrideKeys.push_back(query_level); + + std::string study_instance_uid_ = "0020,000D="; + study_instance_uid_ += study_uid; + overrideKeys.push_back(study_instance_uid_); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_series_uid(std::string study_uid, std::string series_uid) +{ + abstractSyntax = UID_FINDStudyRootQueryRetrieveInformationModel; + + initQueryInfo(); + + std::string query_level = "0008,0052="; + query_level += "STUDY"; + overrideKeys.push_back(query_level); + + std::string study_instance_uid_ = "0020,000D="; + study_instance_uid_ += study_uid; + overrideKeys.push_back(study_instance_uid_); + + std::string series_instance_uid_ = "0020,000E="; + series_instance_uid_ += series_uid; + overrideKeys.push_back(series_instance_uid_); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +int dcm_cfind::find_by_timeinterval(std::string startDate, std::string endDate) +{ + abstractSyntax = UID_FINDModalityWorklistInformationModel; + + // Since worklist is used to get infos which + // will be used by upper user to construct user/study info, + // So we set all the infos except the filter to empty, + // and then pass them to C-Find SCP who will fill these + // value in response + initQueryInfo(); + + std::string scheduleStartDate = "(0040,0100)[0].(0040,0002)="; + if (!startDate.empty()) + { + scheduleStartDate += startDate; + scheduleStartDate += "-"; + } + + if (!endDate.empty()) + { + //std::string scheduleEndDate = "(0040,0100)[0].(0040,0002)="; + //scheduleEndDate += endDate; + //overrideKeys.push_back(scheduleEndDate.c_str()); + scheduleStartDate += endDate; + } + overrideKeys.push_back(scheduleStartDate.c_str()); + + int ret = 0; + OFCondition cond = docfind(); + cond == EC_Normal ? ret = 0 : ret = cond.code(); + return ret; +} + +// METHOD performQuery method from DCMTK sucks +OFCondition dcm_cfind::docfind() +{ + OFStandard::initializeNetwork(); + if (cfind_callback == nullptr) + { + // log >> No callback is provided + cfind_callback = new dcm_cfind_callback(DcmFindSCUExtractMode::FEM_xmlFile, -1, "."); + + // Donnot need to verify, since the query operation should not care about property of callback + } + + DcmFindSCU findscu; + OFCondition cond = findscu.initializeNetwork(acse_timeout); + if (cond.bad()) + { + // log >> failed to connect scp; + return cond; + } + + cond = findscu.performQuery(peerIp_, peerPort_, ourTitle_, peerTitle_, abstractSyntax, + networkTransferSyntax, blockMode, dimse_timeout, maxReceivePDULength, false, abortAssociation, + repeatCount, DcmFindSCUExtractMode::FEM_none, -1, &overrideKeys, cfind_callback, &fileNameList, + "", ""); + if (cond.bad()) + { + // log >> query failed + return cond; + } + + cond = findscu.dropNetwork(); + if (cond.bad()) + { + // log >> drop network failed + return cond; + } + + OFStandard::shutdownNetwork(); + return cond; +} \ No newline at end of file diff --git a/thirdparty/dcm_network/dcm_find.h b/thirdparty/dcm_network/dcm_find.h new file mode 100644 index 0000000..64323dd --- /dev/null +++ b/thirdparty/dcm_network/dcm_find.h @@ -0,0 +1,87 @@ +#ifndef _DCM_FIND_H_ +#define _DCM_FIND_H_ + +#include "internal/dcm_global.h" +#include "internal/dcm_type.h" +#include "internal/dcmtk_headers.h" +#include + + +// this class is awkward, since even it make you a little happy to do with DCMTK, but you still need to +// deal with DCMTK type. +// For a whole wrapper, really a big project. + +// Inherit this class for custom callback handler +class _DCM_EXPORT dcm_cfind_callback : public DcmFindSCUCallback +{ +public: + dcm_cfind_callback(); + dcm_cfind_callback(DcmFindSCUExtractMode extractResponse, int cancelAfterNResponse, const char *outputDirectory = NULL); + virtual ~dcm_cfind_callback() {}; + virtual void callback(T_DIMSE_C_FindRQ *request, int &responseCount, T_DIMSE_C_FindRSP *rsp, DcmDataset *responseIdentifiers); + + std::string get_outputdir(); + // verify whether the dir exists and writable + bool verify(); + +private: + DcmFindSCUExtractMode extractResponse_; + int cancelAfterNReponses_; + std::string outputDirectory_; +}; + +//only support find filter passed in, not files. +class _DCM_EXPORT dcm_cfind +{ +public: + //dcm_cfind(); + dcm_cfind(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTile); + ~dcm_cfind(); + + void set_asce_timeout(int timeout); + void set_dimse_timeout(int timeout); + void set_max_received_pdu_lenght(unsigned long length); + void set_find_callback(dcm_cfind_callback *callback); + + // the query condition will contains as much as information, handle it in your custom callback + int find_by_patient_id(std::string patient_id); + int find_by_patient_id_and_date(std::string patient_id, std::string startdate, std::string enddate); + int find_by_patient_name(std::string patient_name); + int find_by_patient_name_and_date(std::string patient_name, std::string startdate, std::string enddate); + int find_by_accession_no(std::string accession_no); + int find_by_accession_no_and_date(std::string accession_no, std::string startdate, std::string enddate); + int find_by_study_uid(std::string study_uid); + int find_by_series_uid(std::string study_uid); + int find_by_series_uid(std::string study_uid, std::string series_uid); + + // worklist related + int find_by_timeinterval(std::string startDate, std::string endDate); + +private: + OFCondition docfind(); + void initQueryInfo(); + +private: + std::list fileNameList; + bool abortAssociation; + const char * abstractSyntax; + int acse_timeout; + T_DIMSE_BlockingMode blockMode; + //signed long cancelAfterNResponse; + int dimse_timeout; + //DcmFindSCUExtractMode extractResponse; + //std::string extractXMLFileName; + //std::string outputDirectory; + unsigned long maxReceivePDULength; + E_TransferSyntax networkTransferSyntax; + const char * ourTitle_; + const char * peerIp_; + const char * peerTitle_; + unsigned long peerPort_; + unsigned long repeatCount; + std::list overrideKeys; + + dcm_cfind_callback *cfind_callback; +}; + +#endif // _DCM_FIND_H_ \ No newline at end of file diff --git a/thirdparty/dcm_network/dcm_move.cpp b/thirdparty/dcm_network/dcm_move.cpp new file mode 100644 index 0000000..fe153a7 --- /dev/null +++ b/thirdparty/dcm_network/dcm_move.cpp @@ -0,0 +1,1102 @@ +#include "dcm_move.h" + +//------------------------------------------------------------------------------------------------------ +// static function +static void moveCallback(void *callbackData, T_DIMSE_C_MoveRQ *request, int responseCount, T_DIMSE_C_MoveRSP *response) +{ + dcm_cmove_callback * callback = reinterpret_cast(callbackData); + if (callback) + { + callback->callback(request, responseCount, response); + } +} + +//----------------------------------------------------------------------------------------------------- +// c-move callback +dcm_cmove_callback::dcm_cmove_callback() +{ +} + +dcm_cmove_callback::~dcm_cmove_callback() +{ +} + +void dcm_cmove_callback::setAssoc(T_ASC_Association * assoc) +{ + assoc = assoc; +} + +void dcm_cmove_callback::setPresetnationContextID(const T_ASC_PresentationContextID& presId) +{ + presId_ = presId; +} + +void dcm_cmove_callback::setCancelAfterNReponse(int n) +{ + cancelAfterNResponse_ = n; +} + +////dcm_cmove_callback::dcm_cmove_callback(const dcm_cmove_callback& other) +////{ +////} +//// +////dcm_cmove_callback& dcm_cmove_callback::operator=(const dcm_cmove_callback& other) +////{ +////} + + +// default implementation +dcm_cmove_callback_default::dcm_cmove_callback_default() +{ +} + +dcm_cmove_callback_default::~dcm_cmove_callback_default() +{ + +} + +void dcm_cmove_callback_default::callback(T_DIMSE_C_MoveRQ *request, int responseCount, T_DIMSE_C_MoveRSP *response) +{ + OFCondition cond = EC_Normal; + if (cancelAfterNResponse_ == responseCount) + { + cond = DIMSE_sendCancelRequest(assoc_, presId_, request->MessageID); + if (cond != EC_Normal) + { + // log << Cancel Request Failed + } + } +} + + +//------------------------------------------------------------------------------------------------------ +// static function +static void storeSCPCallback(void *callbackData, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *req, char *imageFileName, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *rsp, DcmDataset **statusDetail) +{ + dcm_cmove_storescp_callback *callback = reinterpret_cast(callbackData); + if (callback) + { + callback->callback(progress, req, imageFileName, imageDataSet, rsp, statusDetail); + } +} + +static void subOpCallback(void *callbackData, T_ASC_Network *aNet, T_ASC_Association **subAssoc) +{ + dcm_cmove *cbdata = reinterpret_cast(callbackData); + if (cbdata == NULL) + { + return; + } + if (aNet == NULL) + { + return; + } + if (*subAssoc == NULL) + { + cbdata->acceptSubAssoc(aNet, subAssoc); + } + else + { + cbdata->subOpSCP(subAssoc); + } +} + +//------------------------------------------------------------------------------------------------------ +// c-store scp callback + +dcm_cmove_storescp_callback::dcm_cmove_storescp_callback() +{ +} + +dcm_cmove_storescp_callback::~dcm_cmove_storescp_callback() +{ +} + +void dcm_cmove_storescp_callback::setImageFileName(char * imageFileName) +{ + imageFile_ = imageFileName; +} + +void dcm_cmove_storescp_callback::setDcmFileFormat(DcmFileFormat * dcmff) +{ + dcmff_ = dcmff; +} + +void dcm_cmove_storescp_callback::setAssoc(T_ASC_Association * assoc) +{ + assoc_ = assoc; +} + +////dcm_cmove_storescp_callback::dcm_cmove_storescp_callback(const dcm_cmove_storescp_callback& other) +////{ +////} +//// +////dcm_cmove_storescp_callback& dcm_cmove_storescp_callback::operator=(const dcm_cmove_storescp_callback& other) +////{ +////} + + +// default implementation +dcm_cmove_storescp_callback_default::dcm_cmove_storescp_callback_default(std::string outputDirectory) + : dcm_cmove_storescp_callback() + , abortDuringStore_(false) + , abortAfterStore_(false) + , sleepDuring_(0) + , bitPreserving_(false) + , ignore_(false) + , writeTransferSyntax_(EXS_Unknown) + , sequenceType_(EET_ExplicitLength) + , groupLength_(EGL_recalcGL) + , paddingType_(EPD_withoutPadding) + , filepad_(0) + , itempad_(0) + , useMetaheader_(true) +{ + outputDirectory_ = outputDirectory.c_str(); +} + +dcm_cmove_storescp_callback_default::~dcm_cmove_storescp_callback_default() +{ +} + +void dcm_cmove_storescp_callback_default::callback(T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *request, char *imageFileName, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *response, DcmDataset **statusDetail) +{ + DIC_UI sopClass; + DIC_UI sopInstance; + + if ((abortDuringStore_ && progress->state != DIMSE_StoreBegin) + || (abortAfterStore_ && progress->state == DIMSE_StoreEnd)) + { + // log >> ABORT initiated + ASC_abortAssociation(assoc_); + response->DimseStatus = STATUS_STORE_Refused_OutOfResources; + return; + } + + if (sleepDuring_ > 0) + { + OFStandard::sleep(static_cast(sleepDuring_)); + } + + switch (progress->state) + { + case DIMSE_StoreBegin: + // log << "Recv: " + break; + case DIMSE_StoreEnd: + // log << "End" + break; + default: + // log << "." + break; + } + + if (progress->state == DIMSE_StoreEnd) + { + *statusDetail = NULL; + if ((imageDataSet != NULL) && (*imageDataSet != NULL) && !bitPreserving_ && !ignore_) + { + std::string ofname; + OFStandard::combineDirAndFilename(ofname, outputDirectory_, imageFile_, true); + if (OFStandard::fileExists(ofname)) + { + //log << "DICOM file already exists, overwriting: + } + + E_TransferSyntax xfer = writeTransferSyntax_; + if (xfer == EXS_Unknown) + { + xfer = (*imageDataSet)->getOriginalXfer(); + } + + OFCondition cond = dcmff_->saveFile(ofname.c_str(), xfer, sequenceType_, groupLength_, paddingType_, static_cast(filepad_), static_cast(itempad_), useMetaheader_ ? EWM_fileformat : EWM_dataset); + if (cond.bad()) + { + // log << cannot write DICOM file + response->DimseStatus = STATUS_STORE_Refused_OutOfResources; + OFStandard::deleteFile(ofname); + } + + if ((response->DimseStatus == STATUS_Success) && (!ignore_)) + { + if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sizeof(sopClass), sopClass, sizeof(sopInstance), correctUIDPadding_)) + { + // log << bad DICOM file + response->DimseStatus = STATUS_STORE_Error_CannotUnderstand; + } + else if (strcmp(sopClass, request->AffectedSOPClassUID) != 0) + { + response->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; + } + else if (strcmp(sopInstance, request->AffectedSOPInstanceUID) != 0) + { + response->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; + } + } + } + } +} + + +//-------------------------------------------------------------------------------------------------------- +// dcm-cmove + +typedef struct { + const char * findSyntax; + const char * moveSyntax; +} QuerySyntax; + +static QuerySyntax querySyntax[3] = +{ + { UID_FINDPatientRootQueryRetrieveInformationModel, UID_MOVEPatientRootQueryRetrieveInformationModel }, + { UID_FINDStudyRootQueryRetrieveInformationModel, UID_MOVEStudyRootQueryRetrieveInformationModel }, + { UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel, UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel } +}; + +dcm_cmove::dcm_cmove(const char *peerIp, unsigned long peerPort, const char *peerTitle, unsigned long ourPort, const char *ourTile) + : cmove_callback(NULL) + , storescp_callback(NULL) + , sleepAfter(0) + , sleepDuring(0) + , maxPDU(ASC_DEFAULTMAXPDU) + , useMetaheader(true) + , acceptAllXfer(true) + , in_networkTransferSyntax(EXS_Unknown) + , out_networkTransferSyntax(EXS_Unknown) + , bitPreserving(false) + , ignore(false) + , repeatCount(1) + , abortAssociation(false) + , queryModel(QMPatientRoot) + , blockMode(DIMSE_BLOCKING) + , dimse_timeout(0) + , acse_timeout(30) + , ignorePendingDatasets(true) + , net(NULL) + , overrideKeys(NULL) +{ + peerIp_ = peerIp; + peerPort_ = peerPort; + peerTitle_ = peerTitle; + ourPort_ = ourPort; + ourTitle_ = new char[1024]; + sprintf(ourTitle_, "%s", ourTile); + +} + +dcm_cmove::~dcm_cmove() +{ + if (ourTitle_ != NULL) + { + delete[] ourTitle_; + } +} + +void dcm_cmove::set_sleep_after(unsigned long after) +{ + sleepAfter = after; +} + +void dcm_cmove::set_sleep_during(unsigned long during) +{ + sleepDuring = during; +} + +void dcm_cmove::set_dimse_timeout(int timeout) +{ + dimse_timeout = timeout; +} + +void dcm_cmove::set_acse_timeout(int timeout) +{ + acse_timeout = timeout; +} + +void dcm_cmove::set_cmove_callback(dcm_cmove_callback * callback) +{ + cmove_callback = callback; +} + +void dcm_cmove::set_cmove_scp_callback(dcm_cmove_storescp_callback * callback) +{ + storescp_callback = callback; +} + +int dcm_cmove::move_by_patient_id(std::string patient_id) +{ + queryModel = QMPatientRoot; + + addOverrideKey("0008,0052=PATIENT"); + + std::string patient_id_ = "0010,0020="; + patient_id_ += patient_id; + addOverrideKey(patient_id_.c_str()); + + return docmove(); +} + +int dcm_cmove::move_by_study_uid(std::string study_uid) +{ + queryModel = QMStudyRoot; + + addOverrideKey("0008,0052=STUDY"); + + std::string studyinstance_uid_ = "0020,000D="; + studyinstance_uid_ += study_uid; + addOverrideKey(studyinstance_uid_.c_str()); + + return docmove(); +} + +int dcm_cmove::move_by_series_uid(std::string study_uid, std::string series_uid) +{ + queryModel = QMStudyRoot; + + addOverrideKey("0008,0052=SERIES"); + + std::string studyinstance_uid_ = "0020,000D="; + studyinstance_uid_ += study_uid; + addOverrideKey(studyinstance_uid_.c_str()); + + std::string seriesinstance_uid_ = "0020,000E="; + seriesinstance_uid_ += series_uid; + addOverrideKey(seriesinstance_uid_.c_str()); + + return docmove(); +} + +int dcm_cmove::move_by_image_uid(std::string study_uid, std::string series_uid, std::string image_uid) +{ + queryModel = QMStudyRoot; + + addOverrideKey("0008,0052=IMAGE"); + + std::string studyinstance_uid_ = "0020,000D="; + studyinstance_uid_ += study_uid; + addOverrideKey(studyinstance_uid_.c_str()); + + std::string seriesinstance_uid_ = "0020,000E="; + seriesinstance_uid_ += series_uid; + addOverrideKey(seriesinstance_uid_.c_str()); + + std::string image_uid_ = "0008,0018="; + image_uid_ += image_uid; + addOverrideKey(image_uid_.c_str()); + + return docmove(); +} + +void dcm_cmove::addOverrideKey(const char *s) +{ + unsigned int g = 0xffff; + unsigned int e = 0xffff; + int n = 0; + std::string dicName, valStr; + + n = sscanf(s, "%x,%x=", &g, &e); + std::string toParse = s; + size_t eqPos = toParse.find('='); + if (n < 2) + { + if (eqPos != std::string::npos) + { + dicName = toParse.substr(0, eqPos).c_str(); + valStr = toParse.substr(eqPos + 1, toParse.length()); + } + else + { + dicName = s; + } + + DcmTagKey key(0xffff, 0xffff); + const DcmDataDictionary& globalDataDict = dcmDataDict.rdlock(); + const DcmDictEntry * dicent = globalDataDict.findEntry(dicName.c_str()); + dcmDataDict.rdunlock(); + if (dicent != NULL) + { + key = dicent->getKey(); + g = key.getGroup(); + e = key.getElement(); + } + else + { + // log << bad key format or dictionary name not found in dictionary + } + } + else + { + if (eqPos != std::string::npos) + { + valStr = toParse.substr(eqPos + 1, toParse.length()); + } + } + + DcmTag tag(g, e); + if (tag.error() != EC_Normal) + { + // log << unknown tag + } + DcmElement *elem = DcmItem::newDicomElement(tag); + if (elem == NULL) + { + // log << cannot create element for tag + } + if (!valStr.empty()) + { + if (elem->putString(valStr.c_str()).bad()) + { + // log << cannot put tag value + } + } + + if (overrideKeys == NULL) + { + overrideKeys = new DcmDataset; + } + if (overrideKeys->insert(elem, true).bad()) + { + // log << cannot insert tag + } +} + +OFCondition dcm_cmove::addPresentationContext(T_ASC_Parameters * params, T_ASC_PresentationContextID pid, const char * abstractSyntax) +{ + const char *transferSyntax[] = {NULL, NULL, NULL, NULL}; + int numTransferSyntaxes = 0; + + switch (out_networkTransferSyntax) + { + case EXS_LittleEndianImplicit: + transferSyntax[0] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 1; + break; + case EXS_LittleEndianExplicit: + transferSyntax[0] = UID_LittleEndianExplicitTransferSyntax; + transferSyntax[1] = UID_BigEndianExplicitTransferSyntax; + transferSyntax[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + break; + case EXS_BigEndianExplicit: + transferSyntax[0] = UID_BigEndianExplicitTransferSyntax; + transferSyntax[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntax[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + break; + default: + if (gLocalByteOrder == EBO_LittleEndian) + { + transferSyntax[0] = UID_LittleEndianExplicitTransferSyntax; + transferSyntax[1] = UID_BigEndianExplicitTransferSyntax; + } + else + { + transferSyntax[0] = UID_BigEndianExplicitTransferSyntax; + transferSyntax[1] = UID_LittleEndianExplicitTransferSyntax; + } + transferSyntax[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + break; + } + + return ASC_addPresentationContext(params, pid, abstractSyntax, transferSyntax, numTransferSyntaxes); +} + +void dcm_cmove::substituteOverrideKeys(DcmDataset *dset) +{ + if (overrideKeys == NULL) + return; + + DcmDataset keys(*overrideKeys); + unsigned long elemCount = keys.card(); + for (unsigned long i = 0; i < elemCount; i++) + { + DcmElement *elem = keys.remove(static_cast(0)); + dset->insert(elem, true); + } +} + +OFCondition dcm_cmove::acceptSubAssoc(T_ASC_Network *aNet, T_ASC_Association **assoc) +{ + const char *knownAbstractSyntaxes[] = + { + UID_VerificationSOPClass + }; + const char* transferSyntaxes[] = + { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, //10 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, //20 + NULL //+1 + }; + int numTransferSyntaxes; + + OFCondition cond = ASC_receiveAssociation(aNet, assoc, maxPDU); + if (cond.good()) + { + switch (in_networkTransferSyntax) + { + case EXS_LittleEndianImplicit: + transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 1; + break; + case EXS_LittleEndianExplicit: + transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + break; + case EXS_BigEndianExplicit: + transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + break; + case EXS_JPEGProcess14SV1: + transferSyntaxes[0] = UID_JPEGProcess14SV1TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_JPEGProcess1: + transferSyntaxes[0] = UID_JPEGProcess1TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_JPEGProcess2_4: + transferSyntaxes[0] = UID_JPEGProcess2_4TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_JPEG2000LosslessOnly: + transferSyntaxes[0] = UID_JPEG2000LosslessOnlyTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_JPEG2000: + transferSyntaxes[0] = UID_JPEG2000TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_JPEGLSLossless: + transferSyntaxes[0] = UID_JPEGLSLosslessTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_JPEGLSLossy: + transferSyntaxes[0] = UID_JPEGLSLossyTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_MPEG2MainProfileAtMainLevel: + transferSyntaxes[0] = UID_MPEG2MainProfileAtMainLevelTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes; + break; + case EXS_MPEG2MainProfileAtHighLevel: + transferSyntaxes[0] = UID_MPEG2MainProfileAtHighLevelTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_MPEG4HighProfileLevel4_1: + transferSyntaxes[0] = UID_MPEG4HighProfileLevel4_1TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_MPEG4BDcompatibleHighProfileLevel4_1: + transferSyntaxes[0] = UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_MPEG4HighProfileLevel4_2_For2DVideo: + transferSyntaxes[0] = UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_MPEG4HighProfileLevel4_2_For3DVideo: + transferSyntaxes[0] = UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_MPEG4StereoHighProfileLevel4_2: + transferSyntaxes[0] = UID_MPEG4StereoHighProfileLevel4_2TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_HEVCMainProfileLevel5_1: + transferSyntaxes[0] = UID_HEVCMainProfileLevel5_1TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_HEVCMain10ProfileLevel5_1: + transferSyntaxes[0] = UID_HEVCMain10ProfileLevel5_1TransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + case EXS_RLELossless: + transferSyntaxes[0] = UID_RLELosslessTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 4; + break; + default: + if (acceptAllXfer) + { + transferSyntaxes[0] = UID_JPEG2000TransferSyntax; + transferSyntaxes[1] = UID_JPEG2000LosslessOnlyTransferSyntax; + transferSyntaxes[2] = UID_JPEGProcess2_4TransferSyntax; + transferSyntaxes[3] = UID_JPEGProcess1TransferSyntax; + transferSyntaxes[4] = UID_JPEGProcess14SV1TransferSyntax; + transferSyntaxes[5] = UID_JPEGLSLossyTransferSyntax; + transferSyntaxes[6] = UID_JPEGLSLosslessTransferSyntax; + transferSyntaxes[7] = UID_RLELosslessTransferSyntax; + transferSyntaxes[8] = UID_MPEG2MainProfileAtMainLevelTransferSyntax; + transferSyntaxes[9] = UID_MPEG2MainProfileAtHighLevelTransferSyntax; + transferSyntaxes[10] = UID_MPEG4HighProfileLevel4_1TransferSyntax; + transferSyntaxes[11] = UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax; + transferSyntaxes[12] = UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax; + transferSyntaxes[13] = UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax; + transferSyntaxes[14] = UID_MPEG4StereoHighProfileLevel4_2TransferSyntax; + transferSyntaxes[15] = UID_HEVCMainProfileLevel5_1TransferSyntax; + transferSyntaxes[16] = UID_HEVCMain10ProfileLevel5_1TransferSyntax; + transferSyntaxes[17] = UID_DeflatedExplicitVRLittleEndianTransferSyntax; + if (gLocalByteOrder == EBO_LittleEndian) + { + transferSyntaxes[18] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[19] = UID_BigEndianExplicitTransferSyntax; + } + else + { + transferSyntaxes[18] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[19] = UID_LittleEndianExplicitTransferSyntax; + } + transferSyntaxes[20] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 21; + } + else + { + if (gLocalByteOrder == EBO_LittleEndian) + { + transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax; + } + else + { + transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; + } + transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + } + break; + } + + cond = ASC_acceptContextsWithPreferredTransferSyntaxes((*assoc)->params, knownAbstractSyntaxes, DIM_OF(knownAbstractSyntaxes), transferSyntaxes, numTransferSyntaxes); + if (cond.good()) + { + cond = ASC_acceptContextsWithPreferredTransferSyntaxes((*assoc)->params, dcmAllStorageSOPClassUIDs, numberOfDcmAllStorageSOPClassUIDs, transferSyntaxes, numTransferSyntaxes); + } + } + + + if (cond.good()) + { + cond = ASC_acknowledgeAssociation(*assoc); + } + if (cond.good()) + { + // log << Sub-Association Acknowledged Success + if (ASC_countAcceptedPresentationContexts((*assoc)->params) == 0) + { + // log << but no valid presentation contexes; + } + } + else + { + // log << Sub-Association Acknowleged Failed + ASC_dropAssociation(*assoc); + ASC_destroyAssociation(assoc); + } + + return cond; +} + + +OFCondition dcm_cmove::echoSCP(T_ASC_Association *assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) +{ + T_DIMSE_C_EchoRQ *req = &msg->msg.CEchoRQ; + + OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, req, STATUS_Success, NULL); + if (cond.bad()) + { + // log << Echo SCP Failed + } + return cond; +} + +OFCondition dcm_cmove::storeSCP(T_ASC_Association *assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) +{ + OFCondition cond = EC_Normal; + T_DIMSE_C_StoreRQ *req; + char imageFileName[2048]; + + req = &msg->msg.CStoreRQ; + if (ignore) + { +#ifdef _WIN32 + tmpnam(imageFileName); +#else + OFStandard::strlcpy(imageFileName, NULL_DEVICE_NAME, 2048); +#endif // _WIN32 + } + else + { + sprintf(imageFileName, "%s.%s", dcmSOPClassUIDToModality(req->AffectedSOPClassUID), req->AffectedSOPInstanceUID); + } + + if (storescp_callback == NULL) + storescp_callback = new dcm_cmove_storescp_callback_default("."); + //will not be called actually + storescp_callback->setAssoc(assoc); + DcmFileFormat dcmff; // ? + storescp_callback->setDcmFileFormat(&dcmff); + storescp_callback->setImageFileName(imageFileName); + + if (assoc && assoc->params) + { + const char *aet = assoc->params->DULparams.callingAPTitle; + if (aet) + { + dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet); + } + } + + DcmDataset *dset = dcmff.getDataset(); + if (bitPreserving) + { + cond = DIMSE_storeProvider(assoc, presID, req, imageFileName, useMetaheader, + NULL, storeSCPCallback, reinterpret_cast(storescp_callback), + blockMode, dimse_timeout); + } + else + { + cond = DIMSE_storeProvider(assoc, presID, req, NULL, useMetaheader, + &dset, storeSCPCallback, reinterpret_cast(storescp_callback), + blockMode, dimse_timeout); + } + + if (cond.bad()) + { + if (!ignore) + { + if (strcmp(imageFileName, NULL_DEVICE_NAME) != 0) + { + OFStandard::deleteFile(imageFileName); + } + } +#ifdef _WIN32 + else if (ignore) + { + if (strcmp(imageFileName, NULL_DEVICE_NAME) != 0) + { + OFStandard::deleteFile(imageFileName); + } + } +#endif // _WIN32 + } + + if (sleepAfter > 0) + { + OFStandard::sleep(static_cast(sleepDuring)); + } + + return cond; +} + +OFCondition dcm_cmove::subOpSCP(T_ASC_Association **subAssoc) +{ + T_DIMSE_Message msg; + T_ASC_PresentationContextID presID; + + if (!ASC_dataWaiting(*subAssoc, 0)) + return DIMSE_NODATAAVAILABLE; + + OFCondition cond = DIMSE_receiveCommand(*subAssoc, blockMode, dimse_timeout, &presID, &msg, NULL); + if (cond == EC_Normal) + { + switch (msg.CommandField) + { + case DIMSE_C_ECHO_RQ: + cond = echoSCP(*subAssoc, &msg, presID); + break; + case DIMSE_C_STORE_RQ: + cond = storeSCP(*subAssoc, &msg, presID); + break; + default: + cond = DIMSE_BADCOMMANDTYPE; + break; + } + } + + if (cond == DUL_PEERREQUESTEDRELEASE) + { + cond = ASC_acknowledgeRelease(*subAssoc); + ASC_dropSCPAssociation(*subAssoc); + ASC_destroyAssociation(subAssoc); + return cond; + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + } + else if (cond != EC_Normal) + { + cond = ASC_abortAssociation(*subAssoc); + } + + if (cond != EC_Normal) + { + ASC_dropAssociation(*subAssoc); + ASC_destroyAssociation(subAssoc); + } + + return cond; +} + +OFCondition dcm_cmove::moveSCU(T_ASC_Association * assoc, const char * fname) +{ + T_ASC_PresentationContextID presID; + T_DIMSE_C_MoveRQ req; + T_DIMSE_C_MoveRSP rsp; + DIC_US msgId = assoc->nextMsgID; + DcmDataset *rspIds = NULL; + const char *sopClass; + DcmDataset *statusDetail = NULL; + + DcmFileFormat dcmff; + if (fname != NULL) + { + if (dcmff.loadFile(fname).bad()) + { + // log << bad DICOM file + return DIMSE_BADDATA; + } + } + + substituteOverrideKeys(dcmff.getDataset()); + sopClass = querySyntax[queryModel].moveSyntax; + presID = ASC_findAcceptedPresentationContextID(assoc, sopClass); + if (presID == 0) + { + return DIMSE_NOVALIDPRESENTATIONCONTEXTID; + } + + if (cmove_callback == NULL) + cmove_callback = new dcm_cmove_callback_default(); + cmove_callback->setAssoc(assoc); + cmove_callback->setPresetnationContextID(presID); + + req.MessageID = msgId; + OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID)); + req.Priority = DIMSE_PRIORITY_MEDIUM; + req.DataSetType = DIMSE_DATASET_PRESENT; + if (ourTitle_ == NULL) + { + ourTitle_ = new char[1024]; + ASC_getAPTitles(assoc->params, req.MoveDestination, sizeof(req.MoveDestination), ourTitle_, 1023, NULL, 0); + } + else + { + OFStandard::strlcpy(req.MoveDestination, ourTitle_, sizeof(req.MoveDestination)); + } + + OFCondition cond = DIMSE_moveUser(assoc, presID, &req, dcmff.getDataset(), moveCallback, reinterpret_cast(cmove_callback), + blockMode, dimse_timeout, net, subOpCallback, reinterpret_cast(this), &rsp, &statusDetail, &rspIds, ignorePendingDatasets); + if (cond == EC_Normal) + { + if ((rsp.DimseStatus == STATUS_Success) || (rsp.DimseStatus == STATUS_MOVE_Cancel_SubOperationsTerminatedDueToCancelIndication)) + { + } + else if (rsp.DimseStatus == STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures) + { + // log << response with warning + } + else + { + // log << respone with error + } + } + else + { + // log << Move Request Failed + } + + if (statusDetail != NULL) + delete statusDetail; + if (rspIds != NULL) + delete rspIds; + + return cond; +} + +OFCondition dcm_cmove::cmove(T_ASC_Association *assoc, const char *fname) +{ + OFCondition cond = EC_Normal; + int n = static_cast(repeatCount); + while (cond.good() && n--) + { + cond = moveSCU(assoc, fname); + } + + return cond; +} + +int dcm_cmove::docmove() +{ + T_ASC_Parameters *params = NULL; + DIC_NODENAME peerHost; + T_ASC_Association *assoc = NULL; + + OFStandard::initializeNetwork(); + if (!dcmDataDict.isDictionaryLoaded()) + return e_no_dictionary_loaded; + + T_ASC_NetworkRole role = (ourPort_ > 0) ? NET_ACCEPTORREQUESTOR : NET_REQUESTOR; + OFCondition cond = ASC_initializeNetwork(role, static_cast(ourPort_), acse_timeout, &net); + if (cond.bad()) + { + return e_cannot_initialize_network; + } + + if (OFStandard::dropPrivileges().bad()) + { + return e_cannot_set_uid; + } + + cond = ASC_createAssociationParameters(¶ms, maxPDU); + if (cond.bad()) + { + return e_cannot_create_association_parameters; + } + + ASC_setAPTitles(params, ourTitle_, peerTitle_, NULL); + sprintf(peerHost, "%s:%d", peerIp_, static_cast(peerPort_)); + ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost); + + cond = addPresentationContext(params, 1, querySyntax[queryModel].findSyntax); + cond = addPresentationContext(params, 3, querySyntax[queryModel].moveSyntax); + if (cond.bad()) + { + return e_cannot_create_association_parameters; + } + + cond = ASC_requestAssociation(net, params, &assoc); + if (cond.bad()) + { + if (cond == DUL_ASSOCIATIONREJECTED) + { + return e_association_rejected; + } + else + { + return e_cannot_negotiate_association; + } + } + + if (ASC_countAcceptedPresentationContexts(params) == 0) + { + return e_no_acceptable_presentation_context; + } + + cond = EC_Normal; + cond = cmove(assoc, NULL); + if (cond == EC_Normal) + { + if (abortAssociation) + { + cond = ASC_abortAssociation(assoc); + if (cond.bad()) + { + return e_cannot_abort_association; + } + } + else + { + cond = ASC_releaseAssociation(assoc); + if (cond.bad()) + { + return e_cannot_release_association; + } + } + } + else if (cond == DUL_PEERREQUESTEDRELEASE) + { + cond = ASC_abortAssociation(assoc); + if (cond.bad()) + { + return e_cannot_abort_association; + } + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + } + else + { + cond = ASC_abortAssociation(assoc); + if (cond.bad()) + { + return e_cannot_abort_association; + } + } + + cond = ASC_destroyAssociation(&assoc); + if (cond.bad()) + { + return e_cannot_destroy_association; + } + cond = ASC_dropNetwork(&net); + if (cond.bad()) + { + return e_cannot_drop_network; + } + + OFStandard::shutdownNetwork(); + + return e_ok; +} + + + + + + + diff --git a/thirdparty/dcm_network/dcm_move.h b/thirdparty/dcm_network/dcm_move.h new file mode 100644 index 0000000..63710b0 --- /dev/null +++ b/thirdparty/dcm_network/dcm_move.h @@ -0,0 +1,174 @@ +#ifndef _DCM_MOVE_H_ +#define _DCM_MOVE_H_ + +#include "internal/dcm_global.h" +#include "internal/dcm_type.h" +#include "internal/dcmtk_headers.h" + +// C-Move is a little annoying, since C-Move scu is also a C-Store scp: +// once upon the C-Move request is sent, C-Move scp will send data to +// CMove scu through C-Store in which C_Move scp will act as a C-Store scu +// and C-Move scu will act as a C-Store scp. +// So, you need to provide two callback subclass to implement custom operation +// related the progrss, status and all the like. +// the subOpCallback is implemented internally, do not worry + +class _DCM_EXPORT dcm_cmove_callback +{ +public: + dcm_cmove_callback(); + virtual ~dcm_cmove_callback(); + virtual void callback(T_DIMSE_C_MoveRQ *request, int responseCount, T_DIMSE_C_MoveRSP *response) = 0; + + virtual void setAssoc(T_ASC_Association * assoc); + virtual void setPresetnationContextID(const T_ASC_PresentationContextID& presId); + virtual void setCancelAfterNReponse(int n = -1); + +protected: + T_ASC_Association * assoc_; + T_ASC_PresentationContextID presId_; + int cancelAfterNResponse_; + +private: + ////dcm_cmove_callback(const dcm_cmove_callback& other); + ////dcm_cmove_callback& operator=(const dcm_cmove_callback& other); +}; + +class _DCM_EXPORT dcm_cmove_callback_default : public dcm_cmove_callback +{ +public: + dcm_cmove_callback_default(); + virtual ~dcm_cmove_callback_default(); + virtual void callback(T_DIMSE_C_MoveRQ *request, int responseCount, T_DIMSE_C_MoveRSP *response); +}; + + +// callback funciton in dcmtk is not flexible if you want access to assoc and more underlayer stuff in callback implementation +// so I will put as much as infos in this class, or you can inherit for more info to access +// now the infos conatins: +// char * imageFileName; +// DcmFileFormat *dcmff; +// T_ASC_Association *assoc; +class _DCM_EXPORT dcm_cmove_storescp_callback +{ +public: + dcm_cmove_storescp_callback(); + virtual ~dcm_cmove_storescp_callback(); + virtual void callback(T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *request, char *imageFileName, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *response, DcmDataset **statusDetail) = 0; + virtual void setImageFileName(char * imageFileName); + virtual void setDcmFileFormat(DcmFileFormat * dcmff); + virtual void setAssoc(T_ASC_Association * assoc); + +protected: + char * imageFile_; + DcmFileFormat * dcmff_; + T_ASC_Association * assoc_; + +private: + ////dcm_cmove_storescp_callback(const dcm_cmove_storescp_callback& other); + ////dcm_cmove_storescp_callback& operator=(const dcm_cmove_storescp_callback& other); +}; + +// a default implemetation of dcm_cmove_storescp_callback +// which save files in a specified directory, as a different +// way to handle image, such as store in database, implement it yourself +class _DCM_EXPORT dcm_cmove_storescp_callback_default : public dcm_cmove_storescp_callback +{ +public: + dcm_cmove_storescp_callback_default(std::string outputDirectory); + virtual ~dcm_cmove_storescp_callback_default(); + virtual void callback(T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *request, char *imageFileName, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *response, DcmDataset **statusDetail); + +private: + bool abortDuringStore_; + bool abortAfterStore_; + unsigned long sleepDuring_; + bool bitPreserving_; + bool ignore_; + std::string outputDirectory_; + E_TransferSyntax writeTransferSyntax_; + E_EncodingType sequenceType_; + E_GrpLenEncoding groupLength_; + E_PaddingEncoding paddingType_; + unsigned long filepad_; + unsigned long itempad_; + bool useMetaheader_; + bool correctUIDPadding_; +}; + + + +typedef enum { + QMPatientRoot = 0, + QMStudyRoot = 1, + QMPatientStudyOnly = 2 +} QueryModel; + +class _DCM_EXPORT dcm_cmove +{ +public: + dcm_cmove(const char *peerIp, unsigned long peerPort, const char *peerTitle, unsigned long ourPort, const char *ourTile); + ~dcm_cmove(); + + void set_sleep_after(unsigned long); + void set_sleep_during(unsigned long); + void set_dimse_timeout(int timeout); + void set_acse_timeout(int timeout); + void set_cmove_callback(dcm_cmove_callback * callback); + void set_cmove_scp_callback(dcm_cmove_storescp_callback * callback); + + int move_by_patient_id(std::string patient_id); + int move_by_study_uid(std::string study_uid); + int move_by_series_uid(std::string study_uid, std::string series_uid); + int move_by_image_uid(std::string study_uid, std::string series_uid, std::string image_uid); + + OFCondition acceptSubAssoc(T_ASC_Network *aNet, T_ASC_Association **assoc); + OFCondition subOpSCP(T_ASC_Association **subAssoc); + +private: + void addOverrideKey(const char *s); + OFCondition addPresentationContext(T_ASC_Parameters * params, T_ASC_PresentationContextID pid, const char * abstractSyntax); + void substituteOverrideKeys(DcmDataset *dset); + + OFCondition echoSCP(T_ASC_Association *assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID); + OFCondition storeSCP(T_ASC_Association *assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID); + + OFCondition moveSCU(T_ASC_Association * assoc, const char * fname); + OFCondition cmove(T_ASC_Association *assoc, const char *fname); + + int docmove(); + +private: + const char * peerIp_; + unsigned long peerPort_; + const char * peerTitle_; + unsigned long ourPort_; + char * ourTitle_; + + dcm_cmove_callback * cmove_callback; + dcm_cmove_storescp_callback * storescp_callback; + +private: + unsigned long sleepAfter; + unsigned long sleepDuring; + unsigned long maxPDU; + bool useMetaheader; + bool acceptAllXfer; + E_TransferSyntax in_networkTransferSyntax; + E_TransferSyntax out_networkTransferSyntax; + bool bitPreserving; + bool ignore; + unsigned long repeatCount; + bool abortAssociation; + QueryModel queryModel; + T_DIMSE_BlockingMode blockMode; + int dimse_timeout; + int acse_timeout; + bool ignorePendingDatasets; + + T_ASC_Network * net; + DcmDataset * overrideKeys; +}; + +#endif // !_DCM_MOVE_H_ + diff --git a/thirdparty/dcm_network/dcm_store.cpp b/thirdparty/dcm_network/dcm_store.cpp new file mode 100644 index 0000000..4db4d77 --- /dev/null +++ b/thirdparty/dcm_network/dcm_store.cpp @@ -0,0 +1,820 @@ +#include "dcm_store.h" + +//---------------------------------------------------------------------------------------------------- +// dcm_cstore_callback + +dcm_cstore_callback::dcm_cstore_callback() +{ +} + +dcm_cstore_callback::~dcm_cstore_callback() +{ +} + +////dcm_cstore_callback::dcm_cstore_callback(const dcm_cstore_callback& other) +////{ +////} +//// +////dcm_cstore_callback& dcm_cstore_callback::operator=(const dcm_cstore_callback& other) +////{ +////} + + +//--------------------------------------------------------------------------------------------------------- +// static function + +static void progressCallback(void *callbackData, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *request) +{ + dcm_cstore_callback *callback = reinterpret_cast(callbackData); + if (callback) + callback->callback(progress, request); +} + + + +//--------------------------------------------------------------------------------------------------------- +// dcm-cstore + +dcm_cstore::dcm_cstore(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTitle) + : abortAssociation(false) + , maxReceivedPDULength(ASC_DEFAULTMAXPDU) + , maxSendPDULength(ASC_DEFAULTMAXPDU) + , networkTransferSyntax(EXS_Unknown) + , readMode(ERM_autoDetect) + , scanDir(true) + , recurse(true) + , scanPattern("") + , haltOnUnsuccessfulStore(true) + , unsuccessfulStoreEncountered(false) + , lastStatusCode(STATUS_Success) + , proposeOnlyRequiredPresentationContexts(false) + , combineProposedTransferSyntaxes(false) + , repeatCount(1) + , inventPatientCount(25) + , inventStudyCount(50) + , inventSeriesCount(100) + , inventSOPInstanceInformation(false) + , correctUIDPadding(false) + , patientNamePrefix("OFFIS^TEST_PN_") + , patientIDPrefix("PID_") + , studyIDPrefix("SID_") + , accessionNumberPrefix("") + , configFile(NULL) + , profileName(NULL) + , blockMode(DIMSE_BLOCKING) + , dimse_tiemout(0) + , acse_tiemout(30) + , socket_tiemout(60) + , identMode(ASC_USER_IDENTITY_NONE) + , user("") + , password("") + , identFile_("") + , identResponse(false) + , cstore_callback(nullptr) + , patientCounter(0) + , studyCounter(0) + , seriesCounter(0) + , imageCounter(0) +{ + peerIp_ = peerIp; + peerPort_ = peerPort; + peerTitle_ = peerTitle; + ourTitle_ = ourTitle; +} + +dcm_cstore::~dcm_cstore() +{ +} + +void dcm_cstore::set_dimse_tiemout(int timeout) +{ + dimse_tiemout = timeout; +} + +void dcm_cstore::set_acse_tiemout(int timeout) +{ + acse_tiemout = timeout; +} + +void dcm_cstore::set_ma_received_pdu_length(unsigned long length) +{ + maxReceivedPDULength = length; +} + +void dcm_cstore::set_max_send_pdu_length(unsigned long length) +{ + maxSendPDULength = length; +} + +void dcm_cstore::set_store_callback(dcm_cstore_callback *callback) +{ + cstore_callback = callback; +} + +int dcm_cstore::secondsSince1970() +{ + time_t t = time(NULL); + return static_cast(t); +} + +std::string dcm_cstore::intToString(int i) +{ + char numbuf[32]; + sprintf(numbuf, "%d", i); + return numbuf; +} + +std::string dcm_cstore::makeUID(std::string basePrefix, int counter) +{ + std::string prefix = basePrefix + "." + intToString(counter); + char uidbuf[65]; + std::string uid = dcmGenerateUniqueIdentifier(uidbuf, prefix.c_str()); + return uid; +} + +bool dcm_cstore::updateStringAttributeValue(DcmItem *dataset, const DcmTagKey &key, std::string &value) +{ + DcmStack stack; + DcmTag tag(key); + + OFCondition cond = EC_Normal; + cond = dataset->search(key, stack, ESM_fromHere, false); + if (cond != EC_Normal) + { + // log >> "updateStringAttributeValue : cannot find + return false; + } + + DcmElement *elem = static_cast(stack.top()); + DcmVR vr(elem->ident()); + if (elem->getLength() > vr.getMaxValueLength()) + { + // log >> undateStringAttributeValue : INTERNAL ERROR + return false; + } + + cond = elem->putOFStringArray(value); + if (cond != EC_Normal) + { + // log >> updateStringAttributeValue : cannot put string in attribute + return false; + } + + return true; +} + +void dcm_cstore::replaceSOPInstanceInformation(DcmDataset *dataset) +{ + std::string seriesInstanceUID; + std::string seriesNumber; + std::string studyInstanceUID; + std::string studyID; + std::string accessionNumber; + std::string patientID; + std::string patientName; + + if (seriesInstanceUID.empty()) + seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, static_cast(seriesCounter)); + if (seriesNumber.empty()) + seriesNumber = intToString(static_cast(seriesCounter)); + if (studyInstanceUID.empty()) + studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, static_cast(studyCounter)); + if (studyID.empty()) + studyID = studyIDPrefix + intToString(static_cast(secondsSince1970())) + intToString(static_cast(studyCounter)); + if (accessionNumber.empty()) + accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(static_cast(studyCounter)); + if (patientID.empty()) + patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); + if (patientName.empty()) + patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); + + if (imageCounter >= inventSeriesCount) + { + imageCounter = 0; + seriesCounter++; + seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, static_cast(seriesCounter)); + seriesNumber = intToString(static_cast(seriesCounter)); + } + if (seriesCounter >= inventStudyCount) + { + seriesCounter = 0; + studyCounter++; + studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, static_cast(studyCounter)); + studyID = studyIDPrefix + intToString(secondsSince1970()) + intToString(static_cast(studyCounter)); + accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(static_cast(studyCounter)); + } + if (studyCounter >= inventPatientCount) + { + studyCounter = 0; + patientCounter++; + patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); + patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(static_cast(patientCounter)); + } + + std::string sopInstanceUID = makeUID(SITE_INSTANCE_UID_ROOT, static_cast(imageCounter)); + std::string imageNumber = intToString(static_cast(imageCounter)); + + updateStringAttributeValue(dataset, DCM_PatientName, patientName); + updateStringAttributeValue(dataset, DCM_PatientID, patientID); + updateStringAttributeValue(dataset, DCM_StudyInstanceUID, studyInstanceUID); + updateStringAttributeValue(dataset, DCM_StudyID, studyID); + updateStringAttributeValue(dataset, DCM_SeriesInstanceUID, seriesInstanceUID); + updateStringAttributeValue(dataset, DCM_SeriesNumber, seriesNumber); + updateStringAttributeValue(dataset, DCM_SOPInstanceUID, sopInstanceUID); + updateStringAttributeValue(dataset, DCM_InstanceNumber, imageNumber); + + imageCounter++; +} + +OFCondition dcm_cstore::addStoragePresentationContexts(T_ASC_Parameters *params, std::list &sopClasses) +{ + std::string preferredTransferSyntax; + if (networkTransferSyntax == EXS_Unknown) + { + if (gLocalByteOrder == E_ByteOrder::EBO_LittleEndian) + { + preferredTransferSyntax = UID_LittleEndianExplicitTransferSyntax; + } + else + { + preferredTransferSyntax = UID_BigEndianExplicitTransferSyntax; + } + } + else + { + DcmXfer xfer(networkTransferSyntax); + preferredTransferSyntax = xfer.getXferID(); + } + + std::list::iterator s_cur; + std::list::iterator s_end; + + std::list fallbackSyntaxes; + if ((networkTransferSyntax != EXS_LittleEndianImplicit) && + (networkTransferSyntax != EXS_MPEG2MainProfileAtMainLevel) && + (networkTransferSyntax != EXS_MPEG2MainProfileAtHighLevel) && + (networkTransferSyntax != EXS_MPEG4HighProfileLevel4_1) && + (networkTransferSyntax != EXS_MPEG4BDcompatibleHighProfileLevel4_1) && + (networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For2DVideo) && + (networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For3DVideo) && + (networkTransferSyntax != EXS_MPEG4StereoHighProfileLevel4_2) && + (networkTransferSyntax != EXS_HEVCMainProfileLevel5_1) && + (networkTransferSyntax != EXS_HEVCMain10ProfileLevel5_1)) + { + fallbackSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); + fallbackSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); + fallbackSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); + + fallbackSyntaxes.remove(preferredTransferSyntax); + } + + std::list combinedSyntaxes; + s_cur = fallbackSyntaxes.begin(); + s_end = fallbackSyntaxes.end(); + combinedSyntaxes.push_back(preferredTransferSyntax); + while (s_cur != s_end) + { + if (!isaListMember(combinedSyntaxes, *s_cur)) + { + combinedSyntaxes.push_back(*s_cur); + } + ++s_cur; + } + + if (!proposeOnlyRequiredPresentationContexts) + { + for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++) + { + sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]); + } + } + + std::list sops; + s_cur = sopClasses.begin(); + s_end = sopClasses.end(); + while (s_cur != s_end) + { + if (!isaListMember(sops, *s_cur)) + { + sops.push_back(*s_cur); + } + ++s_cur; + } + + OFCondition cond = EC_Normal; + int pid = 1; + s_cur = sops.begin(); + s_end = sops.end(); + while (s_cur != s_end && cond.good()) + { + if (pid > 255) + { + // log >> Too many presentation contexts + return ASC_BADPRESENTATIONCONTEXTID; + } + + if (combineProposedTransferSyntaxes) + { + addPresentationContext(params, pid, *s_cur, combinedSyntaxes); + pid += 2; + } + else + { + cond = addPresentationContext(params, pid, *s_cur, preferredTransferSyntax); + pid += 2; + + if (fallbackSyntaxes.size() > 0) + { + if (pid > 255) + { + // log >> Too many presentation contexes + return ASC_BADPRESENTATIONCONTEXTID; + } + + cond = addPresentationContext(params, pid, *s_cur, fallbackSyntaxes); + pid += 2; + } + } + ++s_cur; + } + + return cond; +} + +bool dcm_cstore::findSOPClassAndInstanceInFile(const char *fname, char *sopClass, size_t sopClassSize, char *sopInstance, size_t sopInstanceSize) +{ + DcmFileFormat ff; + if (!ff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, readMode).good()) + { + return false; + } + + bool found = DU_findSOPClassAndInstanceInDataSet(ff.getMetaInfo(), sopClass, sopClassSize, sopInstance, sopInstanceSize, correctUIDPadding); + if (!found) + found = DU_findSOPClassAndInstanceInDataSet(ff.getDataset(), sopClass, sopClassSize, sopInstance, sopInstanceSize, correctUIDPadding); + + return found; +} + +OFCondition dcm_cstore::configureUserIdentityRequest(T_ASC_Parameters *params) +{ + OFCondition cond = EC_Normal; + switch (identMode) + { + case ASC_USER_IDENTITY_USER: + { + cond = ASC_setIdentRQUserOnly(params, user, identResponse); + break; + } + case ASC_USER_IDENTITY_USER_PASSWORD: + { + cond = ASC_setIdentRQUserPassword(params, user, password, identResponse); + break; + } + case ASC_USER_IDENTITY_KERBEROS: + case ASC_USER_IDENTITY_SAML: + case ASC_USER_IDENTITY_JWT: + { + OFFile identFile; + if (!identFile.fopen(identFile_.c_str(), "rb")) + { + // log >> Unable to open Kerberbose, SAML or JWT file + return EC_IllegalCall; + } + + offile_off_t result = identFile.fseek(0, SEEK_END); + if (result != 0) + return EC_IllegalParameter; + offile_off_t filesize = identFile.ftell(); + if (filesize > 65535) + { + // log >> "Kerberos, SAML or JWT file is larger than 65535 bytes, bytes after that position are ignored + filesize = 65535; + } + + char *buf = new char[static_cast(filesize)]; + size_t bytesRead = identFile.fread(buf, 1, static_cast(filesize)); + identFile.fclose(); + if (bytesRead == 0) + { + // log >> Unable to read Kerberos, SAML or JWT info file + delete[] buf; + return EC_IllegalCall; + } + + if (identMode == ASC_USER_IDENTITY_KERBEROS) + cond = ASC_setIdentRQKerberos(params, buf, static_cast(bytesRead), identResponse); + else if (identMode == ASC_USER_IDENTITY_SAML) + cond = ASC_setIdentRQSaml(params, buf, static_cast(bytesRead), identResponse); + else + cond = ASC_setIdentRQJwt(params, buf, static_cast(bytesRead), identResponse); + delete[] buf; + break; + } + default: + { + cond = EC_IllegalCall; + break; + } + } + + if (cond.bad()) + { + // log >> Failed + } + + return cond; +} + +OFCondition dcm_cstore::checkUserIdentityResponse(T_ASC_Parameters *params) +{ + if (params == NULL) + return ASC_NULLKEY; + if (identMode == ASC_USER_IDENTITY_NONE || !identResponse) + return EC_Normal; + if (identMode == ASC_USER_IDENTITY_USER || identMode == ASC_USER_IDENTITY_USER_PASSWORD) + { + UserIdentityNegotiationSubItemAC *rsp = params->DULparams.ackUserIdentNeg; + if (rsp == NULL) + { + // log >> User Identity Negotitation failed: Positive response requested but none received + return ASC_USERIDENTIFICATIONFAILED; + } + } + + return EC_Normal; +} + +bool dcm_cstore::isaListMember(std::list &lst, std::string &s) +{ + std::list::iterator cur = lst.begin(); + std::list::iterator end = lst.end(); + + bool found = false; + while (cur != end && !found) + { + found = (s == *cur); + ++cur; + } + + return found; +} + +OFCondition dcm_cstore::addPresentationContext(T_ASC_Parameters *params, int presentationContextId, const std::string &abstractSyntax, const std::string &transferSyntax, T_ASC_SC_ROLE proposedRole) +{ + const char *c_p = transferSyntax.c_str(); + OFCondition cond = ASC_addPresentationContext(params, presentationContextId, abstractSyntax.c_str(), &c_p, 1, proposedRole); + return cond; +} + +OFCondition dcm_cstore::addPresentationContext(T_ASC_Parameters *params, int presentationContextId, const std::string &abstracySyntax, const std::list &transferSyntaxList, T_ASC_SC_ROLE proposedRole) +{ + const char **transferSyntaxes = new const char*[transferSyntaxList.size()]; + int transferSyntaxCount = 0; + + std::list::const_iterator s_cur = transferSyntaxList.begin(); + std::list::const_iterator s_end = transferSyntaxList.end(); + while (s_cur != s_end) + { + transferSyntaxes[transferSyntaxCount++] = (*s_cur).c_str(); + ++s_cur; + } + + OFCondition cond = ASC_addPresentationContext(params, presentationContextId, abstracySyntax.c_str(), transferSyntaxes, transferSyntaxCount, proposedRole); + delete[] transferSyntaxes; + return cond; +} + +OFCondition dcm_cstore::storeSCU(T_ASC_Association *assoc, const char *fname) +{ + DIC_US msgId = assoc->nextMsgID++; + T_ASC_PresentationContextID presID; + T_DIMSE_C_StoreRQ req; + T_DIMSE_C_StoreRSP rsp; + DIC_UI sopClass; + DIC_UI sopInstance; + DcmDataset *statusDetail = NULL; + unsuccessfulStoreEncountered = true; + + DcmFileFormat dcmff; + OFCondition cond = dcmff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, readMode); + if (cond.bad()) + { + // log >> Bad DICOM file + return cond; + } + + if (inventSOPInstanceInformation) + { + replaceSOPInstanceInformation(dcmff.getDataset()); + } + + if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), correctUIDPadding)) + { + // log >> No SOP Class or Instance UID in file + return DIMSE_BADDATA; + } + + DcmXfer filexfer(dcmff.getDataset()->getOriginalXfer()); + if (filexfer.isNotEncapsulated() && networkTransferSyntax == EXS_DeflatedLittleEndianExplicit) + { + filexfer = EXS_DeflatedLittleEndianExplicit; + } + + if (filexfer.getXfer() != EXS_Unknown) + { + presID = ASC_findAcceptedPresentationContextID(assoc, sopClass, filexfer.getXferID()); + } + else + { + presID = ASC_findAcceptedPresentationContextID(assoc, sopClass); + } + if (presID == 0) + { + const char *modalityName = dcmSOPClassUIDToModality(sopClass); + if (!modalityName) + modalityName = dcmFindNameOfUID(sopClass); + if (!modalityName) + modalityName = "unknown SOP class"; + // log >> No presentation context for : + return DIMSE_NOVALIDPRESENTATIONCONTEXTID; + } + + T_ASC_PresentationContext pc; + ASC_findAcceptedPresentationContext(assoc->params, presID, &pc); + DcmXfer netTransfer(pc.acceptedTransferSyntax); + + bzero(reinterpret_cast(&req), sizeof(req)); + req.MessageID = msgId; + OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID)); + OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID)); + req.DataSetType = DIMSE_DATASET_PRESENT; + req.Priority = DIMSE_PRIORITY_MEDIUM; + cond = DIMSE_storeUser(assoc, presID, &req, NULL, dcmff.getDataset(), progressCallback, cstore_callback, blockMode, dimse_tiemout, &rsp, &statusDetail, NULL, static_cast(OFStandard::getFileSize(fname))); + if (cond == EC_Normal && (rsp.DimseStatus == STATUS_Success || DICOM_WARNING_STATUS(rsp.DimseStatus))) + { + unsuccessfulStoreEncountered = false; + } + lastStatusCode = rsp.DimseStatus; + if (cond == EC_Normal) + { + // log >> Recevied Store Response + } + else + { + // log >> Store Failed, file + } + + return cond; +} + +OFCondition dcm_cstore::cstore(T_ASC_Association *assoc, const std::string &fname) +{ + OFCondition cond = EC_Normal; + int n = static_cast(repeatCount); + while (cond.good() && n-- && !(haltOnUnsuccessfulStore && unsuccessfulStoreEncountered)) + { + cond = storeSCU(assoc, fname.c_str()); + } + + if (!haltOnUnsuccessfulStore) + { + cond = EC_Normal; + } + + return cond; +} + +int dcm_cstore::docstore(std::string dir) +{ + std::list fileNameList; + std::list sopClassUIDList; + std::list sopInstanceUIDList; + + T_ASC_Network * net; + T_ASC_Parameters * params; + DIC_NODENAME peerHost; + T_ASC_Association * assoc; + DcmAssociationConfiguration asccfg; + + OFStandard::initializeNetwork(); + if (!dcmDataDict.isDictionaryLoaded()) + { + // log >> "no data dictionary loaded, check environment variable + } + + std::list inputFiles; + OFStandard::searchDirectoryRecursively(dir.c_str(), inputFiles, scanPattern, "", recurse); + if (inputFiles.empty()) + { + return e_no_input_file; + } + + DcmFileFormat dfile; + char sopClassUID[128]; + char sopInstanceUID[128]; + bool ignoreName; + const char * currentFileName = NULL; + std::list::iterator if_iter = inputFiles.begin(); + std::list::iterator if_last = inputFiles.end(); + while (if_iter != if_last) + { + ignoreName = false; + currentFileName = (*if_iter).c_str(); + if (OFStandard::fileExists(currentFileName)) + { + if (proposeOnlyRequiredPresentationContexts) + { + if (!findSOPClassAndInstanceInFile(currentFileName, sopClassUID, sizeof(sopClassUID), sopInstanceUID, sizeof(sopInstanceUID))) + { + ignoreName = true; + if (haltOnUnsuccessfulStore) + { + // missing SOP class (or instance) in file + return e_no_presentation_context; + } + } + else if (!dcmIsaStorageSOPClassUID(sopClassUID, ESSC_All)) + { + ignoreName = true; + if (haltOnUnsuccessfulStore) + { + // unknown storage SOP class in file + return e_unknown_storage_sopclass; + } + } + else + { + sopClassUIDList.push_back(sopClassUID); + sopInstanceUIDList.push_back(sopInstanceUID); + } + } + + if (!ignoreName) + { + fileNameList.push_back(currentFileName); + } + } + else + { + if (haltOnUnsuccessfulStore) + { + // cannot access file + return e_cannot_access_file; + } + } + ++if_iter; + } + + OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, acse_tiemout, &net); + if (cond.bad()) + { + // log >> ASC_initializeNetwork Failed + return cond.code(); + } + + cond = ASC_createAssociationParameters(¶ms, maxReceivedPDULength); + if (cond.bad()) + { + // log >> ASC_createAssociationParameters Failed + return cond.code(); + } + + ASC_setAPTitles(params, ourTitle_, peerTitle_, NULL); + + sprintf(peerHost, "%s:%d", peerIp_, static_cast(peerPort_)); + ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost); + + if (identMode != ASC_USER_IDENTITY_NONE) + { + cond = configureUserIdentityRequest(params); + if (cond.bad()) + return cond.code(); + } + + if (profileName) + { + std::string sprofile; + const unsigned char * c = reinterpret_cast(profileName); + while (*c) + { + if (!isspace(*c)) + sprofile += static_cast(toupper(*c)); + ++c; + } + cond = asccfg.setAssociationParameters(sprofile.c_str(), *params); + } + else + { + cond = addStoragePresentationContexts(params, sopClassUIDList); + } + + if (cond.bad()) + { + // log >> addStoragePresentationContexts List Failed + return cond.code(); + } + + cond = ASC_requestAssociation(net, params, &assoc); + if (cond.bad()) + { + if (cond == DUL_ASSOCIATIONREJECTED) + { + // Association Rejected + return cond.code(); + } + else + { + // C-Store Association Request Failed + return cond.code(); + } + } + + if (ASC_countAcceptedPresentationContexts(params) == 0) + { + // No Acceptable Presentation Contexts + return e_no_acceptable_presentation_context; + } + + cond = checkUserIdentityResponse(params); + if (cond.bad()) + { + // checkUserIdentityResponse Failed + return cond.code(); + } + + // Association Accepted + cond = EC_Normal; + std::list::iterator iter = fileNameList.begin(); + std::list::iterator enditer = fileNameList.end(); + while (iter != enditer && cond.good()) + { + cond = cstore(assoc, *iter); + ++iter; + } + + if (cond == EC_Normal) + { + if (abortAssociation) + { + // Aborting Association + cond = ASC_abortAssociation(assoc); + if (cond.bad()) + { + // Association Abort Failed + return cond.code(); + } + else + { + // Releasing Association + cond = ASC_releaseAssociation(assoc); + if (cond.bad()) + { + // Association Release Failed + return cond.code(); + } + } + } + } + else if (cond == DUL_PEERREQUESTEDRELEASE) + { + // Protocol Error: Peer request release + cond = ASC_abortAssociation(assoc); + if (cond.bad()) + { + // Association Abort Failed + return cond.code(); + } + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + // Peer Aborted Association + } + else + { + // Store SCU Failed + cond = ASC_abortAssociation(assoc); + if (cond.bad()) + { + // Association Abort Failed + return cond.code(); + } + } + + cond = ASC_destroyAssociation(&assoc); + if (cond.bad()) + { + // ASC_destroyAssociation Failed + return cond.code(); + } + + cond = ASC_dropNetwork(&net); + if (cond.bad()) + { + // ASC_dropNetwork Failed + return cond.code(); + } + + OFStandard::shutdownNetwork(); + return e_ok; +} \ No newline at end of file diff --git a/thirdparty/dcm_network/dcm_store.h b/thirdparty/dcm_network/dcm_store.h new file mode 100644 index 0000000..6d2ae97 --- /dev/null +++ b/thirdparty/dcm_network/dcm_store.h @@ -0,0 +1,111 @@ +#ifndef _DCM_STORE_H_ +#define _DCM_STORE_H_ + +#include "internal/dcm_global.h" +#include "internal/dcm_type.h" +#include "internal/dcmtk_headers.h" + +// Inherit this abstract class for custom callback handler +class _DCM_EXPORT dcm_cstore_callback +{ +public: + dcm_cstore_callback(); + virtual ~dcm_cstore_callback(); + virtual void callback(T_DIMSE_StoreProgress * progress, T_DIMSE_C_StoreRQ * req) = 0; + +private: + ////dcm_cstore_callback(const dcm_cstore_callback& other); + ////dcm_cstore_callback& operator=(const dcm_cstore_callback& other); +}; + +class _DCM_EXPORT dcm_cstore +{ +public: + dcm_cstore(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTitle); + ~dcm_cstore(); + + void set_dimse_tiemout(int timeout); + void set_acse_tiemout(int timeout); + void set_ma_received_pdu_length(unsigned long length); + void set_max_send_pdu_length(unsigned long length); + void set_store_callback(dcm_cstore_callback *callback); + + // a directory of the files need to transfer + int docstore(std::string dir); + +private: + int secondsSince1970(); + std::string intToString(int i); + std::string makeUID(std::string basePrefix, int counter); + bool updateStringAttributeValue(DcmItem *dataset, const DcmTagKey &key, std::string &value); + void replaceSOPInstanceInformation(DcmDataset *dataset); + OFCondition addStoragePresentationContexts(T_ASC_Parameters *params, std::list &sopClasses); + bool findSOPClassAndInstanceInFile(const char *fname, char *sopClass, size_t sopClassSize, char *sopInstance, size_t sopInstanceSize); + OFCondition configureUserIdentityRequest(T_ASC_Parameters *params); + OFCondition checkUserIdentityResponse(T_ASC_Parameters *params); + bool isaListMember(std::list &lst, std::string &s); + OFCondition addPresentationContext(T_ASC_Parameters *params, int presentationContextId, const std::string &abstractSyntax, const std::string &transferSyntax, T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT); + OFCondition addPresentationContext(T_ASC_Parameters *params, int presentationContextId, const std::string &abstracySyntax, const std::list &transferSyntaxList, T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT); + + OFCondition storeSCU(T_ASC_Association *assoc, const char *fname); + OFCondition cstore(T_ASC_Association *assoc, const std::string &fname); + +private: + const char * peerIp_; + unsigned long peerPort_; + const char * peerTitle_; + const char * ourTitle_; + + dcm_cstore_callback * cstore_callback; + + unsigned long patientCounter; + unsigned long studyCounter; + unsigned long seriesCounter; + unsigned long imageCounter; + +private: + bool abortAssociation; + unsigned long maxReceivedPDULength; + unsigned long maxSendPDULength; + E_TransferSyntax networkTransferSyntax; + E_FileReadMode readMode; + bool scanDir; + bool recurse; // depends on scanDir + const char * scanPattern; + + bool haltOnUnsuccessfulStore; + bool unsuccessfulStoreEncountered; + int lastStatusCode; + + bool proposeOnlyRequiredPresentationContexts; + bool combineProposedTransferSyntaxes; + + unsigned long repeatCount; + unsigned long inventPatientCount; + unsigned long inventStudyCount; + unsigned long inventSeriesCount; + // set true will cause invent new SOP instance information for the current data set + // donot used + bool inventSOPInstanceInformation; + bool correctUIDPadding; + std::string patientNamePrefix; + std::string patientIDPrefix; + std::string studyIDPrefix; + std::string accessionNumberPrefix; + const char * configFile; + const char * profileName; + T_DIMSE_BlockingMode blockMode; + int dimse_tiemout; + int acse_tiemout; + unsigned long socket_tiemout; + + // identify user may be not used + T_ASC_UserIdentityNegotiationMode identMode; + std::string user; + std::string password; + std::string identFile_; + bool identResponse; +}; + +#endif // !_DCM_STORE_H_ + diff --git a/thirdparty/dcm_network/dcm_verify.cpp b/thirdparty/dcm_network/dcm_verify.cpp new file mode 100644 index 0000000..490db2e --- /dev/null +++ b/thirdparty/dcm_network/dcm_verify.cpp @@ -0,0 +1,195 @@ +#include "dcm_verify.h" + +/* DICOM standard transfer syntaxes */ +static const char* transferSyntaxes[] = { + UID_LittleEndianImplicitTransferSyntax, /* default xfer syntax first */ + UID_LittleEndianExplicitTransferSyntax, + UID_BigEndianExplicitTransferSyntax, + UID_JPEGProcess1TransferSyntax, + UID_JPEGProcess2_4TransferSyntax, + UID_JPEGProcess3_5TransferSyntax, + UID_JPEGProcess6_8TransferSyntax, + UID_JPEGProcess7_9TransferSyntax, + UID_JPEGProcess10_12TransferSyntax, + UID_JPEGProcess11_13TransferSyntax, + UID_JPEGProcess14TransferSyntax, + UID_JPEGProcess15TransferSyntax, + UID_JPEGProcess16_18TransferSyntax, + UID_JPEGProcess17_19TransferSyntax, + UID_JPEGProcess20_22TransferSyntax, + UID_JPEGProcess21_23TransferSyntax, + UID_JPEGProcess24_26TransferSyntax, + UID_JPEGProcess25_27TransferSyntax, + UID_JPEGProcess28TransferSyntax, + UID_JPEGProcess29TransferSyntax, + UID_JPEGProcess14SV1TransferSyntax, + UID_RLELosslessTransferSyntax, + UID_DeflatedExplicitVRLittleEndianTransferSyntax, + UID_JPEGLSLosslessTransferSyntax, + UID_JPEGLSLossyTransferSyntax, + UID_JPEG2000LosslessOnlyTransferSyntax, + UID_JPEG2000TransferSyntax, + UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax, + UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax, + UID_MPEG2MainProfileAtMainLevelTransferSyntax, + UID_MPEG2MainProfileAtHighLevelTransferSyntax, + UID_MPEG4HighProfileLevel4_1TransferSyntax, + UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax, + UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax, + UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax, + UID_MPEG4StereoHighProfileLevel4_2TransferSyntax, + UID_HEVCMainProfileLevel5_1TransferSyntax, + UID_HEVCMain10ProfileLevel5_1TransferSyntax +}; + +dcm_cecho::dcm_cecho(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTitle) + : maxReceivedPDULength(ASC_DEFAULTMAXPDU) + , repeatCount(1) + , abortAssociation(false) + , numXferSyntaxes(1) + , numPresentationCtx(1) + , aces_timeout(30) + , socket_tiemout(60) + , blockMode(DIMSE_BLOCKING) + , dimse_timeout(0) + +{ + maxXferSyntaxes = static_cast(DIM_OF(transferSyntaxes)); + peerIp_ = peerIp; + peerPort_ = peerPort; + peerTitle_ = peerTitle; + ourTitle_ = ourTitle; +} + +dcm_cecho::~dcm_cecho() +{ +} + +void dcm_cecho::set_asce_timeout(int timeout) +{ + aces_timeout = timeout; +} + +void dcm_cecho::set_dimse_timeout(int timeout) +{ + dimse_timeout = timeout; +} + +void dcm_cecho::set_max_received_pdu_length(unsigned long length) +{ + maxReceivedPDULength = length; +} + +int dcm_cecho::docecho() +{ + T_ASC_Network *cecho_net; + T_ASC_Parameters *cecho_params; + DIC_NODENAME cecho_peerHost; + T_ASC_Association *cecho_assoc; + + OFStandard::initializeNetwork(); + if (!dcmDataDict.isDictionaryLoaded()) + return e_no_dictionary_loaded; + + OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, aces_timeout, &cecho_net); + if (cond.bad()) + return cond.code(); // global error code should be a super set of condition error code + + cond = ASC_createAssociationParameters(&cecho_params, maxReceivedPDULength); + if (cond.bad()) + return cond.code(); + + ASC_setAPTitles(cecho_params, ourTitle_, peerTitle_, NULL); + sprintf(cecho_peerHost, "%s:%d", peerIp_, static_cast(peerPort_)); + ASC_setPresentationAddresses(cecho_params, OFStandard::getHostName().c_str(), cecho_peerHost); + + int presentationContextID = 1; + for (unsigned long ii = 0; ii < numPresentationCtx; ii++) + { + cond = ASC_addPresentationContext(cecho_params, presentationContextID, UID_VerificationSOPClass, transferSyntaxes, static_cast(numXferSyntaxes)); + presentationContextID += 2; + if (cond.bad()) + return cond.code(); + } + + cond = ASC_requestAssociation(cecho_net, cecho_params, &cecho_assoc); + if (cond.bad()) + { + if (cond == DUL_ASSOCIATIONREJECTED) + return e_association_rejected; + else + return cond.code(); + } + + if (ASC_countAcceptedPresentationContexts(cecho_params) == 0) + return e_no_acceptable_presentation_context; + + cond = cecho(cecho_assoc, repeatCount, blockMode, dimse_timeout); + if (cond == EC_Normal) + { + if (abortAssociation) + { + cond = ASC_abortAssociation(cecho_assoc); + if (cond.bad()) + return cond.code(); + } + else + { + cond = ASC_releaseAssociation(cecho_assoc); + if (cond.bad()) + return cond.code(); + } + } + else if (cond == DUL_PEERREQUESTEDRELEASE) + { + cond = ASC_abortAssociation(cecho_assoc); + if (cond.bad()) + return cond.code(); + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + } + else + { + cond = ASC_abortAssociation(cecho_assoc); + if (cond.bad()) + return cond.code(); + } + + cond = ASC_destroyAssociation(&cecho_assoc); + if (cond.bad()) + return cond.code(); + + cond = ASC_dropNetwork(&cecho_net); + if (cond.bad()) + return cond.code(); + + OFStandard::shutdownNetwork(); + return e_ok; +} + +OFCondition dcm_cecho::echoSCU(T_ASC_Association * assoc, T_DIMSE_BlockingMode block_mode, int dimse_timeout) +{ + DIC_US msgId = assoc->nextMsgID++; + DIC_US status; + DcmDataset *statusDetail = NULL; + + // log >> "Sending Echo Request + OFCondition cond = DIMSE_echoUser(assoc, msgId, block_mode, dimse_timeout, &status, &statusDetail); + if (statusDetail != NULL) + delete statusDetail; + + return cond; +} + +OFCondition dcm_cecho::cecho(T_ASC_Association * assoc, unsigned long num_repeat, T_DIMSE_BlockingMode block_mode, int dimse_tiemout) +{ + OFCondition cond = EC_Normal; + unsigned long n = num_repeat; + + while (cond.good() && n--) + cond = echoSCU(assoc, block_mode, dimse_tiemout); + + return cond; +} + diff --git a/thirdparty/dcm_network/dcm_verify.h b/thirdparty/dcm_network/dcm_verify.h new file mode 100644 index 0000000..472190b --- /dev/null +++ b/thirdparty/dcm_network/dcm_verify.h @@ -0,0 +1,41 @@ +#ifndef _DCM_VERIFY_H_ +#define _DCM_VERIFY_H_ + +#include "internal/dcm_global.h" +#include "internal/dcm_type.h" +#include "internal/dcmtk_headers.h" + +class _DCM_EXPORT dcm_cecho +{ +public: + dcm_cecho(const char *peerIp, unsigned long peerPort, const char *peerTitle, const char *ourTitle); + ~dcm_cecho(); + + void set_asce_timeout(int timeout); + void set_dimse_timeout(int timeout); + void set_max_received_pdu_length(unsigned long length); + + int docecho(); + +private: + OFCondition echoSCU(T_ASC_Association * assoc, T_DIMSE_BlockingMode block_mode, int dimse_timeout); + OFCondition cecho(T_ASC_Association * assoc, unsigned long num_repeat, T_DIMSE_BlockingMode block_mode, int dimse_tiemout); + +private: + const char * peerIp_; + unsigned long peerPort_; + const char * peerTitle_; + const char * ourTitle_; + unsigned long maxReceivedPDULength; + unsigned long repeatCount; + bool abortAssociation; + unsigned long numXferSyntaxes; + unsigned long numPresentationCtx; + unsigned long maxXferSyntaxes; + int aces_timeout; + int socket_tiemout; + T_DIMSE_BlockingMode blockMode; + int dimse_timeout; +}; + +#endif // _DCM_VERIFY_H_ \ No newline at end of file diff --git a/thirdparty/dcm_network/internal/dcm_global.h b/thirdparty/dcm_network/internal/dcm_global.h new file mode 100644 index 0000000..0f8a7a5 --- /dev/null +++ b/thirdparty/dcm_network/internal/dcm_global.h @@ -0,0 +1,33 @@ +#ifndef _DCM_GLOBAL_H_ +#define _DCM_GLOBAL_H_ + +#ifdef __cplusplus +#define _DCM_BEGIN_EXTERN_C extern "C" { +#define _DCM_END_EXTERN_C } +#else +#define _DCM_BEGIN_EXTERN_C +#define _DCM_END_EXTERN_C +#endif + +#ifdef _MSC_VER + #pragma warning( disable : 4251) + #pragma warning( disable : 4099) + #pragma warning( disable : 4521) + #pragma warning( disable : 4522) +#if _MSC_VER >= 1400 + #pragma warning( disable : 4996) + #pragma warning( disable : 4351) +#endif /* _MSC_VER >= 1400 */ +#endif // _MSC_VER + + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#ifdef _DCM_NET_EXPORTS +#define _DCM_EXPORT __declspec(dllexport) +#else +#define _DCM_EXPORT __declspec(dllimport) +#endif // _DCM_NET_EXPORTS + #else +#define _DCM_EXPORT + #endif // defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + +#endif // !_DCM_GLOBAL_H_ diff --git a/thirdparty/dcm_network/internal/dcm_type.h b/thirdparty/dcm_network/internal/dcm_type.h new file mode 100644 index 0000000..2392d38 --- /dev/null +++ b/thirdparty/dcm_network/internal/dcm_type.h @@ -0,0 +1,23 @@ +#ifndef _DCM_TYPE_H_ +#define _DCM_TYPE_H_ + +const int e_ok = 0; +const int e_no_input_file = -9004; +const int e_no_sopclass_in_file = -9005; +const int e_unknown_storage_sopclass = -9006; +const int e_cannot_access_file = -9007; +const int e_no_acceptable_presentation_context = -9008; +const int e_association_rejected = -9009; +const int e_no_dictionary_loaded = -9010; +const int e_cannot_initialize_network = -9011; +const int e_cannot_set_uid = -9012; +const int e_cannot_create_association_parameters = -9013; +const int e_cannot_negotiate_association = -9014; +const int e_no_presentation_context = -9015; +const int e_cannot_abort_association = -9016; +const int e_cannot_release_association = -9017; +const int e_cannot_destroy_association = -9018; +const int e_cannot_drop_network = -9019; + + +#endif // !DCM_TYPE_H_ diff --git a/thirdparty/dcm_network/internal/dcmtk_headers.h b/thirdparty/dcm_network/internal/dcmtk_headers.h new file mode 100644 index 0000000..a2e4e76 --- /dev/null +++ b/thirdparty/dcm_network/internal/dcmtk_headers.h @@ -0,0 +1,11 @@ +#include "dcmtk/dcmnet/dfindscu.h" +#include "dcmtk/dcmnet/diutil.h" +#include "dcmtk/dcmnet/dcasccfg.h" +#include "dcmtk/dcmdata/dcdict.h" +#include "dcmtk/dcmdata/dcdicent.h" +#include "dcmtk/dcmdata/dcfilefo.h" +#include "dcmtk/dcmdata/dcmetinf.h" +#include "dcmtk/dcmdata/dcdeftag.h" + + +