Basic CGET with dcmtk.

All other questions regarding DCMTK

Moderator: Moderator Team

Post Reply
Message
Author
hakan
Posts: 5
Joined: Tue, 2010-02-23, 16:37

Basic CGET with dcmtk.

#1 Post by hakan »

Hello OFFIS;
this code is a simple skeleton for cget and dcmtk implementation. I test it some series and some images. If some thing wrong, please warn me. I will use this logic in our viewer. Ofcourse there are lots of part copy-paste from dcmtk library.

Best regards.

Code: Select all

// getscu001.cpp : Defines the entry point for the console application.

#include "stdafx.h"

#include <dcmtk/dcmnet/dimse.h>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmimgle/dcmimage.h>

#include <vtkImageViewer2.h>
#include <vtkImageData.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>

T_ASC_Network *m_net;
T_ASC_Parameters *m_params = NULL;

vtkImageData *image = NULL;
DcmDataset *m_imageDataSet = NULL;

typedef struct tagQuerySyntax
{
	const char *findSyntax;
    const char *getSyntax;
}TQuerySyntax;

static TQuerySyntax querySyntax[3] =
{
	{UID_FINDPatientRootQueryRetrieveInformationModel, UID_GETPatientRootQueryRetrieveInformationModel},
	{UID_FINDStudyRootQueryRetrieveInformationModel, UID_GETStudyRootQueryRetrieveInformationModel},
	{UID_FINDPatientStudyOnlyQueryRetrieveInformationModel, UID_GETPatientStudyOnlyQueryRetrieveInformationModel}
};

typedef enum tagStandartQueryInformationModel
{
	sqimPatient = 0,
	sqimStudy,
	sqimPSOnly
}TStandartQueryInformationModel;

int m_opt_abstractSyntax = sqimPatient;
DcmDataset *m_overrideKeys;

static bool addPresentationContext(T_ASC_Parameters*);
static bool getSCU(T_ASC_Association*);

int _tmain(int argc, _TCHAR* argv[])
{
    WSAData winSockData;
    WORD winSockVersionNeeded = MAKEWORD( 1, 1 );// we need at least version 1.1
    if( WSAStartup(winSockVersionNeeded, &winSockData) )
	{//error
		std::cout<<"error in WSAStartup()\n";
		return 1;
	}

	OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, 10, &m_net);
	if(cond.bad())
	{
		std::cout<<"error in ASC_initializeNetwork()\n";
		WSACleanup();
		return 1;
	}


	cond = ASC_createAssociationParameters(&m_params, ASC_DEFAULTMAXPDU);//opt_maxReceivePDULength from parameters in findscu.exe
	if (cond.bad())
	{
		std::cout<<"error in ASC_createAssociationParameters()\n";
		ASC_dropNetwork(&m_net);
		WSACleanup();
		return 1;
	}

	//ASC_setAPTitles(m_params, "ANY-SCU", "ANY-SCP", "ANY-SCU");
	ASC_setAPTitles(m_params, "HAKAN", "AK_CLIENT", NULL);

	cond = ASC_setTransportLayerType(m_params, false);
	if (cond.bad())
	{
		std::cout<<"error in ASC_setTransportLayerType()...try to continue\n";
	}

	char localHost[DIC_NODENAME_LEN + 1];
	gethostname(localHost, sizeof(localHost) - 1);

	std::stringstream peerHost;
	//std::string m_host("87.106.65.167");
	std::string m_host("192.168.67.155");
	int m_port = 105;
	peerHost<<m_host<<":"<<m_port;

	ASC_setPresentationAddresses(m_params, localHost, peerHost.str().c_str());


	if( !ASC_countPresentationContexts(m_params) )
	{
		if( !addPresentationContext(m_params) )
		{//error
			std::cout<<"error in addPresentationContext()\n";
			ASC_dropNetwork(&m_net);
			WSACleanup();
			return 1;
		}
	}
	
	

	T_ASC_Association *assoc;
    cond = ASC_requestAssociation(m_net, m_params, &assoc);
    if( cond.bad() )
	{
		std::cout<<"error in ASC_requestAssociation()\n";
		ASC_dropNetwork(&m_net);
		WSACleanup();
		return 1;

	}

    // count the presentation contexts which have been accepted by the SCP If there are none, finish the execution
    if( !ASC_countAcceptedPresentationContexts(m_params) )
	{//error
		std::cout<<"error in ASC_countAcceptedPresentationContexts()\n";

		ASC_releaseAssociation(assoc);
		ASC_destroyAssociation(&assoc);

		ASC_dropNetwork(&m_net);
		WSACleanup();
		return 1;

	}

	getSCU(assoc);
	
	cond = ASC_releaseAssociation(assoc);
    if (cond.bad())
	{
		std::cout<<"error in ASC_releaseAssociation()\n";
	}

	cond = ASC_destroyAssociation(&assoc);
    if (cond.bad())
	{
		std::cout<<"error in ASC_destroyAssociation()\n";
	}


	cond = ASC_dropNetwork(&m_net);
    if (cond.bad())
	{//error
		std::cout<<"error in ASC_dropNetwork()\n";
	}

    WSACleanup();

	if( !image )
	{
		std::cout<<"error image is NULL\n";
		return 1;
	}


	vtkImageViewer2 *ImageViewer = vtkImageViewer2::New();
	ImageViewer->SetInput( image );
	ImageViewer->SetColorLevel(127);
	ImageViewer->SetColorWindow(255);

	vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::New();
	ImageViewer->SetupInteractor(iren);
	ImageViewer->Render();
	ImageViewer->GetRenderer()->ResetCamera();
	iren->Start();
	ImageViewer->Delete();
	image->Delete();
	iren->Delete();
	return 0;
}

static void subOpCallback(
	void *callbackData,
	T_DIMSE_StoreProgress *progress,// progress state
	T_DIMSE_C_StoreRQ *request,// original store request
	char *imageFileName, DcmDataset **imageDataSet,// being received into
	T_DIMSE_C_StoreRSP *response,// final store response
	DcmDataset **statusDetail)
{
	if( progress->state != DIMSE_StoreEnd )
		return;

	// do not send status detail information
	*statusDetail = NULL;
	if( imageDataSet && *imageDataSet  )
	{
		m_imageDataSet = *imageDataSet;
		//DcmFileFormat dcmf(*imageDataSet);
		//dcmf.saveFile("test.dcm",(*imageDataSet)->getOriginalXfer());
		//delete *imageDataSet;
	}
}

static void substituteOverrideKeys(DcmDataset *dset)
{
	DcmDataset keys(*m_overrideKeys);

	// put the override keys into dset replacing existing tags
	unsigned long elemCount = keys.card();
	for (unsigned long i=0; i<elemCount; i++)
	{
		DcmElement *elem = keys.remove((unsigned long)0);
		dset->insert(elem, OFTrue);
	}
}

static void GetUserCallback( void *callbackData, T_DIMSE_C_GetRQ *request, int responseCount, T_DIMSE_C_GetRSP *response)
{
	if( response->DimseStatus == STATUS_Success )
		return;

/*
	DcmFileFormat dcmf(m_imageDataSet);
	dcmf.saveFile("test.dcm",m_imageDataSet->getOriginalXfer());
	delete m_imageDataSet;
*/

	static unsigned size = 0;
	const DiPixel *dp;
		DicomImage di(m_imageDataSet, m_imageDataSet->getOriginalXfer(), 0UL, 0UL, 0UL);
		dp = di.getInterData();

	long int val;
	int dim[3];
	m_imageDataSet->findAndGetLongInt(DCM_Columns, val);
	dim[0] = val;
	m_imageDataSet->findAndGetLongInt(DCM_Rows, val);
	dim[1] = val;

	if( !image )
	{

		dim[2] = response->NumberOfCompletedSubOperations + response->NumberOfRemainingSubOperations;

		double spacing[3];
		m_imageDataSet->findAndGetFloat64(DCM_PixelSpacing, spacing[0], 0);
		m_imageDataSet->findAndGetFloat64(DCM_PixelSpacing, spacing[1], 1);
		spacing[2] = 1;

		//vtkImageData *image = vtkImageData::New();
		image = vtkImageData::New();
		image->SetOrigin(0,0,0);
		image->SetDimensions(dim);
		image->SetSpacing(spacing);

		
		switch(dp->getRepresentation())
		{
		case EPR_Uint8:
			image->SetScalarTypeToUnsignedChar();
			size = sizeof(unsigned char);
			break;

		case EPR_Sint8:
			image->SetScalarTypeToChar();
			size = sizeof(char);
			break;

		case EPR_Uint16:
			image->SetScalarTypeToUnsignedShort();
			size = sizeof(unsigned short);
			break;

		case EPR_Sint16:
			image->SetScalarTypeToShort();
			size = sizeof(short);
			break;

		case EPR_Uint32:
			image->SetScalarTypeToUnsignedInt();
			size = sizeof(unsigned);
			break;

		case EPR_Sint32:
			image->SetScalarTypeToInt();
			size = sizeof(int);
			break;

		default:break;
		}

		image->AllocateScalars();
	}
	std::memcpy(image->GetScalarPointer(0,0, response->NumberOfCompletedSubOperations - 1), dp->getData(), dim[0] * dim[1] * size);
	image->Update();
	//m_windowCenter = 255;
	//m_windowWidth = 127;

	delete m_imageDataSet;

}


OFCondition MY_DIMSE_getUser(
        T_ASC_Association *assoc, 
        T_ASC_PresentationContextID presID,
        T_DIMSE_C_GetRQ *request,
        DcmDataset *requestIdentifiers,
        DIMSE_GetUserCallback callback, void *callbackData,
        T_DIMSE_BlockingMode blockMode, int timeout,
        T_ASC_Network *net,

        //DIMSE_SubOpProviderCallback subOpCallback,
		DIMSE_StoreProviderCallback subOpCallback,
		void *subOpCallbackData,

        T_DIMSE_C_GetRSP *response, DcmDataset **statusDetail,
        DcmDataset **rspIds)
{
	DcmDataset *imageDataSet = NULL;
	T_DIMSE_Message req, rsp;
    DIC_US msgId;
    int responseCount = 0;
    DIC_US status = STATUS_Pending;

    if (requestIdentifiers == NULL)
		return DIMSE_NULLKEY;

    bzero((char*)&req, sizeof(req));
    bzero((char*)&rsp, sizeof(rsp));
    
    req.CommandField = DIMSE_C_GET_RQ;
	request->DataSetType = DIMSE_DATASET_PRESENT;
    req.msg.CGetRQ = *request;

    msgId = request->MessageID;

    OFCondition cond = DIMSE_sendMessageUsingMemoryData(assoc, presID, &req, NULL, requestIdentifiers, NULL, NULL);
    if (cond != EC_Normal)
        return cond;

    // receive responses
    while (cond == EC_Normal )//&& status == STATUS_Pending
	{
        bzero((char*)&rsp, sizeof(rsp));

        cond = DIMSE_receiveCommand(assoc, blockMode, timeout, &presID, &rsp, statusDetail);
        if (cond != EC_Normal) 
            break;//return cond;

		switch( rsp.CommandField )
		{
		case DIMSE_C_STORE_RQ:
			//cond = DIMSE_storeProvider(assoc,presID, &rsp.msg.CStoreRQ,"test.dcm",true,NULL,NULL,NULL,blockMode,timeout);
			imageDataSet = new DcmDataset();
			cond = DIMSE_storeProvider(assoc, presID, &rsp.msg.CStoreRQ, NULL, true, &imageDataSet, subOpCallback, subOpCallbackData, blockMode, timeout);
			break;

		case DIMSE_C_GET_RSP:
			if( callback )
				callback(callbackData, &req.msg.CGetRQ, ++responseCount/* !! */, &rsp.msg.CGetRSP);

			if( rsp.msg.CGetRSP.DimseStatus == STATUS_Success )
				return EC_Normal;
			break;

		default:
			char buf1[256];
			sprintf(buf1, "DIMSE: Unexpected Response Command Field: 0x%x", (unsigned)rsp.CommandField);
			return makeDcmnetCondition(DIMSEC_UNEXPECTEDRESPONSE, OF_error, buf1);
		}
	} //while (cond == EC_Normal )

    return cond;
}

static bool getSCU(T_ASC_Association *assoc)
{
	DIC_US msgId = assoc->nextMsgID++;
    T_ASC_PresentationContextID presId;

	T_DIMSE_C_GetRQ req;
	T_DIMSE_C_GetRSP rsp;
    DcmDataset *statusDetail = NULL;
    DcmDataset *rspIds = NULL;

    const char *sopClass = querySyntax[m_opt_abstractSyntax].getSyntax;

    presId = ASC_findAcceptedPresentationContextID(assoc, sopClass);
    if (presId == 0)
		return false;//DIMSE_NOVALIDPRESENTATIONCONTEXTID;

	bzero((char*)&req, sizeof(req));
	req.MessageID = msgId;
	strcpy(req.AffectedSOPClassUID, sopClass);
    req.Priority = DIMSE_PRIORITY_MEDIUM;
    req.DataSetType = DIMSE_DATASET_PRESENT;

//QUERY
	DcmFileFormat dcmf;// = new DcmFileFormat();
	m_overrideKeys =  dcmf.getDataset();//new DcmDataset();


	DcmElement *elem = newDicomElement(DCM_QueryRetrieveLevel);
	//elem->putString("IMAGE");
	elem->putString("SERIES");
	m_overrideKeys->insert(elem,true);

	elem = newDicomElement(DCM_PatientID);
	elem->putString("7Thjq7g");
	m_overrideKeys->insert(elem,true);

	elem = newDicomElement(DCM_StudyInstanceUID);
	elem->putString("1.2.840.113704.1.111.5600.1107858801.1");
	m_overrideKeys->insert(elem,true);

	elem = newDicomElement(DCM_SeriesInstanceUID);
	elem->putString("1.2.840.113704.1.111.5600.1107859261.7");
	//elem->putString("1.2.840.113704.1.111.5600.1107859163.3");
	m_overrideKeys->insert(elem,true);
/*
	elem = newDicomElement(DCM_SOPInstanceUID);
	elem->putString("1.2.840.113704.7.1.1.6124.1107859994.7");
	m_overrideKeys->insert(elem,true);
*/
	m_overrideKeys->insertEmptyElement(DCM_SOPInstanceUID,true);
	substituteOverrideKeys(m_overrideKeys);

	OFCondition cond = MY_DIMSE_getUser(assoc, presId, &req,
        m_overrideKeys,
        GetUserCallback/*NULL*/, NULL, DIMSE_BLOCKING, 0, m_net, subOpCallback, NULL, &rsp, &statusDetail, &rspIds);

	if( cond.bad() )
	{
		delete statusDetail;
		delete rspIds;
		return false;	
	}

	delete statusDetail;
	delete rspIds;
	return true;
}

static bool addPresentationContext(T_ASC_Parameters *params)
{
    /*
    ** We prefer to use Explicitly encoded transfer syntaxes.
    ** If we are 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.
    **
    ** The presentation contexts proposed here are only used for
    ** C-FIND and C-MOVE, so there is no need to support compressed
    ** transmission.
    */
	const char* knownAbstractSyntaxes[] ={UID_VerificationSOPClass};
    const char* transferSyntaxes[] = { NULL, NULL, NULL };
    int numTransferSyntaxes = 3;

    if (gLocalByteOrder == EBO_LittleEndian)  // defined in dcxfer.h
    {
        transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
        transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
    }
	else
	{
        transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
        transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
    }

	transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;


	OFCondition cond = ASC_addPresentationContext(params, 1, querySyntax[m_opt_abstractSyntax].findSyntax, transferSyntaxes, numTransferSyntaxes,ASC_SC_ROLE_SCUSCP);
	cond = ASC_addPresentationContext(params, 3, querySyntax[m_opt_abstractSyntax].getSyntax, transferSyntaxes, numTransferSyntaxes,ASC_SC_ROLE_SCUSCP);

	cond = ASC_addPresentationContext(params, 5, knownAbstractSyntaxes[0], transferSyntaxes, numTransferSyntaxes,ASC_SC_ROLE_SCUSCP);

	T_ASC_PresentationContextID id = 7;
	for(int I = 0; I < numberOfAllDcmStorageSOPClassUIDs; ++I)
	{
		cond = ASC_addPresentationContext(params,id,dcmAllStorageSOPClassUIDs[I],transferSyntaxes,numTransferSyntaxes,ASC_SC_ROLE_SCUSCP);
		id += 2;
	}
	return cond.good();
}

hakan
Posts: 5
Joined: Tue, 2010-02-23, 16:37

#2 Post by hakan »

I am so sad because dcmtk-3.6.0 was announced but I can't see anything about CGET... :(

Jörg Riesmeier
ICSMED DICOM Services
ICSMED DICOM Services
Posts: 2217
Joined: Fri, 2004-10-29, 21:38
Location: Oldenburg, Germany

#3 Post by Jörg Riesmeier »

Correct, there is still no C-GET SCU in DCMTK because there was no need for us to implement one.

What we have implemented is an MPPS SCU and a Storage Commitment SCU. However, they are not part of the publicly available DCMTK but have to be licensed separately (see web page).

The reasons for implementing new features or tools are (non-exhaustive):
  • We need this new feature/tool for our own work.
  • We think that there is a market for this new tool.
  • Somebody is paying for the development.
  • Somebody is contributing his/her code to us.
  • We think that this would be a nice feature and we have time for it :-)
Btw, there is a new experimental SCU class that would be a good starting point for implementing a C-GET SCU tool ...

ali.m.habib
Posts: 85
Joined: Sun, 2010-12-26, 17:34

#4 Post by ali.m.habib »

I want an event that indicates that connection is closed without a release request for example the connection is cut suddenly ? is there a way to check for this ?

omarelgazzar
Posts: 101
Joined: Wed, 2009-07-08, 16:06
Location: Oldenburg, Germany

#5 Post by omarelgazzar »

You can find such notifications in the DCMSCP class. You can try overwriting the event notifyAssociationTermination().
/** Overwrite this function to be notified when an association is terminated.
* The standard handler only outputs some information to the logger.
*/
virtual void notifyAssociationTermination();

J. Riesmeier
DCMTK Developer
Posts: 2503
Joined: Tue, 2011-05-03, 14:38
Location: Oldenburg, Germany
Contact:

#6 Post by J. Riesmeier »

FYI: Now, there is a "getscu" in the current development version of the DCMTK. See this commit. Of course, it will also be part of the next snapshot.

Post Reply

Who is online

Users browsing this forum: Baidu [Spider] and 1 guest