基于dcmtk的dicom工具 第六章 StoreSCU 图像发送

发布于:2025-07-20 ⋅ 阅读:(17) ⋅ 点赞:(0)

系列文章目录



前言

第四章完成图像接受StoreSCP。
本章为图像发送StoreSCU,参考dcmtk storscu.cc,源文件路径dcmtk-3.6.9\dcmnet\apps\storescu.cc
与第四章生成的程序测试效果如下:
在这里插入图片描述


一、界面介绍

vs2017 添加对话框工程请参考第三章
界面分为三个区:

  1. 参数设置区,主要设置StoreSCP的AE Title,IP,端口
  2. 文件加载区
  3. 日志显示区
    如下图:
    在这里插入图片描述

二、CDcmStoreSCU类

1. storescu.cc中的主要流程

  1. 调用findSOPClassAndInstanceInFile解析待发送文件,获取sopClassUID、sopInstanceUID,判断是否符合发送要求。
  2. ASC_initializeNetwork初始化网络
  3. ASC_createAssociationParameters
  4. ASC_setAPTitles
  5. ASC_setTransportLayerType
  6. ASC_setPresentationAddresses
  7. addStoragePresentationContexts
  8. ASC_requestAssociation
  9. ASC_countAcceptedPresentationContexts判断连接是否成功,不成功返回,成功则循环调用storeSCU发送文件
  10. storeSCU函数,调用DIMSE_storeUser真正发送文件
  11. 发送完成,释放连接,关闭网络

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.接口函数

  1. SetParam,设置连接参数,日志级别,日志窗口句柄
  2. Echo,测试连接函数
  3. 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(&params, 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(&params, 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


网站公告

今日签到

点亮在社区的每一天
去签到