系列文章目录
文章目录
前言
第四章完成图像接受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 codecs
DJDecoderRegistration::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 line
OFString 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 list
fallbackSyntaxes.remove(preferredTransferSyntax);
// create a list of transfer syntaxes combined from the preferred and fallback syntaxes
OFList<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.h
for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]);
// thin out the SOP classes to remove any duplicates
OFList<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 pair
OFCondition cond = EC_Normal;
int pid = 1; // presentation context id
s_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 syntax
cond = addPresentationContext(params, pid, *s_cur, combinedSyntaxes);
pid += 2; /* only odd presentation context id's */
}
else
{
// SOP class with preferred transfer syntax
cond = 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 syntaxes
const 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());
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_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;
}
}