changing session label during archive

365 views
Skip to first unread message

Kate Alpert

unread,
Dec 4, 2013, 2:24:28 PM12/4/13
to Xnat
Dear all,

Would someone be able to point me to the code that sets the session and subject labels when an mr session is uploaded to XNAT?

When studies have been anonymized prior to being sent, the dcm subject id and dcm subject name are often changed to the same value.  In XNAT, then, the session label will be the same as the subject label.  This means that only one session for that subject can be auto-archived and all subsequently arriving sessions will show up in prearchive with status "ERROR" since they couldn't be archived due to a duplicate session label restriction.

We would like to create a feature that, in the case of a duplicate session label, would create a session label out of subject label and a session date stamp in the format <subject_label>_<yyyymmdd>. 

Any advice?
Thanks!
Kate



David Gutman

unread,
Dec 4, 2013, 3:13:06 PM12/4/13
to xnat_di...@googlegroups.com

Kate j have a scrip t that fixes that as I have the same problem... I can send it to you offline to twst and if it works ill share with the group...

--
You received this message because you are subscribed to the Google Groups "xnat_discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to xnat_discussi...@googlegroups.com.
To post to this group, send email to xnat_di...@googlegroups.com.
Visit this group at http://groups.google.com/group/xnat_discussion.
For more options, visit https://groups.google.com/groups/opt_out.

Kate Alpert

unread,
Dec 4, 2013, 3:15:59 PM12/4/13
to Xnat
David,

That would be wonderful!  Thanks!

Kate

Tim Olsen

unread,
Dec 5, 2013, 6:07:21 PM12/5/13
to xnat_di...@googlegroups.com

There might be a way to easily do this in XNAT, but I can’t think of it.  Maybe Kevin would have an idea.

 

One other option would be to use CTP as a relay here.  It can modify the PATIENT_ID on the files to append the timestamp and then forward them to XNAT.

 

Tim

--

David Gutman

unread,
Dec 5, 2013, 7:07:49 PM12/5/13
to xnat_di...@googlegroups.com

Ill post the script.tomorrow.to the.group..

Archie, Kevin

unread,
Dec 8, 2013, 10:09:40 AM12/8/13
to <xnat_discussion@googlegroups.com>
Hi, Kate,

Sorry about the delay in responding -- I've been trying to think of a clean and straightforward way to handle this and coming up dry. So I'll sketch out the convoluted approach instead.

The project/subject/session labels are derived by an implementation of org.nrg.xnat.DicomObjectIdentifer, an interface defined in dicom-xnat-util. The choice of implementing class is in dicom-import-context.xml; by default, this is org.nrg.dcm.id.ClassicDicomObjectIdentifier. (That class and most of the associated class definitions are in the XNAT web app code, in org.nrg.dcm.id.)

ClassicDicomObjectIdentifer is a CompositeDicomObjectIdentifer that uses separate pieces to derive the project, subject, and session labels, thereby implementing the fairly complicated labeling rules that had accumulated by XNAT 1.5, but in a general framework where individual components can be modified or replaced.

So all you really need to do is make a replacement object identifier class. (Warning: I think it's all still reasonably clean and straightforward so far. It gets worse.) Usually I build these out of independent pieces for each of project, subject, and session, but here your pieces can't be independent because the session label needs to know something about the project it's in. In principle, each time you receive a file, you could identify the project, get a list of the sessions in that project, and check for conflicts. That sounds like a lot of database hits and the wheels would probably fall off if you were to upload a big session from a local scanner, so I suspect you want to do something clever with caching. (That's as far as I've pursued that thought, sorry. If you think you want to go down this road I'll be happy to answer questions and offer advice.)

  - Kevin

--
You received this message because you are subscribed to the Google Groups "xnat_discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to xnat_discussi...@googlegroups.com.
To post to this group, send email to xnat_di...@googlegroups.com.
Visit this group at http://groups.google.com/group/xnat_discussion.
For more options, visit https://groups.google.com/groups/opt_out.




The material in this message is private and may contain Protected Healthcare Information (PHI). If you are not the intended recipient, be advised that any unauthorized use, disclosure, copying or the taking of any action in reliance on the contents of this information is strictly prohibited. If you have received this email in error, please immediately notify the sender via telephone or return mail.

Simon Doran

unread,
Dec 8, 2013, 7:27:34 PM12/8/13
to xnat_di...@googlegroups.com
Hi Guys,

  I'm not sure whether this will be what you are looking for, Kate, but it's sort of similar and I thought it might be worth sharing what I do for importing data, just in case it *is* exactly what someone else is looking for.

  This is the configuration for the DICOM receiver for our "clinical" version of XNAT. I allow two modes:

1. All studies sent from one of the clinical scanners go to the AETITLE XXXX_RPACS01 (not the real value!) which is assigned to us by the hospital and conforms to their DICOM node nomenclature. These data are processed such that XNAT will place them into a fixed "CLINICAL" project, but with subject name taken from the incoming DICOM and session name automatically generated from two fields in the incoming DICOM file.

2. All studies sent from other sources are aimed at a different AETITLE, XNAT_XXXX and these are processed according to the standard XNAT DICOM import rules, specified here: https://wiki.xnat.org/display/XNAT16/Basic+DICOM+object+identification

  The configuration principles allowing the flexibility of doing all this are well explained by Kevin here: https://wiki.xnat.org/display/XNAT16/DICOM+identifier+configuration+and+customization

but I thought it might be worth posting a working piece of code that allows dual functionality, together with working versions of extensions to ClassicDicomObjectIdentifier for the session naming.

  I don't promise that this is the tidiest XML or Java, but it seems to do what I expected.

  Hope it's useful.

Simon


dicom-import-context.xml

<?xml version="1.0" encoding="UTF-8"?>

        <bean name="dicomObjectIdentifier" class="org.nrg.dcm.id.ClassicDicomObjectIdentifier">
                <property name="userProvider" ref="receivedFileUserProvider" />
        </bean>

        <bean name="dicomSCPExecutor" class="java.util.concurrent.Executors"
                factory-method="newCachedThreadPool" />

        <bean name="rpacsProjectIdent" class="org.nrg.dcm.id.FixedDicomProjectIdentifier">
                <constructor-arg value="XXXX_RPACS01"/>
        </bean>

        <bean name="baseSubjectIdent" class="org.nrg.dcm.id.ICRDicomObjectIdentifier" factory-method="getSubjectExtractors"/>
        <bean name="baseSessionIdent" class="org.nrg.dcm.id.ICRDicomObjectIdentifier" factory-method="getSessionExtractors"/>
        <bean name="baseAAIdent" class="org.nrg.dcm.id.ICRDicomObjectIdentifier" factory-method="getAAExtractors"/>

        <bean name="rpacsObjectIdent" class="org.nrg.dcm.id.CompositeDicomObjectIdentifier">
                <constructor-arg ref="rpacsProjectIdent"/>
                <constructor-arg ref="baseSubjectIdent"/>
                <constructor-arg ref="baseSessionIdent"/>
                <constructor-arg ref="baseAAIdent"/>
                <property name="userProvider" ref="receivedFileUserProvider"/>
        </bean>

        <util:map id="cstores">
                <entry key="XXXX_RPACS01" value-ref="rpacsObjectIdent"/>
                <entry key="XNAT_XXXX" value-ref="dicomObjectIdentifier"/>
        </util:map>

        <bean name="dicomSCP" class="org.nrg.dcm.DicomSCP" factory-method="create">
                <constructor-arg ref="dicomSCPExecutor" />
                <constructor-arg value="8104" />
                <constructor-arg ref="receivedFileUserProvider" />
                <constructor-arg ref="cstores" />
        </bean>

        <bean name="dicomSCPManager" class="org.nrg.dcm.DicomSCPManager">
                <constructor-arg ref="dicomSCP" />
        </bean>
</beans>


New class, located in /home/xnat/Tomcat/Home/webapps/XNAT_XXXX/resources/java/org/nrg/dcm/id in the final build:


import java.util.List;
import java.util.regex.Pattern;

import org.dcm4che2.data.Tag;
import org.nrg.dcm.ContainedAssignmentExtractor;
import org.nrg.dcm.Extractor;
import org.nrg.dcm.TextExtractor;
import org.nrg.dcm.TwoFieldExtractor;
import org.nrg.dcm.TwoFieldExtractorTruncate;

import com.google.common.collect.ImmutableList;

/**
 * DicomObjectIdentifier that implements the a custom object identification for the ICR Research PACS
 * @author Kevin A. Archie <kar...@wustl.edu>
 * Modified by Simon Doran
 *
 */
public class ICRDicomObjectIdentifier extends CompositeDicomObjectIdentifier {
    private static final ImmutableList<Extractor> aaExtractors, sessionExtractors, subjectExtractors;
    static {
        final ImmutableList.Builder<Extractor> aab = new ImmutableList.Builder<Extractor>();
        aab.add(new ContainedAssignmentExtractor(Tag.PatientComments, "AA", Pattern.CASE_INSENSITIVE),
                new ContainedAssignmentExtractor(Tag.StudyComments, "AA", Pattern.CASE_INSENSITIVE));
        aaExtractors = aab.build();

        final ImmutableList.Builder<Extractor> sessb = new ImmutableList.Builder<Extractor>();
        sessb.add(new TwoFieldExtractorTruncate(Tag.StudyDate, Tag.StudyTime),
                new ContainedAssignmentExtractor(Tag.PatientComments, "Session", Pattern.CASE_INSENSITIVE),
                new ContainedAssignmentExtractor(Tag.StudyComments, "Session", Pattern.CASE_INSENSITIVE));
        sessionExtractors = sessb.build();

        final ImmutableList.Builder<Extractor> subjb = new ImmutableList.Builder<Extractor>();
        subjb.add(new TwoFieldExtractor(Tag.PatientName, Tag.PatientBirthDate),
                new TextExtractor(Tag.PatientID),
                new ContainedAssignmentExtractor(Tag.PatientComments, "Subject", Pattern.CASE_INSENSITIVE),
                new ContainedAssignmentExtractor(Tag.StudyComments, "Subject", Pattern.CASE_INSENSITIVE));
        subjectExtractors = subjb.build();
    }

    public static final List<Extractor> getAAExtractors() { return aaExtractors; }
    public static final List<Extractor> getSessionExtractors() { return sessionExtractors; }
    public static final List<Extractor> getSubjectExtractors() { return subjectExtractors; }

    public ICRDicomObjectIdentifier() {
        super(new Xnat15DicomProjectIdentifier(), subjectExtractors, sessionExtractors, aaExtractors);
    }
}



New class TwoFieldExtractor.java sitting in /home/xnat/Tomcat/Home/webapps/XNAT_XXXX/resources/java/org/nrg/dcm

package org.nrg.dcm;

import java.util.SortedSet;
import java.util.TreeSet;

import org.dcm4che2.data.DicomObject;
import org.nrg.util.SortedSets;

public class TwoFieldExtractor implements Extractor
{
   private final int tag1;
   private final int tag2;

   public TwoFieldExtractor(final int tag1, final int tag2)
   {
      this.tag1 = tag1;
      this.tag2 = tag2;
   }

 /*
  * (non-Javadoc)
  * @see org.nrg.dcm.Extractor#extract(org.dcm4che2.data.DicomObject)
  */
   public String extract(final DicomObject o)
   {
      StringBuilder sb = new StringBuilder(o.getString(tag1));
      sb.append("_");
      sb.append(o.getString(tag2));
      return sb.toString();
   }

/*
 * (non-Javadoc)
 * @see org.nrg.dcm.Extractor#getTags()
 */
   public SortedSet<Integer> getTags()
   {
      SortedSet<Integer> ss = new TreeSet<Integer>();
      ss.add(tag1);
      ss.add(tag2);
      return ss;
   }
}



     

Archie, Kevin

unread,
Dec 9, 2013, 11:53:13 AM12/9/13
to xnat_di...@googlegroups.com
Simon,

Thanks for sending the documentation links that I should have included in my response to Kate. I haven't dug into the details of your files but I suspect they'd be a really useful example. Would you mind if I built an example/tutorial on our wiki from those files (maybe modified slightly for generality or clarity if that makes sense)?

Thanks,
Kevin


From: xnat_di...@googlegroups.com [xnat_di...@googlegroups.com] on behalf of Simon Doran [simon...@icr.ac.uk]
Sent: Sunday, December 08, 2013 6:27 PM
To: xnat_di...@googlegroups.com
Subject: Re: [XNAT Discussion] changing session label during archive

--
You received this message because you are subscribed to the Google Groups "xnat_discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to xnat_discussi...@googlegroups.com.
To post to this group, send email to xnat_di...@googlegroups.com.
Visit this group at http://groups.google.com/group/xnat_discussion.
For more options, visit https://groups.google.com/groups/opt_out.

David Gutman

unread,
Dec 9, 2013, 12:03:11 PM12/9/13
to xnat_di...@googlegroups.com
So I am in the process of posting my version as well--

It should show up here for documentation:


Jake Cobb and I wrote something that will modify the postgres database, and also update the XML files so that if it finds SESSION_ID's that are duplicated, it will replace the SESSION_ID with PATIENT_ID-DATE

For example:
DG-PT1 becomes DG-PT1_2013-08-09_08-37-43   (I initially only included day/month/year but cocasionally a patient had 2 scan sessions.. could be modified if desired)


After installing the dependencies, it's just  ./sessfix.py <projectname> 
It will go through the project specified and disambiguate any sessions...








the program you'd want is sessfix.py
This currently is easiest when run on the same machine as the XNAT instance, as it talks to the postgres database...

--
David A Gutman, M.D. Ph.D.
Assistant Professor of Biomedical Informatics
Senior Research Scientist, Center for Comprehensive Informatics
Emory University School of Medicine

Simon Doran

unread,
Dec 9, 2013, 1:29:55 PM12/9/13
to xnat_di...@googlegroups.com
Kevin,

  No problem at all. Go ahead and use as you see fit!

Simon
Reply all
Reply to author
Forward
0 new messages