尝试从 SCP 查询时如何调用 C-STORE

How to invoke C-STORE when trying to query from SCP

我一直在使用以下代码,它来自 pynetdicom 库,用于在我的机器 (SCU) 上查询和存储来自远程服务器 SCP 的一些图像。

"""
Query/Retrieve SCU AE example.

This demonstrates a simple application entity that support the Patient
Root Find and Move SOP Classes as SCU. In order to receive retrieved
datasets, this application entity must support the CT Image Storage
SOP Class as SCP as well. For this example to work, there must be an
SCP listening on the specified host and port.

For help on usage,
python qrscu.py -h
"""

import argparse
from netdicom.applicationentity import AE
from netdicom.SOPclass import *
from dicom.dataset import Dataset, FileDataset
from dicom.UID import ExplicitVRLittleEndian, ImplicitVRLittleEndian, \
    ExplicitVRBigEndian
import netdicom
# netdicom.debug(True)
import tempfile

# parse commandline
parser = argparse.ArgumentParser(description='storage SCU example')
parser.add_argument('remotehost')
parser.add_argument('remoteport', type=int)
parser.add_argument('searchstring')
parser.add_argument('-p', help='local server port', type=int, default=9999)
parser.add_argument('-aet', help='calling AE title', default='PYNETDICOM')
parser.add_argument('-aec', help='called AE title', default='REMOTESCU')
parser.add_argument('-implicit', action='store_true',
                    help='negociate implicit transfer syntax only',
                    default=False)
parser.add_argument('-explicit', action='store_true',
                    help='negociate explicit transfer syntax only',
                    default=False)

args = parser.parse_args()

if args.implicit:
    ts = [ImplicitVRLittleEndian]
elif args.explicit:
    ts = [ExplicitVRLittleEndian]
else:
    ts = [
        ExplicitVRLittleEndian,
        ImplicitVRLittleEndian,
        ExplicitVRBigEndian
    ]

# call back


def OnAssociateResponse(association):
    print "Association response received"


def OnAssociateRequest(association):
    print "Association resquested"
    return True


def OnReceiveStore(SOPClass, DS):
    print("FINALLY ENTERED")
    print "Received C-STORE", DS.PatientName
    try:
        # do something with dataset. For instance, store it.
        file_meta = Dataset()
        file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
        # !! Need valid UID here
        file_meta.MediaStorageSOPInstanceUID = "1.2.3"
        # !!! Need valid UIDs here
        file_meta.ImplementationClassUID = "1.2.3.4"
        filename = '%s/%s.dcm' % (tempfile.gettempdir(), DS.SOPInstanceUID)
        ds = FileDataset(filename, {},
                         file_meta=file_meta, preamble="[=10=]" * 128)
        ds.update(DS)
        #ds.is_little_endian = True
        #ds.is_implicit_VR = True
        ds.save_as(filename)
        print "File %s written" % filename
    except:
        pass
    # must return appropriate status
    return SOPClass.Success


# create application entity
MyAE = AE(args.aet, args.p, [PatientRootFindSOPClass,
                             PatientRootMoveSOPClass,
                             VerificationSOPClass], [StorageSOPClass], ts)
MyAE.OnAssociateResponse = OnAssociateResponse
MyAE.OnAssociateRequest = OnAssociateRequest
MyAE.OnReceiveStore = OnReceiveStore
MyAE.start()


# remote application entity
RemoteAE = dict(Address=args.remotehost, Port=args.remoteport, AET=args.aec)

# create association with remote AE
print "Request association"
assoc = MyAE.RequestAssociation(RemoteAE)


# perform a DICOM ECHO
print "DICOM Echo ... ",
st = assoc.VerificationSOPClass.SCU(1)
print 'done with status "%s"' % st

print "DICOM FindSCU ... ",
d = Dataset()
d.PatientsName = args.searchstring
d.QueryRetrieveLevel = "PATIENT"
d.PatientID = "*"
st = assoc.PatientRootFindSOPClass.SCU(d, 1)
print 'done with status "%s"' % st

for ss in st:
    if not ss[1]:
        continue
    # print ss[1]
    try:
        d.PatientID = ss[1].PatientID
    except:
        continue
    print "Moving"
    print d
    assoc2 = MyAE.RequestAssociation(RemoteAE)
    gen = assoc2.PatientRootMoveSOPClass.SCU(d, 'SAMTEST', 1)
    for gg in gen:
        print
        print gg
    assoc2.Release(0)
    print "QWEQWE"

print "Release association"
assoc.Release(0)

# done
MyAE.Quit()

运行 程序,我得到以下输出:

Request association
Association response received
DICOM Echo ...  done with status "Success "
DICOM FindSCU ...  done with status "<generator object SCU at 0x106014c30>"
Moving
(0008, 0052) Query/Retrieve Level                CS: 'PATIENT'
(0010, 0010) Patient's Name                      PN: 'P*'
(0010, 0020) Patient ID                          LO: 'Pat00001563'
Association response received
QWEQWE
Moving
(0008, 0052) Query/Retrieve Level                CS: 'PATIENT'
(0010, 0010) Patient's Name                      PN: 'P*'
(0010, 0020) Patient ID                          LO: 'Pat00002021'
Association response received
QWEQWE
Release association

Echo 有效,所以我知道我的关联有效,并且我能够按照输出的建议查询和查看服务器上的文件。但是,正如您所见,OnReceiveStore 似乎没有被调用。我是 DICOM 的新手,我想知道可能是什么情况。如果我错了请纠正我,但我认为行 gen = assoc2.PatientRootMoveSOPClass.SCU(d, 'SAMTEST', 1) 应该调用 OnReceiveStore。如果没有,请提供一些关于如何调用 C-STORE 的见解。

DICOM C-MOVE操作比较复杂,如果你对DICOM不是很熟悉的话。

你不是在调用 OnReceiveStore 你自己,它是在 SCP 开始向你的应用程序发送实例时调用的。您向具有您自己的 AE 标题的 SCP 发出 C-MOVE 命令,您希望在其中接收图像。在您的情况下 SAMTEST。由于这是C-MOVE命令的唯一参数,那么SCP需要配置before-hand才能知道SAMTEST AE的IP和端口。你做到了吗?

我不知道,为什么代码没有输出 SCP 对 C-MOVE 命令的响应。看起来应该如此。这些可以很好地指示正在发生的事情。您还可以检查 SCP 端的日志,看看发生了什么。

此外,这里有一些 quite good reading about C-MOVE operation