Questions / Discussions related to the dmcrt pre-release

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

Moderator: Moderator Team

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

Questions / Discussions related to the dmcrt pre-release

#1 Post by Oliver Nix » Thu, 2010-02-04, 17:35

This thread is intended to discuss questions arising from using the dcmrt pre-release version.

Note: This module is currently not publicly available. First public versions are expected for around April 2010.


Q1: How to deal with abnormal states?
The new logging mechanism has the following information states trace, debug, info, warn, error, fatal.
How shall fatal, error and warn be defined. What would be examples for error and fatal states?
Shall we associate certain actions to them?
Always exit or abort in case of fatal error?
Always return OFCondition false in case of error?


Q2: RT image and RT dose objects.
RT Image Tag (0028,0103) Pixel Representation is a type 1 tag and shall have the value Ox000F (PS3.3 - 2008, P503ff)
RT Dose Tag (0028,0103) Pixel Representation is either 0x0000H or 0x0001H.
In our development RT objects this tag seems to be 0 all the time.
Am i correct that this is in violation of the standard?
I am implementing some convenience functions to retrieve the image data from RT dose and image objects. Would it be safe to expect them to be always of type 16 bit unsigned integer ?

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, 2010-02-04, 21:16

Regarding Q1: Fatal errors should never occur within the library, also aborting from a library function is usually no good idea (unless there is a very good reason for it). Fatal errors are mainly intended for applications like the well-known DCMTK command line tools. See current snapshot for examples ...

From the API documentation of module "oflog":

Code: Select all

  TRACE_LOG_LEVEL   trace: output more details on the internal application state, a kind of "verbose debug" 
  DEBUG_LOG_LEVEL   debug: fine-grained informational events that are most useful to debug an application 
  INFO_LOG_LEVEL    info: informational messages that highlight the progress of the application at coarse-grained level 
  WARN_LOG_LEVEL    warn: potentially harmful situations 
  ERROR_LOG_LEVEL   error: events that might still allow the application to continue running 
  FATAL_LOG_LEVEL   fatal: very severe error events that will presumably lead the application to abort

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

#3 Post by Oliver Nix » Fri, 2010-02-05, 09:24

Fine, then let's agree on the following:

No fatal error messages from dcmrt. No calls to exit or abort from the library. The decision to abort is left to the application that uses dcmrt features.

Error messages in case of errors that will not allow an action to complete in a way that the user expects. In case of errors either an OFCondition bad or a OFBool OFFalse is returned.

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, 2010-02-05, 09:28

I agree :-)

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

#5 Post by Jörg Riesmeier » Fri, 2010-02-05, 09:39

Regarding Q2:
RT Image Tag (0028,0103) Pixel Representation is a type 1 tag and shall have the value Ox000F (PS3.3 - 2008, P503ff)
No, according to section C.8.8.2.6.6 (PS 3.3-2009) the value shall always be 0x0000 (i.e. unsigned integer).
In our development RT objects this tag seems to be 0 all the time.
Am i correct that this is in violation of the standard?
No, this only means that your data is always unsigned.
I am implementing some convenience functions to retrieve the image data from RT dose and image objects. Would it be safe to expect them to be always of type 16 bit unsigned integer ?
No, because for RT Dose objects the data can also be signed. And Bits Allocated can either be 8 or 16, and Bits Stored either 8 or 12-16 for RT Image objects. For RT Dose objects, Bits Allocated can even be 32.
Btw, for RT Image objects it might be appropriate to use the functionality provided by the dcmimgle module.

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

#6 Post by Oliver Nix » Wed, 2010-02-10, 19:52

As we dicussed before the mid-level API will consist of a set of C++ classes that will provide some additional functions that will allow to
access data from DICOM RT objects more easily.

Each mid-level API class is derived from a RT IOD classs like RT image.
class DRTImage : public DRTImageIOD, public DRTObject
{

public:

//Constructor
DRTImage();
DRTImage(DcmDataset*);

*snip*

};
How to deal with sequence objects? First i thought it would be a good idea to provide some functions to access objects from sequences or more importantly nested sequences objects.

Some very simple inline functions like those

Code: Select all

inline OFBool exposureSequenceExists() {return(getExposureSequence().isEmpty());}

inline Uint16 getNumberOfItemsInExposureSeqence(){return(getExposureSequence().getNumberOfItems());} 

DRTExposureSequenceInRTImageIOD::Item& getItemFromExposureSequence(Uint16 n){return(getExposureSequence().getItem(n));}

On the other hand these functions have to be written for every of the many sequences and they simplify the code users of the mid-level API classes will write only a bit.
Do you think it is worth implementing such functions or not. :?:
Last edited by Oliver Nix on Thu, 2010-02-11, 09:47, edited 1 time in total.

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

#7 Post by Oliver Nix » Wed, 2010-02-10, 19:55

Jörg Riesmeier wrote: Btw, for RT Image objects it might be appropriate to use the functionality provided by the dcmimgle module.
I will look into dcmimgle. Anything special in mind? I am not familiar with this module.

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

#8 Post by Jörg Riesmeier » Thu, 2010-02-11, 08:25

For example, getOutputData() gives you a bitmap with the rendered output of the DICOM image, i.e. including all the transformations of the grayscale pipeline. The command line tool dcm2pnm shows what is possible with this module ...

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

#9 Post by Jörg Riesmeier » Thu, 2010-02-11, 09:59

Do you think it is worth implementing such functions or not.
I don't think that it makes sense to replicate the functionality of the low-level API. So, instead of your proposed "simple inline functions" I would rather go one level of abstraction up and define something like getNumberOfExposures() - but of course only for very important elements that are accessed quite often.

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

#10 Post by Oliver Nix » Thu, 2010-02-11, 10:07

You are right. DicomImage is powerful and better suited than directly extracting pixel data from the image object and storing it using C-Style arrays as part of DRTImage.
Providing image pixel data access by the the DRTImage class will also become obsolete since PixelData access / retrieval is handled by DicomImage.
A pointer to a DicomImage will be added to DRTImage. The DicomImage object can be constructed either by using a special DRTImage constructor or a member function getDicomImage( ... ).

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

#11 Post by Oliver Nix » Thu, 2010-02-11, 10:20

Jörg Riesmeier wrote:
I would rather go one level of abstraction up and define something like getNumberOfExposures() - but of course only for very important elements that are accessed quite often.
I agree. The problem is to identify sequence elements accessed frequently. Since most of the sequences are type 3 it is not even clear which planning system writes which sequences and which not. I have only hands on data originating from one commercial planning system.
I will post a suggestion for RTImage and RTDose objects on that sometimes this week.

Shall we consider providing a small QT based demo programme (executable) ? Since QT is now LGPL QT would be a good choice.

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

#12 Post by Jörg Riesmeier » Thu, 2010-02-11, 10:47

Shall we consider providing a small QT based demo programme (executable) ? Since QT is now LGPL QT would be a good choice.
I like Qt pretty much, so go ahead! :-)

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

#13 Post by Oliver Nix » Fri, 2010-02-12, 16:49

Jörg Riesmeier wrote: I would rather go one level of abstraction up and define something like getNumberOfExposures() - but of course only for very important elements that are accessed quite often.
I think the following approach makes sense:

Define access functions to sequence objects like this (example from RTDose):

Code: Select all

OFList<DRTStructureSetROISequence::Item>  getDRTStructureSetROISequenceElements();
 DRTStructureSetROISequence::Item getDRTStructureSetROISequenceElement(Uint16);
Implementation:

Code: Select all

OFList<DRTROIContourSequence::Item> DRTDose::getDRTROIContourSequenceElements()
{

  OFList<DRTROIContourSequence::Item> roi_contour_list;
  DRTROIContourSequence &seq = this->getROIContourSequence();
  if(!seq.isEmpty())
    {
    if (seq.gotoFirstItem().good())
      {
      do
       {          
        DRTROIContourSequence::Item &item = seq.getCurrentItem();
        roi_contour_list.push_back(item);
            
       }  while (seq.gotoNextItem().good());
      }
     }
 return roi_contour_list;

}

DRTROIContourSequence::Item DRTDose::getDRTROIContourSequenceElement(Uint16 element)
{
OFList<DRTROIContourSequence::Item> list = this->getDRTROIContourSequenceElements();

 size_t size = list.size();
 OFListIterator(DRTROIContourSequence::Item) start = list.begin();
 OFListIterator(DRTROIContourSequence::Item) stop = list.end();
 Uint16 ctr=0;
 DRTROIContourSequence::Item current = *start; 
 while (start != stop)
   {
    if(element == ctr)
       break;
    ++start;
    ++ctr;
   }
 return current;
}

These functions will access the StructureSetROISequence objects. The first one returns a list of all objects of that kind found. The second returns one item from the sequence (selected by the position in the sequence).
So far this is straight forward.

For sequences containing sequences i propose the following:
One case is the RoiContourSequence where each item can contain another ContourSequence.

Code: Select all

(3006,0039) SQ (Sequence with undefined length #=1)     # u/l, 1 ROIContourSequence
  (fffe,e000) na (Item with undefined length #=3)         # u/l, 1 Item
    (3006,002a) IS [0\0\255]                                #   8, 3 ROIDisplayColor
    (3006,0040) SQ (Sequence with undefined length #=1)     # u/l, 1 ContourSequence
      (fffe,e000) na (Item with undefined length #=3)         # u/l, 1 Item
        (3006,0042) CS [POINT]                                  #   6, 1 ContourGeometricType
        (3006,0046) IS [1]                                      #   2, 1 NumberOfContourPoints
        (3006,0050) DS [7.10\-174.70\-841.50]                   #  20, 3 ContourData
      (fffe,e00d) na (ItemDelimitationItem)                   #   0, 0 ItemDelimitationItem
    (fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem
    (3006,0084) IS [1]                                      #   2, 1 ReferencedROINumber
  (fffe,e00d) na (ItemDelimitationItem)                   #   0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem


Here the following would handle the access

Code: Select all

OFList<DRTContourSequence::Item> DRTDose::getDRTContourSequenceElements(Uint16);
 DRTContourSequence::Item DRTDose::getDRTContourSequenceElement(Uint16, Uint16);
Implementation:

Code: Select all

OFList<DRTContourSequence::Item> DRTDose::getDRTContourSequenceElements(Uint16 RoiCSEelement)
{

DRTROIContourSequence::Item ROIContSequenceElement = getDRTROIContourSequenceElement(RoiCSEelement);
OFList<DRTContourSequence::Item> contour_list;
DRTContourSequence &seq = ROIContSequenceElement.getContourSequence();
  if(!seq.isEmpty())
    {
    if (seq.gotoFirstItem().good())
      {
      do
       {          
        DRTContourSequence::Item &item = seq.getCurrentItem();
        contour_list.push_back(item);
            
       }  while (seq.gotoNextItem().good());
      }
     }
 return contour_list;
}

DRTContourSequence::Item DRTDose::getDRTContourSequenceElement(Uint16 RoiCSEelement, Uint16 CSelement)
{

 OFList<DRTContourSequence::Item> list = getDRTContourSequenceElements(RoiCSEelement);
 size_t size = list.size();
 OFListIterator(DRTContourSequence::Item) start = list.begin();
 OFListIterator(DRTContourSequence::Item) stop = list.end();
 Uint16 ctr=0;
 DRTContourSequence::Item current = *start; 
 while (start != stop)
   {
    if(CSelement == ctr)
       break;
    ++start;
    ++ctr;
   }
 return current; 

}


This will create flat access points to sequence items of any kind. Of course the user still has to know the logical connections between the objects in nested sequences.

For tripple nested objects the logic would be

Code: Select all

OFList<AnySequence::Item> DRTDose::getDRTAnySequenceElements(Uint16, Uint16);
 AnySequence::Item DRTDose::getDRTAnySequenceElement(Uint16, Uint16, Uint16);

Since these access functions are structurally always very similar i thought about using templated functions. But i am not sure if this is a clever idea or even possible.

What is your opinion.
Do you think the mid-API sequence handling of RT IODs is well addressed?

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

#14 Post by Jörg Riesmeier » Fri, 2010-02-12, 17:17

I thought it would be better to go even further for the medium-level API: The sequence and item stuff is already handled by the low-level API. This shouldn't be replicated for the medium-level API!

When I wrote about having something like getNumberOfExposures() I was thinking that this method simply returns the number of exposure and the user does not need to know how this information is determined. In other words, the user of the medium-level API does not need to know about sequences and items or, more generally, about the internal structure of the DICOM information object.

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

#15 Post by Oliver Nix » Fri, 2010-02-12, 19:51

In other words, the user of the medium-level API does not need to know about sequences and items or, more generally, about the internal structure of the DICOM information object.
In general i agree on that. The full information content is obviously located somehere in the DICOM object. Either as simple variables or as something derived from the objects structure,eg the number of objects in a sequence. The purpose of the mid level API is imho to hide the complexity of the underlying DICOM object from the person dealing with it. The low level DICOM objects (API) is driven by the internal structure of the object. Especially in case of nested sequences you have to have a quite thorough look in the DICOM standard to see how to brachiate to the information you need. Since it is such that the low level structures can anyhow be used and the mid level API classes can be ignored at all by the expert user, the simplified access point are something which can be used but must not be used at all. Programmers well familiar with dcmtk and DICOM will probably use the IODs directly because they are used to this model. The mid level API in my opinion serves two purposes. One ist to provide simplified access to data content regardless of where it is located. In other words to simplify the appearance of the object to the programmer. This was what i had in mind with the sequence item access functions. The nameing tells you what you can expect to get, no matter where it is located in the internal data structures. I agree, it is a replication, but also a simplification. The second purpose is a logical grouping of information and a processing of information to calculate derived information frequently used. An expample for that would be the scaling of dose pixel values into units of gray. This second purpose was not addressed so far. Presently the attention is put on simplifying data access. For example by providing a constructor for any of the IODs taking a filename or a dcmdataset and to extract the structures and image values into a data structure easily transferable to other tools and libraries for further processing, like visualization.
Let's discuss the processing part of the API in the weeks to come.

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest