Processing images with multiplie patient ID issuers

214 views
Skip to first unread message

karine....@sfr.fr

unread,
Oct 30, 2020, 12:24:30 PM10/30/20
to RSNA MIRC CTP/TFS User Group
Dear all, dear John,

We are working with a site having several Issuers of Patient IDs. 

When looking at the DICOM header of those exams, it may look like this:
- (0010,0020)Patient ID --> PatientID A
- (0010,0021)Issuer of Patient ID --> Issuer A
- (0010,1002)Other Patient IDs Sequence 
- 1>(0010,0020)Patient ID --> PatientID B
- 1>(0010,0021)Issuer of Patient ID --> Issuer B

While on some other exams, this will be reversed:
- (0010,0020)Patient ID --> PatientID B
- (0010,0021)Issuer of Patient ID --> Issuer B
- (0010,1002)Other Patient IDs Sequence 
- 1>(0010,0020)Patient ID --> PatientID A
- 1>(0010,0021)Issuer of Patient ID --> Issuer A

We are not sure why that is and don't know enough about DICOM to tell wether there is some kind of hierarchy in those patient ID numbering.

We want to process PatientID A from Issuer A, and remove PatientID B from Issuer B.

Our CTP pipelines may be of two kinds:

Type 1:
- DirectoryImportService
- LookupTableChecker
- DICOM Anonymizer :
(0010,0020) PatientID --> @lookup(this,PatientID)
(0010,1002) OtherPatientIDsSeq --> @process()
==> This does not do what we want since we only enter a replacement value for PatientID A and exams get quarantines at the LookupTableChecker stage waiting to get a replacement value for PatientID B
- DirectoryStorageService

Type 2:
- DirectoryImportService
- DICOM Anonymizer:
(0010,0020) PatientID --> @hash(this,16)
(0010,1002) OtherPatientIDsSeq --> @process()
- IDMap
==> Here we want to get the pseudonymization table for Issuer A IDs, which we cannot have when the Issuer A ID is in the (0010,1002)Other Patient IDs Sequence field.
- DirectoryStorageService

Can you help us find the right configuration?

One thing we thought of was to have a preliminary DICOM Anonymizer stage that would remove the Patient ID when Issuer of Patient ID is Issuer B, but we are not sure whether this is a good idea and how to do this.

Thank you very much in advance for your help!
Best regards,
Karine

John Perry

unread,
Oct 30, 2020, 1:50:24 PM10/30/20
to rsnas-ctpmir...@googlegroups.com
Karine:
 
I'm not sure I fully understand what you want to do. It looks like you want to replace A PatientIDs with ones you have stored in a lookup table, and you want to hash all other PatientIDs.
 
If my understanding is correct, I don't see how to do it with a single anonymizer. How about this:
 
Anonymizer 1:
(0010,0020) PatientID –> @keep()
(0012,0040) ClinicalTrialSubjectID –> @always()@lookup(PatientID ,PatientID,default,"zzzzzz")
 
IdMap
 
Anonymizer 2:
(0010,0020) PatientID –> @if(ClinicalTrialSubjectID,equals,"zzzzzz"){@hash(this,16)}{@contents(ClinicalTrialSubjectID)}
(0012,0040) ClinicalTrialSubjectID –> @remove()
 
If you need the IDMap stage, note that it must be between the two anonymizers.

JP
--
You received this message because you are subscribed to the Google Groups "RSNA MIRC CTP/TFS User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rsnas-ctpmirc-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rsnas-ctpmirc-user-group/56888cbc-ebb5-4142-8069-f439f631926dn%40googlegroups.com.

karine....@sfr.fr

unread,
Oct 30, 2020, 4:29:38 PM10/30/20
to RSNA MIRC CTP/TFS User Group
Hi John,

Thank you for your answer and sorry my question was not clear.  We have two use cases: 
- one where we want to replace patient IDs from Issuer A by ones that we have stored in the a lookup table, 
- one where we want to hash patient IDs from Issuer A  . 

In both use cases, we want to get rid of IDs from Issuer B. 

Would something like that work with a single DICOM Anonymizer?
For 1st use case:
(0010,0020) PatientID –> @if(IssuerOfPatientID,equals,"IssuerA"){@lookup(this,PatientID)}{@remove()}  
(0010,1002) OtherPatientIDsSeq --> @process()  

For 2nd use case:
(0010,0020) PatientID –> @if(IssuerOfPatientID,equals,"IssuerA"){@hash(this,16)}{@remove()}  
(0010,1002) OtherPatientIDsSeq --> @process()  

I confirm that in the 2nd use case, the IDMap is before the DICOMAnonymizer. My mistake.

Thanks a lot!
Karine

John Perry

unread,
Oct 30, 2020, 5:09:06 PM10/30/20
to rsnas-ctpmir...@googlegroups.com
Karine:
 
The PatientID element is required, so you shouldn't call @remove() in the false clause of the @if function.
 
What do you want to do with the PatientIDs that are not from IssuerA? As an example, if you don't want to modify the PatientIDs that are not from IssuerA, , then you can do the whole thing with one Anonymizer like this:
 
(0010,0020) PatientID –> @if(IssuerOfPatientID,equals,"IssuerA"){@lookup(this,PatientID)}{@keep()} 
(0010,1002) OtherPatientIDsSeq --> @process()
 
Or instead, if you want to hash those IDs, then do this:
 
(0010,0020) PatientID –> @if(IssuerOfPatientID,equals,"IssuerA"){@lookup(this,PatientID)}{@hash(this,16)} 
(0010,1002) OtherPatientIDsSeq --> @process() 
 

karine....@sfr.fr

unread,
Oct 30, 2020, 6:08:45 PM10/30/20
to RSNA MIRC CTP/TFS User Group
Hi John,

I think your proposed solution 
(0010,0020) PatientID –> @if(IssuerOfPatientID,equals,"IssuerA"){@lookup(this,PatientID)}{@hash(this,16)} 
(0010,1002) OtherPatientIDsSeq --> @process() 
would work for our first use case.

However, for our second use case, where we want to hash patient IDs from Issuer A and want to retrieve the pseudonymization table through the IDMap stage for this Issuer A, we have a problem with incoming studies that look like
- (0010,0020)Patient ID --> PatientID B
- (0010,0021)Issuer of Patient ID --> Issuer B
- (0010,1002)Other Patient IDs Sequence 
- 1>(0010,0020)Patient ID --> PatientID A
- 1>(0010,0021)Issuer of Patient ID --> Issuer A
since the IDMAP stage does not display information from the OtherPatientIDsSequence. Therefore all we see in the IDMAP stage are replacement values for PatientID B.

I guess what we would need is a preliminary stage where, in the above configuration, the "main" patient ID (PatientID B) is replaced by the information coming the OtherPatientIDsSequence (PatientID A), and the OtherPatientIDsSequence is removed. I just don't see how to do that.

Thanks a lot for your help!
Karine

John Perry

unread,
Oct 30, 2020, 8:54:42 PM10/30/20
to rsnas-ctpmir...@googlegroups.com
Karine:
 
It is possible to reference an element in an item dataset of a sequence element, but only in the first item dataset, so it isn't possible to use a sequence element as a lookup table using standard anonymizer functions.
 
It is possible, however, to implement an AnonymizerExtension to do exactly what you want. An AnonymizerExtension is a Plugin that can be called by an anonymizer script. An AnonymizerExtension has full access to the whole DICOM dataset through the methods of the dcm4che Dataset object.
 
There is a wiki article on how to write an AnonymizerExtension  (http://mircwiki.rsna.org/index.php?title=Developing_DICOM_Anonymizer_Extensions). The article contains example code, but the example is pretty simple, and you would have to do some more sophisticated things at the level of dcm4che to walk the item datasets of the OtherPatientIDsSequence element, looking for the one containing the PatientID with the IssuerOfPatientID you want. If you haven't done it before, there is a learning curve, but if it is really important, you can do it.

karine....@sfr.fr

unread,
Nov 1, 2020, 4:42:11 AM11/1/20
to RSNA MIRC CTP/TFS User Group
Hi John,

Thank you again for your response. Assuming that we only have one item dataset in this sequence element, would the following work?

Have a preliminary DICOM Anonymizer stage with:

(0010,0020)PatientID --> @if(IssuerOfPatientID,equals,"IssuerA"){@keep()}{@if([0010,1002]::IssuerOfPatientID,equals,"IssuerA"),{@contents[0010,1002]::PatientID}{@quarantine()}}

Thanks!
Karine

John Perry

unread,
Nov 1, 2020, 7:19:31 AM11/1/20
to rsnas-ctpmir...@googlegroups.com
Karine:
 
Alas, your proposed solution isn't allowed. The @if function does not support nested @if calls:
 
image
 
I could have worded that better. What I meant was that @if calls are not allowed in the subordinate clauses.
 
JP
image[1].png

karine....@sfr.fr

unread,
Nov 1, 2020, 8:20:50 AM11/1/20
to RSNA MIRC CTP/TFS User Group
Hi John,

Thank you for the clarification. Then I could maybe have my preliminary DICOM Anonymizer be:


(0010,0020)PatientID --> @if(IssuerOfPatientID,equals,"IssuerA"){@keep()}{@contents([0010,1002]::PatientID)}
(0010,0021)IssuerOfPatientID --> @if(this,equals,"IssuerA"){@keep()}{contents([0010,1002]::IssuerOfPatientID)}

This would do about the same, except there would not be the quarantine option at that stage. I could however call a quarantine option downstream in my second DICOM Anonymizer, if IssuerOfPatientID was not "IssuerA".

Thank you!
Karine

Reply all
Reply to author
Forward
0 new messages