DICOM : Is this a valid Associate Response? - dicom

My application is Storage SCU. It pushes NM and CT instances to third party PACS. I am proposing four presentation context in my association (Associate Request). PACS is responding (Associate Response) like below:
Application Context: DICOM Application Context Name
Implementation Class: 1.2........
Implementation Version: XYZ
Maximum PDU Size: 32768
Called AE Title: PACS
Calling AE Title: MyApp
Presentation Contexts: 4
Presentation Context: 1 [Accept]
Abstract: Nuclear Medicine Image Storage
Transfer: Explicit VR Little Endian
Presentation Context: 3 [Reject - Transfer Syntaxes Not Supported]
Abstract: Nuclear Medicine Image Storage
Transfer: JPEG 2000 Lossy
Presentation Context: 5 [Proposed]
Abstract: CT Image Storage
Transfer: Explicit VR Little Endian
Presentation Context: 7 [Reject - Transfer Syntaxes Not Supported]
Abstract: CT Image Storage
Transfer: JPEG 2000 Lossy
Second and fourth (id 3 and 7) presentation context is rejected as expected. PACS DICOM Conformance statement states that it does not support that transfer syntax.
First (id 1) presentation context is accepted as expected.
Look at third (id 5) presentation context. It's status says [Proposed].
In my understanding, PACS should either accept the presentation context or reject it. It must not keep the status [Proposed] as is which was set by SCU i.e. my application.
Is my understanding correct?
I am looking in to specifications to find something concluding; no success so far. Please point me to the location in specifications where this is explained.
Edit 1:
PS 3.7-2011 - Message Exchange
D.3.2 Presentation contexts negotiation
c. the Association-acceptor may accept or reject each Presentation
Context individually.
Look at the may in specifications. What does this mean? Is it up to the SCP to "accept or reject (or leave as is i.e. [proposed])" the status?

"Proposed" means that the SCP didn't include in the reply the abstract syntax with the proper code:
0 acceptance​
1 user-rejection​
2 no-reason (provider rejection)​
3 abstract-syntax-not-supported (provider rejection)​
4 transfer-syntaxes-not-supported (provider rejection)​
Therefore DCMTK leaves in the abstract syntax the original status of "Not yet negotiated" (printed as "proposed" in the logs).
In DCMTK this status is represented by the constant ASC_P_NOTYETNEGOTIATED.
It looks like the SCP is to blame here

Related

Kafka Producer for internal topics with individual configs

One of my topologies generates an internal topic e.g. KSTREAM-AGGREGATE-STATE-STORE-0000000031 (see snipped from below) and for which an internal topic <app-id>KSTREAM-AGGREGATE-STATE-STORE-0000000031-changelog is created.
<...>
Processor: KSTREAM-FLATMAPVALUES-0000000022 (stores: [])
--> KSTREAM-AGGREGATE-0000000032, KSTREAM-FLATMAP-0000000027, KSTREAM-MAP-0000000023, KSTREAM-MAP-0000000025, KSTREAM-MAP-0000000029
<-- KSTREAM-TRANSFORMVALUES-0000000017
Processor: KSTREAM-AGGREGATE-0000000032 (stores: [KSTREAM-AGGREGATE-STATE-STORE-0000000031])
--> KTABLE-TOSTREAM-0000000033
<-- KSTREAM-FLATMAPVALUES-0000000022
Processor: KTABLE-TOSTREAM-0000000033 (stores: [])
--> KSTREAM-PEEK-0000000034
<-- KSTREAM-AGGREGATE-0000000032
<...>
the topology is defined as follows (BusObjKey and BusObj both are Avro Objects with according serdes, TransformBusObj provides the business logic for the aggregation and later mapping)
<...>
KStream<BusObjKey, BusObj> busObjStream = otherBusObjStream
.groupByKey()
.windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
.aggregate(BusObj::new,
TransformBusObj::aggregate,
Materialized.with(busObjKeySerde, busObjSerde))
.toStream()
.map(TransformBusObj::map);
<...>
How can I controll the properties used for the producer creating as well sending messages to <app-id>KSTREAM-AGGREGATE-STATE-STORE-0000000031-changelog ? In particular I would need to turn compression on (e.g. config.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy") ). Since I do not want to have compression all over the other producers I wonder of how to achive this in Spring Boot.
If you use the config.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"snappy"), it will turn on compression for all the producers in the Kafka Streams application. The only workaround I can think of would be to provide your own producer via the overloaded KafkaStreams constructor that accepts a KafkaClientSupplier. In your custom producer, you can inspect the topic name before sending and manually compress the data. Since you're manually compressing the data, I believe you'd also have to provide a custom restore consumer that "knows" to decompress. But I'm not sure this suggestion would even work or would be worth the effort.
HTH,
Bill

How can I do a DICOM query and retrieve from a Ginkgo PACS (or any other) using dcm4che

I'm writing a "simple" HL7 listener and then using the dcm4che binary utility movescu to make a query and retrieve operation from a remote PACS
I need to retrieve a Study and I have (00080050) AccessionNumber from the HL7 data, then I do the following:
./movescu -b LOCALAET#{local_ip}:044 -c REMOTEAET#{remote_ip}:104 --dest LOCALAET#{local_ip}:104 -m 00080050={known_accession_number}
with a remote PACS I am getting this:
(0000,0002) UI [1.2.840.10008.5.1.4.1.2.2.2] AffectedSOPClassUID
(0000,0100) US [32801] CommandField
(0000,0120) US [1] MessageIDBeingRespondedTo
(0000,0800) US [257] CommandDataSetType
(0000,0900) US [49152] Status
(0000,0902) LO [Error querying db (ImageRetrieve)] ErrorComment
(0000,1020) US [0] NumberOfRemainingSuboperations
(0000,1021) US [0] NumberOfCompletedSuboperations
(0000,1022) US [0] NumberOfFailedSuboperations
(0000,1023) US [0] NumberOfWarningSuboperations
And with a local Ginkgo I am getting:
17:12:58,906 DEBUG - LOCALAET->REMOTEAET(1): enter state: Sta6 - Association established and ready for data transfer
17:12:58,946 INFO - LOCALAET->REMOTEAET(1) >> A-ABORT[source: 0 - service-user, reason: 0]
17:12:58,946 INFO - LOCALAET->REMOTEAET(1): close Socket[addr=/XXX.XXX.X.XX,port=8080,localport=36105]
17:12:58,947 DEBUG - LOCALAET->REMOTEAET(1): enter state: Sta1 - Idle
movescu: Sta1 - Idle
org.dcm4che3.net.AssociationStateException: Sta1 - Idle
at org.dcm4che3.net.State.writeAReleaseRQ(State.java:223)
at org.dcm4che3.net.Association.release(Association.java:326)
at org.dcm4che3.tool.movescu.MoveSCU.close(MoveSCU.java:331)
at org.dcm4che3.tool.movescu.MoveSCU.main(MoveSCU.java:268)
(using different port on Ginkgo)
So what am I missing?
I do not know the Ginkgo PACS, but quite likely your retrieval fails because your request is malformed.
See PS3.4, C.4.2.1.4.1 Request Identifier Structure
Your request must include the attribute Query Retrieve Level (0008,0052) which I assume will be "STUDY" in your case since the Accession Number is a study-level attribute.
Furthermore it must contain
"Unique Key Attributes, which may include Patient ID (0010,0020),
Study Instance UIDs (0020,000D), Series Instance UIDs (0020,000E), and
the SOP Instance UIDs (0008,0018)"
That is, you have to specify the scope of your retrieve request by providing unique identifiers for the patient/study/series/image(s) you want to move - and nothing else!
So Accession Number may be used to query (C-FIND) for the corresponding Study Instance UID that you need for the C-MOVE. But it is not allowed in the C-MOVE-Request.
Caution: Whether or not you must include or omit the Patient-ID (0010,0020) in your C-MOVE request depends on the information model that you have negotiated during association establishment and that you select by choosing the presentation context for your message. You must include it in Patient Root, you must not include it in Study Root.

What should be the contents of OSI CCR atomic action identifier?

X/Open XA specification defines a transaction identifier for both recognizing transaction ID and making them unique.
One of the recommendation is to use OSI CCR atomic action identifier for this purpose.
The atomic action identifier is defined, according to X/Open spec, by ISO/IEC 9804.3 (1989). That ISO spec doesn't seem to be around anymore, ISO's own website won't even find it.
I found ITU X.852 that seems to define atomic action identifiers, but doesn't go into any detail what the contents should be (besides being unique).
So far, my web crawling really seems to be a dead end, so I was wondering if anybody had any information on what are the OSI CCR contents supposed to be, or whether I'm better off coming up with my own format ID, and generating some reasonable transaction IDs that make sense for my application.
I was searching for the same topic and the best information I found was in "ACSE/Presentation: Transaction Processing API (XAP-TP)"
1.3.19 Atomic Action Identifiers
OSI TP uses CCR Atomic Action Identifiers (AAIds) to identify uniquely a provider-supported distributed transaction. The AAId applies to the entire transaction tree. So, each transaction branch of the tree has the same AAId. The AAId is formed from the Application Entity Title (AET) of the originator of the transaction and a suffix unique within the scope of the AET.
Note: The AAId must be globally unique.
As far as I understand it says that OSI-CCR identifier has a prefix (unique among applications) and a suffix (some kind of counter/id). I don't think the original document goes into more details.
I'm using an algorithm based on Twitter's Snowflake (https://github.com/twitter-archive/snowflake/tree/b3f6a3c6ca8e1b6847baa6ff42bf72201e2c2231) that gives me a unique 64bit number.
For anyone coming across this in the future, the answer is entirely given within public standards. X.852 gives the following ASN.1 definition
CCR { joint-iso-itu-t ccr(7) module(1) ccr-apdus1(1) version3(3) }
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
EXPORTS ... ;
IMPORTS
AE-title
FROM ACSE-1 { joint-iso-itu-t association-control(2) module(2) apdus(1) version1(1) };
-- ASN.1 module defined in ITU-T Rec. X.227 | ISO/IEC 8650-1
ATOMIC-ACTION-IDENTIFIER ::= SEQUENCE
{
owners-name CHOICE {
name [0] EXPLICIT AE-title,
side [1] ENUMERATED
{ sender(0), receiver(1), ... },
... },
atomic-action-suffix CHOICE {
form1 [2] OCTET STRING,
form2 [3] INTEGER,
... }
}
side is shorthand and basically means "use the AE-Title belonging to the specified end of the connection, as we already exchanged"
AE-Title is imported from X.227
ACSE-1 { joint-iso-itu-t association-control(2) modules(0) apdus(0) version1(1) }
-- ACSE-1 refers to ACSE version 1
DEFINITIONS ::=
BEGIN
v
-- The data types Name and RelativeDistinguishedName are imported from ISO/IEC9594-2.
-- object identifier assignments.
IMPORTS Name, RelativeDistinguishedName
FROM InformationFramework { joint-iso-ccitt ds(5) modules(1) informationFramework(1) } ;
-- As defined in CCITT Rec. X.650 | ISO 7498-3, an application-entity title is composed of an
-- application-process title and an application-entity qualifier. The ACSE protocol provides for the transfer of
-- an application-entity title value by the transfer of its component values. However, the following data type is
-- provided for International Standards that reference a single syntactic structure for AE titles.
AE-title ::= CHOICE { AE-title-form1, AE-title-form2 }
AE-title-form1 ::= Name
-- For access to The Directory (ITU-T Rec. X.500-Series | ISO/IEC 9594), an AE title
-- has AE-title-form1. This value can be constructed from AP-title-form1 and AE-qualifier-form1 values
-- contained in an AARQ or AARE APDU. A discussion of forming an AE-title-form1 from AP-title-form1 and
-- AE-qualifier-form1 may be found in CCITT Rec. X.665 | ISO/IEC 9834-6.
AE-title-form2 ::= OBJECT IDENTIFIER
Continuing the standards chase, Name comes from X.501, and is the same definition as used for x.509 certificates (i.e. the DC=com, DC=example, OU=users, CN=bob type structure)
The full definition can be found in either X.501 or the IETF PKIX RFCs. The latest version of X.501 is only available after payment due to ISO nonsense, but older versions including the 2016 revision are free.
The XAP-TP page 52 effectively confirms that the XA XID is the BER encoding of ATOMIC-ACTION-IDENTIFIER, after expanding any side shorthand. (I presume this should actually be the DER encoding, so they're byte comparable)

DCMTK findscu returns multiple results when one is expected

I am trying to run a c-move to get a RTDOSe from a given RTPlan, first I tried to find the rtdose that references my rtplan. I expected that result to be a single item but I am getting multiple items. Here is my find scu:
findscu -v -aet DCMTK -aec VMSDBD1 -S -k "0008,0052=IMAGE" -k "0008,0016=1.2.840.10008.5.1.4.1.1.481.2" -k "0020,000D=1.2.xxx.xxx.71.1.xxx173684671.xxxx20.20160817145909" -k "(300c,0002)[0].ReferencedSOPInstanceUID=1.2.xxx.xxx.71.5.xxxx73684671.xxxx31.2016092318xxxx" xx.xx.xx.20 5678
I get a result like:
W: Find Response: 1 (Pending)
(...)
W: Find Response: 2 (Pending)
I: ===================== INCOMING DIMSE MESSAGE ====================
I: Message Type : C-FIND RSP
I: Message ID Being Responded To : 1
I: Affected SOP Class UID : FINDStudyRootQueryRetrieveInformationModel
I: Data Set : none
I: DIMSE Status : 0x0000: Success
I: ======================= END DIMSE MESSAGE =======================
I: Releasing Association
Question 1: why am I getting multiple results instead of the one dose that references the RTpLAN ?
Question 2: after replacing findscu by movescu I get an error about the syntax:
"(300c,0002)[0].ReferencedSOPInstanceUID= ...
Is this syntax not supported for movescu ?
Given an RTPLAN object, how would you query the corresponding RTDOSE ?
Thank you.
GT
Your C-FIND request (using findscu) is not standard conformant: When querying on IMAGE level in Study Root Information Model you also have to specify the Series Instance UID. Furthermore, you should make sure that your other query keys are actually supported by the SCP.
Is this syntax not supported for movescu ?
This C-MOVE request (using movescu) is also not standard conformant: When retrieving DICOM objects on IMAGE level in Study Root Information Model you have to specify the Study Instance UID, the Series Instance UID and the SOP Instance UID. The Referenced SOP Instance UID (as part of the Referenced RT Plan Sequence) is not supported for this service / message.
See DICOM PS3.4 for details on the Query/Retrieve Service.
Added on 2016-01-09:
I forgot to answer your final question:
Given an RTPLAN object, how would you query the corresponding RTDOSE ?
I would check whether the Referenced Dose Sequence (300C,0080) is present in the RT Plan object, and if so, use the Referenced SOP Instance UID(s) from the contained item(s) for a subsequent retrieve (i.e. C-MOVE request).

Dynamics AX 2009 AIF Tables

Background
I have an issue where roughly once a month the AIFQueueManager table is populated with ~150 records which relate to messages which had been sent to AX (where they "successfully failed"; i.e. errorred due to violation of business rules, but returned an exception as expected) over 6 months ago.
Question
What tables are involved in the AIF inbound message process / what order to events occur in? e.g. XML file is picked up and recorded in the AifDocumentLog, data's extracted and added to the AifQueueManager and AifGatewayQueue tables, records from here are then inserted in the AifMessageLog, etc.
Thanks in advance.
There are 4 main AIF classes, I will be talking about the inbound only, and focusing on the included file system adapter and flat XML files. I hope this makes things a little less hazy.
AIFGatewayReceiveService - Uses adapters/channels to read messages in from different sources, and dumps them in the AifGatewayQueue table
AIFInboundProcessingService - This processes the AifGatewayQueue table data and sends to the Ax[Document] classes
AIFOutboundProcessingService - This is the inverse of #2. It creates XMLs with relevent metadata
AIFGatewaySendService - This is the inverse of #1, where it uses adapters/channels to send messages out to different locations from the AifGatewayQueue
For #1
So #1 basically fills the AifGatewayQueue, which is just a queue of work. It loops through all of your channels and then finds the relevant adapter by ClassId. The adapters are classes that implement AifIntegrationAdapter and AifReceiveAdapter if you wanted to make your own custom one. When it loops over the different channels, it then loops over each "message" and tries to receive it into the queue.
If it can't process the file for some reason, it catches exceptions and throws them in the SysExceptionTable [Basic>Periodic>Application Integration Framework>Exceptions]. These messages are scraped from the infolog, and the messages are generated mostly from the receive adaptor, which would be AifFileSystemReceiveAdapter for my example.
For #2
So #2 is processing the inbound messages sitting in the queue (ready/inprocess). The AifRequestProcessor\processServiceRequest does the work.
From this method, it will call:
Various calls to Classes\AifMessageManager, which puts records in the AifMessageLog and the AifDocumentLog.
This key line: responseMessage = AifRequestProcessor::executeServiceOperation(message, endpointActionPolicy); which actually does the operation against the Ax[Document] classes by eventually getting to AifDispatcher::callServiceMethod(...)
It gets the return XML and packages that into an AifMessage called responseMessage and returns that where it may be logged. It also takes that return value, and if there is a response channel, it submits that back into the AifGatewayQueue
AifQueueManager is actually cleared and populated on the fly by calling AifQueueManager::createQueueManagerData();.

Resources