How to access data elements in sequence item for RT IODs

Questions regarding the DCMRT library, a DCMTK add-on that implements support for the various DICOM Radiation Therapy (RT) IODs

Moderator: Moderator Team

Post Reply
Message
Author
Oliver Nix
Posts: 18
Joined: Tue, 2009-09-22, 12:57
Location: DKFZ Heidelberg

How to access data elements in sequence item for RT IODs

#1 Post by Oliver Nix » Thu, 2009-10-01, 14:53

I like to access elements from nested sequences for example from a DICOM RT structure set file.

Let's say the structure of the file is as follows (dcmdump output)

Code: Select all

3006,0010) SQ (Sequence with undefined length #=2)     # u/l, 1 ReferencedFrameOfReferenceSequence
  (fffe,e000) na (Item with undefined length #=3)         # u/l, 1 Item
    (0020,0052) UI [1.3.12.2.1107.5.1.4.28334.4.0.4556475228861625] #  46, 1 FrameOfReferenceUID
    (3006,0012) SQ (Sequence with undefined length #=1)     # u/l, 1 RTReferencedStudySequence
      (fffe,e000) na (Item with undefined length #=3)         # u/l, 1 Item
        (0008,1150) UI =StudyComponentManagementSOPClass        #  24, 1 ReferencedSOPClassUID
        (0008,1155) UI [1.2.840.113619.6.95.31.0.3.4.1.6013.13.2863364] #  46, 1 ReferencedSOPInstanceUID
        (3006,0014) SQ (Sequence with undefined length #=1)     # u/l, 1 RTReferencedSeriesSequence
          (fffe,e000) na (Item with undefined length #=2)         # u/l, 1 Item
            (0020,000e) UI [1.3.12.2.1107.5.1.4.28334.4.0.4556490411543837] #  46, 1 SeriesInstanceUID
            (3006,0016) SQ (Sequence with undefined length #=62)    # u/l, 1 ContourImageSequence
              (fffe,e000) na (Item with undefined length #=2)         # u/l, 1 Item
                (0008,1150) UI =CTImageStorage                          #  26, 1 ReferencedSOPClassUID
                (0008,1155) UI [1.3.6.1.4.1.2452.6.3787560449.1085974281.3785418625.1983991160] #  62, 1 ReferencedSOPInstanceUID
              (fffe,e00d) na (ItemDelimitationItem)                   #   0, 0 ItemDelimitationItem
.........
        (fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem
          (fffe,e00d) na (ItemDelimitationItem)                   #   0, 0 ItemDelimitationItem
        (fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem
      (fffe,e00d) na (ItemDelimitationItem)                   #   0, 0 ItemDelimitationItem
    (fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem
    (3006,00c0) SQ (Sequence with undefined length #=14)    # u/l, 1 FrameOfReferenceRelationshipSequence
The dcmtk generic way to retrieve this data would be

Code: Select all

     DcmSequenceOfItems *image_sequence = NULL;
     OFCondition cond;
     cond = dset->findAndGetSequence(DCM_ContourImageSequence, image_sequence, true); 
     if (cond.good())
      {
       
       Uint16 numimages = image_sequence->card();       
       for(int i=0;i<numimages;i++)
         {
          DcmItem *image_item = image_sequence->getItem(i);
          OFString SOPclassUID,SOPinstanceUID;
          OFString &SOPclassUID_r =SOPclassUID;
          OFString &SOPinstanceUID_r = SOPinstanceUID;
          image_item->findAndGetOFString(DCM_ReferencedSOPInstanceUID,SOPinstanceUID_r);
          image_item->findAndGetOFString(DCM_ReferencedSOPClassUID,SOPclassUID_r);
         } // end numimages
   
     } // end cond.good
I would now like to access the same data using the dcmrt DRTStructureSetIOD.
The following code seems to me the "logical" way.

Code: Select all

 DRTReferencedFrameOfReferenceSequence &seq = this->getReferencedFrameOfReferenceSequence();
 Uint16 num_items = seq.getNumberOfItems(); 
 for(int i = 0; i <= num_items; i++)
  {
   DRTReferencedFrameOfReferenceSequence::Item &item = seq.getItem(i);
   OFString uid;
   item.getFrameOfReferenceUID(uid);
   DRTRTReferencedStudySequence &rss_ref = item.getRTReferencedStudySequence();   
   for (int j = 0; j <= rss_ref.getNumberOfItems(); j++)
    {
     DRTRTReferencedStudySequence::Item &rss_item = rss_ref.getItem(j);
     DRTRTReferencedSeriesSequence &ref_series_seq = rss_item.getRTReferencedSeriesSequence();
     Uint16 nssitem = ref_series_seq.getNumberOfItems();
     for (int k = 0; k <= nssitem; k++)
      {
       DRTRTReferencedSeriesSequence::Item &ref_series_seq_item = ref_series_seq.getItem(k);
       DRTContourImageSequence &image_sequence = ref_series_seq_item.getContourImageSequence();   
       Uint16 num_images_in_sequence = image_sequence.getNumberOfItems();
       for (int l = 0; l <= num_images_in_sequence; l++)
       {
        DRTContourImageSequence::Item &image_contour_item = image_sequence.getItem(l);
        OFString refframenum;
        image_contour_item.getReferencedFrameNumber(refframenum);
        std::cout << refframenum << std::endl;
       }
     }
    }
  }
This code compiles but the returned items are always empty (EmptyDefaultItem = true) All variables remain empty. Number od items in the list is 0, instead of 1 or many in case of the ContourImageSequence.
Why is that? Is the logic of accessing the nested sequences wrong?
For the mid level API i would rather like to use the second style of data access because it reflects the data organisation and hierachy.

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

#2 Post by Jörg Riesmeier » Thu, 2009-10-01, 16:20

The good thing: I checked your code and it works in principle :-)

First of all, you should use "<" instead of "<=" in the for-loops because the item index starts with 0 and ends with number of items minus 1.
Then, are you sure that your sample file really contains a value for ReferencedFrameNumber for the individual items? In my sample file, this data element was missing and, therefore, I used ReferencedSOPClassUID for testing purposes.

Btw, in order to iterate over all items of a sequence you should probably better use the methods gotoFirstItem() and gotoNextItem() of the particular sequence class in a do-while-loop. It's probably a little more efficient and elegant :-) Here's an example:

Code: Select all

    if (seq.gotoFirstItem().good())
    {
        do
        { 
            /* e.g. use seq.getCurrentItem() ... */
        } while (seq.gotoNextItem().good());
    }

Oliver Nix
Posts: 18
Joined: Tue, 2009-09-22, 12:57
Location: DKFZ Heidelberg

#3 Post by Oliver Nix » Fri, 2009-10-02, 11:49

Using the iterator is a good idea.

The code looks now like this

Code: Select all


 if (seq.gotoFirstItem().good())
  {
   do
    {
     DRTReferencedFrameOfReferenceSequence::Item &item = seq.getCurrentItem();
     OFString uid;
     item.getFrameOfReferenceUID(uid);
     DRTRTReferencedStudySequence &reference_study_sequence_ref = item.getRTReferencedStudySequence();   
     do
      {
       DRTRTReferencedStudySequence::Item &rss_item = reference_study_sequence_ref.getCurrentItem();
       DRTRTReferencedSeriesSequence &series_seq_ref = rss_item.getRTReferencedSeriesSequence();
       do
        {
         DRTRTReferencedSeriesSequence::Item &ref_series_seq_item = series_seq_ref.getCurrentItem();
         DRTContourImageSequence &image_sequence_seq_ref = ref_series_seq_item.getContourImageSequence();   
         do
         {
           DRTContourImageSequence::Item &image_contour_item = image_sequence_seq_ref.getCurrentItem();
           OFString refframenum;
           image_contour_item.getReferencedFrameNumber(refframenum);
           std::cout << refframenum << std::endl;
         } while (image_sequence_seq_ref.gotoNextItem().good());        
        } while (series_seq_ref.gotoNextItem().good());
      } while (reference_study_sequence_ref.gotoNextItem().good());
    } while (seq.gotoNextItem().good());
   } // end if seq.first item
Nevertheless i cannot access any valid item from the nested sequences. Still read try to read the structure set file posted above.
It is this one, in case you like to try
" ...\\Phantom\\RS1.3.6.1.4.1.2452.6.2951369053.1166013784.715527822.1679960.dcm"

Code: Select all

DRTReferencedFrameOfReferenceSequence &seq = this->getReferencedFrameOfReferenceSequence();
Fine, i get a list with one valid item, as expected.

Code: Select all

DRTRTReferencedStudySequence &reference_study_sequence_ref = item.getRTReferencedStudySequence();   

Again, fine. A list with one valid item.

Code: Select all

  DRTRTReferencedSeriesSequence &series_seq_ref = rss_item.getRTReferencedSeriesSequence();
The list is empty, but should contain again 1 element
Same of course true for the lists from the two other do loops.

I investigated the problem a bit further and i think it is related to the read method of the DRTStructureSetIOD.

I do the following:

Code: Select all

   DRTStructureSet *structure_set = new DRTStructureSet(d);
   DcmItem &ditem = dynamic_cast<DcmItem&>(*dset);
   structure_set->read(ditem);
DRTStructureSet is a mid level API class. It is derived from DRTStructureSetIOD.

In

Code: Select all

OFCondition DRTStructureSetIOD::read(DcmItem &dataset)
{
.....

ReferencedFrameOfReferenceSequence.read(dataset, "1-n", "3", LogStream, "StructureSetModule");

}
is called.

Code: Select all

OFCondition DRTReferencedFrameOfReferenceSequence::read(DcmItem &dataset,
                                                        const OFString &card,
                                                        const OFString &type,
                                                        OFConsole *stream,
                                                        const char *moduleName)
{
....

  if (checkElementValue(*sequence, card, type, stream, result, moduleName))
            {
      
.....
                while (result.good() && sequence->nextObject(stack, first /*intoSub*/).good())
                {
                    DcmItem *ditem = OFstatic_cast(DcmItem *, stack.top());

.....
                        SequenceOfItems.push_back(item);
.....
               }          
            }
....
}
If i understand the code correctly all items in the sequence are read and appended to the sequence list. If the sequence itself contains another sequence this other sequence is not considered, since no read for the nested sequence objects is called. Dcmrt classes representing objects from the nested sequences exists and read methods are implemented, but never called from "outer" sequence items read methods.

It would be possible to read the missing sequences elements from the mid-level API class. Nevertheless i think it would be better to extend the read method in the e.g. DRTReferencedFrameOfReferenceSequence class, because otherwise the IOD has a potentially incomplete data content and might cause problems for those using the low level RT information objects.

Have i understood the situation correctly? How to deal with it?

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

#4 Post by Jörg Riesmeier » Fri, 2009-10-02, 14:00

Hmm, the read() method of the item is called:

Code: Select all

OFCondition DRTReferencedFrameOfReferenceSequence::read(DcmItem &dataset,
                                                        const OFString &card,
                                                        const OFString &type,
                                                        const char *moduleName)
{
    /* ... */
                            result = item->read(*ditem);
                            if (result.good())
                            {
                                /* append new item to the end of the list */
                                SequenceOfItems.push_back(item);
                                first = OFFalse;
                            }
    /* ... */
}
Btw, you need to call gotoFirstItem() for the other sequences, too. And, there probably is no ReferencedFrameNumber in your test dataset :-)

Oliver Nix
Posts: 18
Joined: Tue, 2009-09-22, 12:57
Location: DKFZ Heidelberg

#5 Post by Oliver Nix » Fri, 2009-10-02, 15:33

Hello Jörg, further debuging reveals that indeed all read methods are called properly.
The problem was located in my code. gotoFirstItem() has to be called before entering each of the do loops and subsequent calls to getCurrentItem. It seems that the iterator was not set to a valid item or not initialized properly when getCurrentItem was called.
This was a classic from bug world :roll:

This code behaves as expected

Code: Select all

 if (seq.gotoFirstItem().good())
  {
   do
    {
     DRTReferencedFrameOfReferenceSequence::Item &item = seq.getCurrentItem();
     OFString uid;
     item.getFrameOfReferenceUID(uid);
     DRTRTReferencedStudySequence &reference_study_sequence_ref = item.getRTReferencedStudySequence();   
     if (reference_study_sequence_ref.gotoFirstItem().good())
      {
       do
       {
        DRTRTReferencedStudySequence::Item &rss_item = reference_study_sequence_ref.getCurrentItem();
        DRTRTReferencedSeriesSequence &series_seq_ref = rss_item.getRTReferencedSeriesSequence();
        if (series_seq_ref.gotoFirstItem().good())
        {
         do
         {
          DRTRTReferencedSeriesSequence::Item &ref_series_seq_item = series_seq_ref.getCurrentItem();
          DRTContourImageSequence &image_sequence_seq_ref = ref_series_seq_item.getContourImageSequence();   
          if (image_sequence_seq_ref.gotoFirstItem().good())
          {
           do
           {
             DRTContourImageSequence::Item &image_contour_item = image_sequence_seq_ref.getCurrentItem();
             OFString refSOPInstUID;
             image_contour_item.getReferencedSOPInstanceUID(refSOPInstUID);
             std::cout << refSOPInstUID << std::endl;
           } while (image_sequence_seq_ref.gotoNextItem().good());
          } // end if image_sequence_seq_ref
         } while (series_seq_ref.gotoNextItem().good());
        } // end if series_seq_ref good
       } while (reference_study_sequence_ref.gotoNextItem().good());
     } // end if reference_study_sequence_ref good 
    } while (seq.gotoNextItem().good());
  } // end if seq.first item

[/code]

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

#6 Post by Jörg Riesmeier » Fri, 2009-10-02, 16:28

Great :-)

Btw, why are you using the following type cast?

Code: Select all

DcmItem &ditem = dynamic_cast<DcmItem&>(*dset);
It should be noted that DCMTK does not (and will probably never) use RTTI, Exceptions and the like. Also instead of using "std::cout" and "std::endl" you should better use "COUT" (in applications) or "OFConsole::lockCout()" (in libraries) and "OFendl". This will allow for compiling the toolkit on more platforms ...

You might also be interested in the fact that we are currently completely reworking the error reporting (i.e. logging) of the DCMTK. See new oflog module which is already part of the latest version of the DCMRT module package. So the setLogStream() method will disappear with the next pre-release of the DCMRT package. A modified version of this module is already in our CVS repository -- since today :-)

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest