基于dcmtk的dicom工具 第六章 StoreSCU 图像发送
系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、界面介绍
- 二、CDcmStoreSCU类
- 1. storescu.cc中的主要流程
- 2.日志函数
- 3.接口函数
- 4. 完成代码
- 1. CDcmStoreSCU.h
- 2. CDcmStoreSCU.cpp
- 四、调用CDcmStoreSCU类
- 1. 测试连接-Echo
- 2. 发送文件-Send
- 总结
前言
第四章完成图像接受StoreSCP。
本章为图像发送StoreSCU,参考dcmtk storscu.cc,源文件路径dcmtk-3.6.9\dcmnet\apps\storescu.cc
与第四章生成的程序测试效果如下:
一、界面介绍
vs2017 添加对话框工程请参考第三章
界面分为三个区:
- 参数设置区,主要设置StoreSCP的AE Title,IP,端口
- 文件加载区
- 日志显示区
如下图:
二、CDcmStoreSCU类
1. storescu.cc中的主要流程
- 调用findSOPClassAndInstanceInFile解析待发送文件,获取sopClassUID、sopInstanceUID,判断是否符合发送要求。
- ASC_initializeNetwork初始化网络
- ASC_createAssociationParameters
- ASC_setAPTitles
- ASC_setTransportLayerType
- ASC_setPresentationAddresses
- addStoragePresentationContexts
- ASC_requestAssociation
- ASC_countAcceptedPresentationContexts判断连接是否成功,不成功返回,成功则循环调用storeSCU发送文件
- storeSCU函数,调用DIMSE_storeUser真正发送文件
- 发送完成,释放连接,关闭网络
2.日志函数
与第四章CStoreServer中的四个日志函数功能一样,由于是客户端程序,只发送日志到界面
class CDcmStoreSCU
{
public:CDcmStoreSCU();~CDcmStoreSCU();
...protected:void log_debug(const char* fmt, ...);void log_info(const char* fmt, ...);void log_warn(const char* fmt, ...);void log_error(const char* fmt, ...);};
3.接口函数
- SetParam,设置连接参数,日志级别,日志窗口句柄
- Echo,测试连接函数
- Send,发送函数
class CDcmStoreSCU
{
public:CDcmStoreSCU();~CDcmStoreSCU();void SetParam(SCUParam param);BOOL Send(std::vector<std::string>& filelist);BOOL Echo();
...};
4. 完成代码
1. CDcmStoreSCU.h
#pragma once#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/ofstd/ofstd.h"
#include "dcmtk/dcmnet/dimse.h"
#include "dcmtk/dcmnet/diutil.h"
#include "dcmtk/dcmnet/dcmtrans.h" /* for dcmSocketSend/ReceiveTimeout */
#include "dcmtk/dcmnet/dcasccfg.h" /* for class DcmAssociationConfiguration */
#include "dcmtk/dcmnet/dcasccff.h" /* for class DcmAssociationConfigurationFile */
#include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */struct SCUParam
{std::string localAET;std::string remoteAET;std::string serverIP;int port;HWND hLogWnd;int loglevel;};class CDcmStoreSCU
{
public:CDcmStoreSCU();~CDcmStoreSCU();void SetParam(SCUParam param);BOOL Send(std::vector<std::string>& filelist);BOOL Echo();OFBool findSOPClassAndInstanceInFile(const char *fname,char *sopClass,size_t sopClassSize,char *sopInstance,size_t sopInstanceSize);OFCondition addStoragePresentationContexts(T_ASC_Parameters *params, OFList<OFString> &sopClasses);OFBool isaListMember(OFList<OFString> &lst, OFString &s);/** This function will read all the information from the given file,* figure out a corresponding presentation context which will be used* to transmit the information over the network to the SCP, and it* will finally initiate the transmission of all data to the SCP.** Parameters:* assoc - [in] The association (network connection to another DICOM application).* fname - [in] Name of the file which shall be processed.*/OFCondition storeSCU(T_ASC_Association *assoc, const char *fname);static void progressCallback(void * callbackData, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ * req);OFCondition echoSCU(T_ASC_Association * assoc);OFCondition CDcmStoreSCU::addPresentationContext(T_ASC_Parameters *params, int presentationContextId,const OFString &abstractSyntax, const OFList<OFString> &transferSyntaxList, T_ASC_SC_ROLE proposedRole = (T_ASC_SC_ROLE)1);OFCondition CDcmStoreSCU::addPresentationContext(T_ASC_Parameters *params, int presentationContextId,const OFString &abstractSyntax, const OFString &transferSyntax, T_ASC_SC_ROLE proposedRole = (T_ASC_SC_ROLE)1);protected:void log_debug(const char* fmt, ...);void log_info(const char* fmt, ...);void log_warn(const char* fmt, ...);void log_error(const char* fmt, ...);private:SCUParam m_param;int lastStatusCode;};
2. CDcmStoreSCU.cpp
#include "pch.h"
#include "CDcmStoreSCU.h"
#include "Utilities.h"#include "dcmtk/dcmjpeg/djdecode.h" /* for JPEG decoders */
#include "dcmtk/dcmjpeg/djencode.h" /* for JPEG encoders */
#include "dcmtk/dcmjpls/djdecode.h" /* for JPEG-LS decoders */
#include "dcmtk/dcmjpls/djencode.h" /* for JPEG-LS encoders */
#include "dcmtk/dcmdata/dcrledrg.h" /* for RLE decoder */
#include "dcmtk/dcmdata/dcrleerg.h" /* for RLE encoder */
#include "dcmtk/dcmjpeg/djrploss.h"#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "netapi32.lib")#pragma comment(lib, "dcmnet.lib")
#pragma comment(lib, "dcmdata.lib")
#pragma comment(lib, "oflog.lib")
#pragma comment(lib, "ofstd.lib")
#pragma comment(lib, "dcmtls.lib")
#pragma comment(lib, "oficonv.lib")#pragma comment(lib,"dcmimgle.lib")
#pragma comment(lib,"dcmimage.lib")
#pragma comment(lib,"ijg8.lib")
#pragma comment(lib,"ijg12.lib")
#pragma comment(lib,"ijg16.lib")
#pragma comment(lib,"dcmjpeg.lib")
#pragma comment(lib,"dcmjpls.lib")
#pragma comment(lib,"dcmtkcharls.lib")#ifdef _DEBUG
#pragma comment(lib,"zlib_d.lib")
#else
#pragma comment(lib,"zlib_o.lib")
#endif#define UM_ADD_LOG WM_USER + 10/* DICOM standard transfer syntaxes */
static const char* EchotransferSyntaxes[] = {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_JPEGLSLosslessTransferSyntax,UID_JPEGLSLossyTransferSyntax,UID_DeflatedExplicitVRLittleEndianTransferSyntax,UID_JPEG2000LosslessOnlyTransferSyntax,UID_JPEG2000TransferSyntax,UID_MPEG2MainProfileAtMainLevelTransferSyntax,UID_MPEG2MainProfileAtHighLevelTransferSyntax,UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax,UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax,UID_MPEG4HighProfileLevel4_1TransferSyntax,UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax
};CDcmStoreSCU::CDcmStoreSCU(void)
{lastStatusCode = STATUS_Success;OFStandard::initializeNetwork();// register global JPEG decompression codecsDJDecoderRegistration::registerCodecs();DJEncoderRegistration::registerCodecs();DJLSDecoderRegistration::registerCodecs();DJLSEncoderRegistration::registerCodecs();DcmRLEDecoderRegistration::registerCodecs();DcmRLEEncoderRegistration::registerCodecs();}CDcmStoreSCU::~CDcmStoreSCU(void)
{OFStandard::shutdownNetwork();
}void CDcmStoreSCU::SetParam(SCUParam param)
{m_param = param;
}BOOL CDcmStoreSCU::Send(std::vector<std::string>& filelist)
{OFList<OFString> fileNameList;OFList<OFString> sopClassUIDList;OFList<OFString> sopInstanceUIDList;char sopClassUID[128];char sopInstanceUID[128];OFBool ignoreName;std::string currentFilename;if (!dcmDataDict.isDictionaryLoaded()){//OFLOG_WARN(storescuLogger, "no data dictionary loaded, check environment variable: "// << DCM_DICT_ENVIRONMENT_VARIABLE);log_warn(_T("no data dictionary loaded, check environment variable: %s"), DCM_DICT_ENVIRONMENT_VARIABLE);}for (int i = 0; i < filelist.size(); i++){ignoreName = OFFalse;currentFilename = filelist.at(i);if (OFStandard::fileExists(currentFilename.c_str())){if (!findSOPClassAndInstanceInFile(currentFilename.c_str(), sopClassUID, sizeof(sopClassUID), sopInstanceUID, sizeof(sopInstanceUID))){ignoreName = OFTrue;log_warn(_T("missing SOP class (or instance) in file:[%s], ignoring file"), currentFilename.c_str());}else if (!dcmIsaStorageSOPClassUID(sopClassUID, ESSC_Image/*ESSC_All*/)){ignoreName = OFTrue;log_warn(_T("unknown storage SOP class in file:[%s], ignoring file"),currentFilename.c_str());}else{sopClassUIDList.push_back(sopClassUID);sopInstanceUIDList.push_back(sopInstanceUID);}if (!ignoreName){fileNameList.push_back(currentFilename.c_str());}}else{log_warn(_T("cannot access file:[%s], ignoring file"), currentFilename.c_str());}}log_info(_T("%d files will be sent"), fileNameList.size());if (fileNameList.size() <= 0){return TRUE;}T_ASC_Network* net;T_ASC_Parameters* params;T_ASC_Association* assoc;DIC_NODENAME localHost;DIC_NODENAME peerHost;OFString temp_str;std::string msg;/* initialize network, i.e. create an instance of T_ASC_Network*. */OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, 60, &net);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("初始化网络失败: %s"), temp_str.c_str());return FALSE;}/* initialize asscociation parameters, i.e. create an instance of T_ASC_Parameters*. */cond = ASC_createAssociationParameters(¶ms, ASC_DEFAULTMAXPDU);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Create Association Parameters Failed: %s"), temp_str.c_str());return FALSE;}/* sets this application's title and the called application's title in the params *//* structure. The default values to be set here are "STORESCU" and "ANY-SCP". */ASC_setAPTitles(params, m_param.localAET.c_str(), m_param.remoteAET.c_str(), NULL);/* Set the transport layer type (type of network connection) in the params *//* strucutre. The default is an insecure connection; where OpenSSL is *//* available the user is able to request an encrypted,secure connection. */cond = ASC_setTransportLayerType(params, OFFalse);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Set Transport Layer Type Failed: %s"), temp_str.c_str());return FALSE;}/* Figure out the presentation addresses and copy the *//* corresponding values into the association parameters.*/gethostname(localHost, sizeof(localHost) - 1);sprintf_s(peerHost, "%s:%d", m_param.serverIP.c_str(), m_param.port);ASC_setPresentationAddresses(params, localHost, peerHost);/* Set the presentation contexts which will be negotiated *//* when the network connection will be established */cond = addStoragePresentationContexts(params, sopClassUIDList);if (cond.bad()){//OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Set Storage presentation contexts error: %s"), temp_str.c_str());return FALSE;}//调试日志ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ);Replace(temp_str, _T("\n"), _T("\r\n"));log_debug( _T("Request Parameters:\r\n%s"), temp_str.c_str());log_debug( _T("Requesting Association"));cond = ASC_requestAssociation(net, params, &assoc);if (cond.bad()){if (cond == DUL_ASSOCIATIONREJECTED){T_ASC_RejectParameters rej;ASC_getRejectParameters(params, &rej);//OFLOG_FATAL(storescuLogger, "Association Rejected:" << OFendl << ASC_printRejectParameters(temp_str, &rej));ASC_printRejectParameters(temp_str, &rej);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Rejected:\r\n%s"), temp_str.c_str());return FALSE;}else{//OFLOG_FATAL(storescuLogger, "Association Request Failed: " << DimseCondition::dump(temp_str, cond));DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Request Failed:%s"), temp_str.c_str());return FALSE;}}/* dump the connection parameters if in debug mode*/ASC_dumpConnectionParameters(temp_str, assoc);Replace(temp_str, _T("\n"), _T("\r\n"));log_debug(temp_str.c_str());/* dump the presentation contexts which have been accepted/refused */ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC);Replace(temp_str, _T("\n"), _T("\r\n"));log_debug(_T("Association Parameters Negotiated:\r\n%s"), temp_str.c_str());/* count the presentation contexts which have been accepted by the SCP *//* If there are none, finish the execution */if (ASC_countAcceptedPresentationContexts(params) == 0){//OFLOG_FATAL(storescuLogger, "No Acceptable Presentation Contexts");log_error(_T("No Acceptable Presentation Contexts"));return FALSE;}/* dump general information concerning the establishment of the network connection if required */log_debug( _T("Association Accepted (Max Send PDV: %d)"), assoc->sendPDVLength);/* do the real work, i.e. for all files which were specified in the *//* command line, transmit the encapsulated DICOM objects to the SCP. */cond = EC_Normal;OFListIterator(OFString) iter = fileNameList.begin();OFListIterator(OFString) enditer = fileNameList.end();while ((iter != enditer) && cond.good()){cond = storeSCU(assoc, (*iter).c_str());++iter;}BOOL bSendOK = FALSE;if (lastStatusCode == STATUS_Success)bSendOK = TRUE;/* tear down association, i.e. terminate network connection to SCP */if (cond == EC_Normal){/* release association */log_info(_T("Releasing Association"));cond = ASC_releaseAssociation(assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Release Failed: %s"), temp_str.c_str());return bSendOK;}}else if (cond == DUL_PEERREQUESTEDRELEASE){log_error(_T("Protocol Error: Peer requested release (Aborting)"));log_info(_T("Aborting Association"));cond = ASC_abortAssociation(assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Abort Failed: %s"), temp_str.c_str());return bSendOK;}}else if (cond == DUL_PEERABORTEDASSOCIATION){log_info(_T("Peer Aborted Association"));}else{DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Store SCU Failed: %s"), temp_str.c_str());log_info(_T("Aborting Association"));cond = ASC_abortAssociation(assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Abort Failed: %s"), temp_str.c_str());return bSendOK;}}/* destroy the association, i.e. free memory of T_ASC_Association* structure. This *//* call is the counterpart of ASC_requestAssociation(...) which was called above. */cond = ASC_destroyAssociation(&assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("destroy association failed: %s"), temp_str.c_str());return bSendOK;}/* drop the network, i.e. free memory of T_ASC_Network* structure. This call *//* is the counterpart of ASC_initializeNetwork(...) which was called above. */cond = ASC_dropNetwork(&net);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Drop Network Failed: %s"), temp_str.c_str());return bSendOK;}return bSendOK;
}OFBool CDcmStoreSCU::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, ERM_autoDetect).good())return OFFalse;/* look in the meta-header first */OFBool found = DU_findSOPClassAndInstanceInDataSet(ff.getMetaInfo(), sopClass, sopClassSize, sopInstance, sopInstanceSize, OFFalse);if (!found)found = DU_findSOPClassAndInstanceInDataSet(ff.getDataset(), sopClass, sopClassSize, sopInstance, sopInstanceSize, OFFalse);return found;
}OFCondition CDcmStoreSCU::addStoragePresentationContexts(T_ASC_Parameters *params, OFList<OFString> &sopClasses)
{/** Each SOP Class will be proposed in two presentation contexts (unless* the opt_combineProposedTransferSyntaxes global variable is true).* The command line specified a preferred transfer syntax to use.* This prefered transfer syntax will be proposed in one* presentation context and a set of alternative (fallback) transfer* syntaxes will be proposed in a different presentation context.** Generally, we prefer to use Explicitly encoded transfer syntaxes* and if running on a Little Endian machine we prefer* LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.* Some SCP implementations will just select the first transfer* syntax they support (this is not part of the standard) so* organise the proposed transfer syntaxes to take advantage* of such behaviour.*/// Which transfer syntax was preferred on the command lineOFString preferredTransferSyntax;/* gLocalByteOrder is defined in dcxfer.h */if (gLocalByteOrder == EBO_LittleEndian){/* we are on a little endian machine */preferredTransferSyntax = UID_LittleEndianExplicitTransferSyntax;}else{/* we are on a big endian machine */preferredTransferSyntax = UID_BigEndianExplicitTransferSyntax;}OFListIterator(OFString) s_cur;OFListIterator(OFString) s_end;OFList<OFString> fallbackSyntaxes;// - If little endian implicit is preferred, we don't need any fallback syntaxes// because it is the default transfer syntax and all applications must support it.// - If MPEG2 or MPEG4 is preferred, we don't want to propose any fallback solution// because this is not required and we cannot decompress the movie anyway.fallbackSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);fallbackSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);fallbackSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);// Remove the preferred syntax from the fallback listfallbackSyntaxes.remove(preferredTransferSyntax);// create a list of transfer syntaxes combined from the preferred and fallback syntaxesOFList<OFString> 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;}// add the (short list of) known storage SOP classes to the list// the array of Storage SOP Class UIDs comes from dcuid.hfor (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]);// thin out the SOP classes to remove any duplicatesOFList<OFString> 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;}// add a presentations context for each SOP class / transfer syntax pairOFCondition cond = EC_Normal;int pid = 1; // presentation context ids_cur = sops.begin();s_end = sops.end();while (s_cur != s_end && cond.good()){if (pid > 255){return ASC_BADPRESENTATIONCONTEXTID;}if (combinedSyntaxes.size() > 0){if (pid > 255){//OFLOG_ERROR(storescuLogger, "Too many presentation contexts");return ASC_BADPRESENTATIONCONTEXTID;}// SOP class with fallback transfer syntaxcond = addPresentationContext(params, pid, *s_cur, combinedSyntaxes);pid += 2; /* only odd presentation context id's */}else{// SOP class with preferred transfer syntaxcond = addPresentationContext(params, pid, *s_cur, preferredTransferSyntax);pid += 2; /* only odd presentation context id's */}++s_cur;}return cond;
}OFBool CDcmStoreSCU::isaListMember(OFList<OFString> &lst, OFString &s)
{OFListIterator(OFString) cur = lst.begin();OFListIterator(OFString) end = lst.end();OFBool found = OFFalse;while (cur != end && !found) {found = (s == *cur);++cur;}return found;
}OFCondition CDcmStoreSCU::
addPresentationContext(T_ASC_Parameters *params,int presentationContextId,const OFString &abstractSyntax,const OFString &transferSyntax,T_ASC_SC_ROLE proposedRole/* = ASC_SC_ROLE_DEFAULT*/)
{const char *c_p = transferSyntax.c_str();OFCondition cond = ASC_addPresentationContext(params, presentationContextId,abstractSyntax.c_str(), &c_p, 1, proposedRole);return cond;
}OFCondition CDcmStoreSCU::
addPresentationContext(T_ASC_Parameters *params,int presentationContextId,const OFString &abstractSyntax,const OFList<OFString> &transferSyntaxList,T_ASC_SC_ROLE proposedRole/* = ASC_SC_ROLE_DEFAULT*/)
{// create an array of supported/possible transfer syntaxesconst char **transferSyntaxes = new const char*[transferSyntaxList.size()];int transferSyntaxCount = 0;OFListConstIterator(OFString) s_cur = transferSyntaxList.begin();OFListConstIterator(OFString) s_end = transferSyntaxList.end();while (s_cur != s_end) {transferSyntaxes[transferSyntaxCount++] = (*s_cur).c_str();++s_cur;}OFCondition cond = ASC_addPresentationContext(params, presentationContextId,abstractSyntax.c_str(), transferSyntaxes, transferSyntaxCount, proposedRole);delete[] transferSyntaxes;return cond;
}OFCondition CDcmStoreSCU::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;OFString temp_str;OFBool unsuccessfulStoreEncountered = OFTrue; // assumption//OFLOG_INFO(storescuLogger, "Sending file: " << fname);log_info(_T("发送文件: [%s]"),fname);/* read information from file. After the call to DcmFileFormat::loadFile(...) the information *//* which is encapsulated in the file will be available through the DcmFileFormat object. *//* In detail, it will be available through calls to DcmFileFormat::getMetaInfo() (for *//* meta header information) and DcmFileFormat::getDataset() (for data set information). */DcmFileFormat dcmff;OFCondition cond = dcmff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, ERM_autoDetect);/* figure out if an error occured while the file was read*/if (cond.bad()){//OFLOG_ERROR(storescuLogger, "Bad DICOM file: " << fname << ": " << cond.text());log_error(_T("Bad DICOM file: [%s][%s]"), fname, cond.text());return cond;}/* if required, invent new SOP instance information for the current data set (user option) *//*if (opt_inventSOPInstanceInformation) {replaceSOPInstanceInformation(dcmff.getDataset());}*//* figure out which SOP class and SOP instance is encapsulated in the file */if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), OFFalse)){log_error(_T("No SOP Class or Instance UID in file: [%s]"), fname);return DIMSE_BADDATA;}/* figure out which of the accepted presentation contexts should be used */DcmXfer filexfer(dcmff.getDataset()->getOriginalXfer());if (filexfer.getXfer() != EXS_Unknown)presID = ASC_findAcceptedPresentationContextID(assoc, sopClass, filexfer.getXferID());elsepresID = ASC_findAcceptedPresentationContextID(assoc, sopClass);if (presID == 0){const char *modalityName = dcmSOPClassUIDToModality(sopClass);if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);if (!modalityName) modalityName = "unknown SOP class";log_error(_T("No presentation context for: (%s) %s"), modalityName, sopClass);return DIMSE_NOVALIDPRESENTATIONCONTEXTID;}T_ASC_PresentationContext pc;ASC_findAcceptedPresentationContext(assoc->params, presID, &pc);DcmXfer netTransfer(pc.acceptedTransferSyntax);/* if required, dump general information concerning transfer syntaxes */DcmXfer fileTransfer(dcmff.getDataset()->getOriginalXfer());log_debug( _T("Converting transfer syntax: %s -> %s"), fileTransfer.getXferName(), netTransfer.getXferName());if (netTransfer.getXfer() != filexfer.getXfer()){cond = dcmff.getDataset()->chooseRepresentation(netTransfer.getXfer(), NULL);}/* prepare the transmission of data *///bzero(OFreinterpret_cast(char *, &req), sizeof(req));memset((void*)(&req), 0, 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;/* if required, dump some more general information *///OFLOG_INFO(storescuLogger, "Sending Store Request (MsgID " << msgId << ", "// << dcmSOPClassUIDToModality(sopClass, "OT") << ")");log_debug( _T("Sending Store Request (MsgID %d,%s)"), msgId, dcmSOPClassUIDToModality(sopClass, "OT"));/* finally conduct transmission of data */cond = DIMSE_storeUser(assoc, presID, &req,NULL, dcmff.getDataset(), progressCallback, this,DIMSE_BLOCKING, 0,&rsp, &statusDetail, NULL, OFStandard::getFileSize(fname));/** If store command completed normally, with a status* of success or some warning then the image was accepted.*/if (cond == EC_Normal && (rsp.DimseStatus == STATUS_Success || DICOM_WARNING_STATUS(rsp.DimseStatus))){unsuccessfulStoreEncountered = OFFalse;}/* remember the response's status for later transmissions of data */lastStatusCode = rsp.DimseStatus;/* dump some more general information */if (cond == EC_Normal){log_info(_T("Received Store Response (%s)"), DU_cstoreStatusString(rsp.DimseStatus));}else{//OFLOG_ERROR(storescuLogger, "Store Failed, file: " << fname << ":" << OFendl << DimseCondition::dump(temp_str, cond));DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Store Failed, file: [%s]\r\n%s"), fname, temp_str.c_str());}/* dump status detail information if there is some */if (statusDetail != NULL){//OFLOG_DEBUG(storescuLogger, "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));std::ostringstream ostr;ostr << DcmObject::PrintHelper(*statusDetail);temp_str = ostr.str();Replace(temp_str, _T("\n"), _T("\r\n"));log_debug(_T("Status Detail:%s"), temp_str.c_str());delete statusDetail;}return cond;
}void CDcmStoreSCU::progressCallback(void * callbackData,T_DIMSE_StoreProgress *progress,T_DIMSE_C_StoreRQ * req)
{CDcmStoreSCU* pUser = (CDcmStoreSCU*)callbackData;if (progress->state == DIMSE_StoreBegin){OFString str;//OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(str, *req, DIMSE_OUTGOING));DIMSE_dumpMessage(str, *req, DIMSE_OUTGOING);pUser->log_debug(str.c_str());}
}BOOL CDcmStoreSCU::Echo()
{T_ASC_Network *net;T_ASC_Parameters *params;DIC_NODENAME localHost;DIC_NODENAME peerHost;T_ASC_Association *assoc;OFString temp_str;/* initialize network, i.e. create an instance of T_ASC_Network*. */OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, 30, &net);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("初始化网络失败: \r\n%s"), temp_str.c_str());return FALSE;}/* initialize asscociation parameters, i.e. create an instance of T_ASC_Parameters*. */cond = ASC_createAssociationParameters(¶ms, ASC_DEFAULTMAXPDU);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Create Association Parameters Failed: %s"), temp_str.c_str());return FALSE;}/* sets this application's title and the called application's title in the params *//* structure. The default values to be set here are "STORESCU" and "ANY-SCP". */ASC_setAPTitles(params, m_param.localAET.c_str(), m_param.remoteAET.c_str(), NULL);/* Set the transport layer type (type of network connection) in the params *//* strucutre. The default is an insecure connection; where OpenSSL is *//* available the user is able to request an encrypted,secure connection. */cond = ASC_setTransportLayerType(params, OFFalse);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Set Transport Layer Type Failed: %s"), temp_str.c_str());return FALSE;}/* Figure out the presentation addresses and copy the *//* corresponding values into the association parameters.*/gethostname(localHost, sizeof(localHost) - 1);sprintf_s(peerHost, "%s:%d", m_param.serverIP.c_str(), m_param.port);ASC_setPresentationAddresses(params, localHost, peerHost);/* Set the presentation contexts which will be negotiated *//* when the network connection will be established */int presentationContextID = 1; /* odd byte value 1, 3, 5, .. 255 */for (unsigned long ii = 0; ii < 1; ii++){cond = ASC_addPresentationContext(params, presentationContextID, UID_VerificationSOPClass,EchotransferSyntaxes, 1);presentationContextID += 2;if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Add Presentation Context Failed: %s"), temp_str.c_str());return FALSE;}}/* dump presentation contexts if required *///OFLOG_DEBUG(echoscuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ);Replace(temp_str, _T("\n"), _T("\r\n"));log_debug( _T("Request Parameters:\r\n%s"), temp_str.c_str());/* create association, i.e. try to establish a network connection to another *//* DICOM application. This call creates an instance of T_ASC_Association*. */cond = ASC_requestAssociation(net, params, &assoc);if (cond.bad()){if (cond == DUL_ASSOCIATIONREJECTED){T_ASC_RejectParameters rej;ASC_getRejectParameters(params, &rej);ASC_printRejectParameters(temp_str, &rej);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Rejected:\r\n%s"), temp_str.c_str());return FALSE;}else{DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Request Failed: %s"), temp_str.c_str());return FALSE;}}/* dump the presentation contexts which have been accepted/refused */ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC);Replace(temp_str, _T("\n"), _T("\r\n"));log_debug( _T("Association Parameters Negotiated:\r\n%s"), temp_str.c_str());/* count the presentation contexts which have been accepted by the SCP *//* If there are none, finish the execution */if (ASC_countAcceptedPresentationContexts(params) == 0){log_error(_T("No Acceptable Presentation Contexts"));return FALSE;}/* dump general information concerning the establishment of the network connection if required */log_debug( _T("Association Accepted (Max Send PDV: %d)"), assoc->sendPDVLength);/* do the real work, i.e. send a number of C-ECHO-RQ messages to the DICOM application *//* this application is connected with and handle corresponding C-ECHO-RSP messages. */cond = echoSCU(assoc);BOOL bEchoOK = FALSE;if (cond == EC_Normal)bEchoOK = TRUE;/* tear down association, i.e. terminate network connection to SCP */if (cond == EC_Normal){/* release association */log_debug( _T("Releasing Association"));log_info(_T("连接成功."));cond = ASC_releaseAssociation(assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Release Failed: %s"), temp_str.c_str());return bEchoOK;}}else if (cond == DUL_PEERREQUESTEDRELEASE){log_error(_T("Protocol Error: Peer requested release (Aborting)"));log_info(_T("连接失败."));cond = ASC_abortAssociation(assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Abort Failed: %s"), temp_str.c_str());return bEchoOK;}}else if (cond == DUL_PEERABORTEDASSOCIATION){log_info(_T("Peer Aborted Association"));log_info(_T("连接失败."));}else{DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Store SCU Failed: %s"), temp_str.c_str());log_info(_T("连接失败."));cond = ASC_abortAssociation(assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Association Abort Failed: %s"), temp_str.c_str());return bEchoOK;}}/* destroy the association, i.e. free memory of T_ASC_Association* structure. This *//* call is the counterpart of ASC_requestAssociation(...) which was called above. */cond = ASC_destroyAssociation(&assoc);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("destroy association failed: %s"), temp_str.c_str());return bEchoOK;}/* drop the network, i.e. free memory of T_ASC_Network* structure. This call *//* is the counterpart of ASC_initializeNetwork(...) which was called above. */cond = ASC_dropNetwork(&net);if (cond.bad()){DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Drop Network Failed: %s"), temp_str.c_str());return bEchoOK;}return bEchoOK;
}OFCondition CDcmStoreSCU::echoSCU(T_ASC_Association * assoc)
{DIC_US msgId = assoc->nextMsgID++;DIC_US status;DcmDataset *statusDetail = NULL;OFString temp_str;/* dump information if required */log_info(_T("Sending Echo Request (MsgID %d)"), msgId);/* send C-ECHO-RQ and handle response */OFCondition cond = DIMSE_echoUser(assoc, msgId, DIMSE_BLOCKING, 0, &status, &statusDetail);/* depending on if a response was received, dump some information */if (cond.good()){log_info(_T("Received Echo Response (%s)"), DU_cechoStatusString(status));}else{DimseCondition::dump(temp_str, cond);Replace(temp_str, _T("\n"), _T("\r\n"));log_error(_T("Echo Failed: %s"), temp_str.c_str());}/* check for status detail information, there should never be any */if (statusDetail != NULL){std::ostringstream ostr;ostr << DcmObject::PrintHelper(*statusDetail);temp_str = ostr.str();Replace(temp_str, _T("\n"), _T("\r\n"));log_debug("Status Detail (should never be any):\r\n%s", temp_str.c_str());delete statusDetail;}/* return result value */return cond;
}void CDcmStoreSCU::log_debug(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][DEBUG] " + str + "\r\n";if (0 >= m_param.loglevel) {::SendMessage(m_param.hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}void CDcmStoreSCU::log_info(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][INFO] " + str + "\r\n";if (1 >= m_param.loglevel) {::SendMessage(m_param.hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}void CDcmStoreSCU::log_warn(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][WARN] " + str + "\r\n";if (2 >= m_param.loglevel) {::SendMessage(m_param.hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}void CDcmStoreSCU::log_error(const char* fmt, ...)
{std::string str;va_list args;va_start(args, fmt);{int nLength = _vscprintf(fmt, args);nLength += 1;std::vector<char> vectorChars(nLength);_vsnprintf(vectorChars.data(), nLength, fmt, args);str.assign(vectorChars.data());}va_end(args);std::string timeStr = GetTimeStr(5);std::string logStr = "[" + timeStr + "][ERROR] " + str + "\r\n";if (3 >= m_param.loglevel) {::SendMessage(m_param.hLogWnd, UM_ADD_LOG, (WPARAM)logStr.c_str(), 0);}
}
四、调用CDcmStoreSCU类
1. 测试连接-Echo
void CStoreSCUDlg::OnBnClickedButtonStorescuEcho()
{UpdateData();if (m_remoteAET.IsEmpty() || m_serverIP.IsEmpty() || m_localAET.IsEmpty()){MessageBox(_T("请正确填写DICOM网络参数."));return;}CDcmStoreSCU scu;int ll = m_cmbLL.GetCurSel();SCUParam param;param.remoteAET = m_remoteAET;param.serverIP = m_serverIP;param.hLogWnd = GetSafeHwnd();param.loglevel = ll;param.localAET = m_localAET;param.port = m_port;scu.SetParam(param);scu.Echo();
}
2. 发送文件-Send
每次发送不超过100个文件
void CStoreSCUDlg::OnBnClickedButtonStorescuSend()
{UpdateData();if (m_remoteAET.IsEmpty() || m_serverIP.IsEmpty() || m_localAET.IsEmpty()){MessageBox(_T("请正确填写DICOM网络参数."));return;}if (m_files.size() <= 0){MessageBox(_T("请选择文件!"));return;}CDcmStoreSCU scu;int ll = m_cmbLL.GetCurSel();SCUParam param;param.remoteAET = m_remoteAET;param.serverIP = m_serverIP;param.hLogWnd = GetSafeHwnd();param.loglevel = ll;param.localAET = m_localAET;param.port = m_port;scu.SetParam(param);int sendCount = 0;while (m_files.size() > sendCount) {std::vector<std::string> vec;vec.reserve(100);int count = 100;if (m_files.size() - sendCount < 100) count = m_files.size() - sendCount;vec.assign(m_files.begin() + sendCount, m_files.begin() + sendCount + count);scu.Send(vec);sendCount += count;}
}
总结
下载StoreSCU.exe