]> Creatis software - gdcm.git/blob - src/gdcmHeader.cxx
* Memory leak hunt with the following command:
[gdcm.git] / src / gdcmHeader.cxx
1 // gdcmHeader.cxx
2 //-----------------------------------------------------------------------------
3 #include "gdcmHeader.h"
4 #include "gdcmGlobal.h"
5 #include "gdcmUtil.h"
6 #include "gdcmDebug.h"
7 #include "gdcmTS.h"
8 #include "gdcmValEntry.h"
9
10 #include <vector>
11
12 //-----------------------------------------------------------------------------
13 // Constructor / Destructor
14 /**
15  * \brief  Constructor 
16  * @param  InFilename name of the file whose header we want to analyze
17  * @param  exception_on_error whether we want to throw an exception or not
18  * @param  enable_sequences = true to allow the header 
19  *         to be parsed *inside* the SeQuences, when they have an actual length 
20  * @param  ignore_shadow = true if user wants to skip shadow groups 
21  *         during parsing, to save memory space
22  */
23 gdcmHeader::gdcmHeader(const char *InFilename, 
24                        bool exception_on_error,
25                        bool enable_sequences, 
26                        bool ignore_shadow):
27    gdcmDocument(InFilename,exception_on_error,enable_sequences,ignore_shadow)
28
29 /*
30    typedef struct {
31       guint32 totalSQlength;
32       guint32 alreadyParsedlength;
33    } pileElem;
34 */
35    
36    // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
37    // We may encounter the 'RETired' (0x0028, 0x0200) tag
38    // (Image Location") . This Element contains the number of
39    // the group that contains the pixel data (hence the "Pixel Data"
40    // is found by indirection through the "Image Location").
41    // Inside the group pointed by "Image Location" the searched element
42    // is conventionally the element 0x0010 (when the norm is respected).
43    // When the "Image Location" is absent we default to group 0x7fe0.
44    
45    // This IS the right place for the code
46  
47    std::string ImageLocation = GetEntryByNumber(0x0028, 0x0200);
48    if ( ImageLocation == GDCM_UNFOUND ) { // Image Location
49       GrPixel = 0x7fe0;                   // default value
50    } else {
51       GrPixel = (guint16) atoi( ImageLocation.c_str() );
52    }   
53    if (GrPixel == 0xe07f) // sometimes Image Location value doesn't follow 
54       GrPixel = 0x7fe0;   // the supposed processor endianity. 
55                           // see gdcmData/cr172241.dcm      
56    if (GrPixel != 0x7fe0) 
57       // This is a kludge for old dirty Philips imager.
58       NumPixel = 0x1010;
59    else
60       NumPixel = 0x0010;
61    
62 }
63
64 /**
65  * \brief Constructor  
66  * @param exception_on_error whether we want to throw an exception or not
67  */
68 gdcmHeader::gdcmHeader(bool exception_on_error) :
69    gdcmDocument(exception_on_error)
70 {
71 }
72
73 /**
74  * \ingroup gdcmHeader
75  * \brief   Canonical destructor.
76  */
77 gdcmHeader::~gdcmHeader (void) {
78 }
79
80 //-----------------------------------------------------------------------------
81 // Print
82
83
84 //-----------------------------------------------------------------------------
85 // Public
86
87 /**
88  * \ingroup gdcmHeader
89  * \brief  This predicate, based on hopefully reasonable heuristics,
90  *         decides whether or not the current gdcmParser was properly parsed
91  *         and contains the mandatory information for being considered as
92  *         a well formed and usable Dicom/Acr File.
93  * @return true when gdcmParser is the one of a reasonable Dicom/Acr file,
94  *         false otherwise. 
95  */
96 bool gdcmHeader::IsReadable(void) {
97    if(!gdcmDocument::IsReadable()) {
98       return(false);
99    }
100    std::string res = GetEntryByNumber(0x0028, 0x0005);
101    if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 ) 
102       return false; // Image Dimensions
103    if ( !GetDocEntryByNumber(0x0028, 0x0100) )
104       return false; // "Bits Allocated"
105    if ( !GetDocEntryByNumber(0x0028, 0x0101) )
106       return false; // "Bits Stored"
107    if ( !GetDocEntryByNumber(0x0028, 0x0102) )
108       return false; // "High Bit"
109    if ( !GetDocEntryByNumber(0x0028, 0x0103) )
110       return false; // "Pixel Representation" i.e. 'Sign'
111    return true;
112 }
113
114 /**
115  * \brief   Retrieve the number of columns of image.
116  * @return  The encountered size when found, 0 by default.
117  *          0 means the file is NOT USABLE. The caller will have to check
118  */
119 int gdcmHeader::GetXSize(void) {
120    std::string StrSize;
121    StrSize = GetEntryByNumber(0x0028,0x0011);
122    if (StrSize == GDCM_UNFOUND)
123       return 0;
124    return atoi(StrSize.c_str());
125 }
126
127 /**
128  * \ingroup gdcmHeader
129  * \brief   Retrieve the number of lines of image.
130  * \warning The defaulted value is 1 as opposed to gdcmHeader::GetXSize()
131  * @return  The encountered size when found, 1 by default 
132  *          (The ACR-MEMA file contains a Signal, not an Image).
133  */
134 int gdcmHeader::GetYSize(void) {
135    std::string StrSize = GetEntryByNumber(0x0028,0x0010);
136    if (StrSize != GDCM_UNFOUND)
137       return atoi(StrSize.c_str());
138    if ( IsDicomV3() )
139       return 0;
140    else
141       // The Rows (0028,0010) entry was optional for ACR/NEMA. It might
142       // hence be a signal (1d image). So we default to 1:
143       return 1;
144 }
145
146 /**
147  * \ingroup gdcmHeader
148  * \brief   Retrieve the number of planes of volume or the number
149  *          of frames of a multiframe.
150  * \warning When present we consider the "Number of Frames" as the third
151  *          dimension. When absent we consider the third dimension as
152  *          being the ACR-NEMA "Planes" tag content.
153  * @return  The encountered size when found, 1 by default (single image).
154  */
155 int gdcmHeader::GetZSize(void) {
156    // Both  DicomV3 and ACR/Nema consider the "Number of Frames"
157    // as the third dimension.
158    std::string StrSize = GetEntryByNumber(0x0028,0x0008);
159    if (StrSize != GDCM_UNFOUND)
160       return atoi(StrSize.c_str());
161
162    // We then consider the "Planes" entry as the third dimension 
163    StrSize = GetEntryByNumber(0x0028,0x0012);
164    if (StrSize != GDCM_UNFOUND)
165       return atoi(StrSize.c_str());
166    return 1;
167 }
168
169 /**
170  * \ingroup gdcmHeader
171  * \brief   Retrieve the number of Bits Stored (actually used)
172  *          (as opposite to number of Bits Allocated)
173  * @return  The encountered number of Bits Stored, 0 by default.
174  *          0 means the file is NOT USABLE. The caller has to check it !
175  */
176 int gdcmHeader::GetBitsStored(void) {  
177    std::string StrSize = GetEntryByNumber(0x0028,0x0101);
178    if (StrSize == GDCM_UNFOUND)
179       return 0;  // It's supposed to be mandatory
180                  // the caller will have to check
181    return atoi(StrSize.c_str());
182 }
183
184 /**
185  * \ingroup gdcmHeader
186  * \brief   Retrieve the number of Bits Allocated
187  *          (8, 12 -compacted ACR-NEMA files, 16, ...)
188  * @return  The encountered number of Bits Allocated, 0 by default.
189  *          0 means the file is NOT USABLE. The caller has to check it !
190  */
191 int gdcmHeader::GetBitsAllocated(void) {
192    std::string StrSize = GetEntryByNumber(0x0028,0x0100);
193    if (StrSize == GDCM_UNFOUND)
194       return 0; // It's supposed to be mandatory
195                 // the caller will have to check
196    return atoi(StrSize.c_str());
197 }
198
199 /**
200  * \ingroup gdcmHeader
201  * \brief   Retrieve the number of Samples Per Pixel
202  *          (1 : gray level, 3 : RGB -1 or 3 Planes-)
203  * @return  The encountered number of Samples Per Pixel, 1 by default.
204  *          (Gray level Pixels)
205  */
206 int gdcmHeader::GetSamplesPerPixel(void) {
207    std::string StrSize = GetEntryByNumber(0x0028,0x0002);
208    if (StrSize == GDCM_UNFOUND)
209       return 1; // Well, it's supposed to be mandatory ...
210                 // but sometimes it's missing : *we* assume Gray pixels
211    return atoi(StrSize.c_str());
212 }
213
214 /**
215  * \ingroup gdcmHeader
216  * \brief   Retrieve the Planar Configuration for RGB images
217  *          (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
218  * @return  The encountered Planar Configuration, 0 by default.
219  */
220 int gdcmHeader::GetPlanarConfiguration(void) {
221    std::string StrSize = GetEntryByNumber(0x0028,0x0006);
222    if (StrSize == GDCM_UNFOUND)
223       return 0;
224    return atoi(StrSize.c_str());
225 }
226
227 /**
228  * \ingroup gdcmHeader
229  * \brief   Return the size (in bytes) of a single pixel of data.
230  * @return  The size in bytes of a single pixel of data; 0 by default
231  *          0 means the file is NOT USABLE; the caller will have to check        
232  */
233 int gdcmHeader::GetPixelSize(void) {
234    std::string PixelType = GetPixelType();
235    if (PixelType == "8U"  || PixelType == "8S")
236       return 1;
237    if (PixelType == "16U" || PixelType == "16S")
238       return 2;
239    if (PixelType == "32U" || PixelType == "32S")
240       return 4;
241    if (PixelType == "FD")
242       return 8;         
243    dbg.Verbose(0, "gdcmHeader::GetPixelSize: Unknown pixel type");
244    return 0;
245 }
246
247 /**
248  * \ingroup gdcmHeader
249  * \brief   Build the Pixel Type of the image.
250  *          Possible values are:
251  *          - 8U  unsigned  8 bit,
252  *          - 8S    signed  8 bit,
253  *          - 16U unsigned 16 bit,
254  *          - 16S   signed 16 bit,
255  *          - 32U unsigned 32 bit,
256  *          - 32S   signed 32 bit,
257  *          - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
258  * \warning 12 bit images appear as 16 bit.
259  *          24 bit images appear as 8 bit
260  * @return  0S if nothing found. NOT USABLE file. The caller has to check
261  */
262 std::string gdcmHeader::GetPixelType(void) { 
263    std::string BitsAlloc = GetEntryByNumber(0x0028, 0x0100); // Bits Allocated
264    if (BitsAlloc == GDCM_UNFOUND) {
265       dbg.Verbose(0, "gdcmHeader::GetPixelType: unfound Bits Allocated");
266       BitsAlloc = std::string("16");
267    }
268    if (BitsAlloc == "64")            // )
269       return ("FD");
270    if (BitsAlloc == "12")            // It will be unpacked
271       BitsAlloc = std::string("16");
272    else if (BitsAlloc == "24")       // (in order no to be messed up
273       BitsAlloc = std::string("8");  // by old RGB images)
274      
275    std::string Signed = GetEntryByNumber(0x0028, 0x0103); // "Pixel Representation"
276    if (Signed == GDCM_UNFOUND) {
277       dbg.Verbose(0, "gdcmHeader::GetPixelType: unfound Pixel Representation");
278       BitsAlloc = std::string("0");
279    }
280    if (Signed == "0")
281       Signed = std::string("U");
282    else
283       Signed = std::string("S");
284
285    return( BitsAlloc + Signed);
286 }
287
288
289 /**
290  * \ingroup gdcmHeader
291  * \brief   Recover the offset (from the beginning of the file) 
292  *          of *image* pixels (not *icone image* pixels, if any !)
293  * @return Pixel Offset
294  */
295 size_t gdcmHeader::GetPixelOffset(void) { 
296       
297    gdcmDocEntry* PixelElement = GetDocEntryByNumber(GrPixel,NumPixel);
298  
299    if (PixelElement) {
300       return PixelElement->GetOffset();
301    } else {
302 #ifdef GDCM_DEBUG
303       std::cout << "Big trouble : Pixel Element ("
304                 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found"
305                 << std::endl;  
306 #endif //GDCM_DEBUG
307       return 0;
308    }     
309 }
310 // TODO : unify those two (previous one and next one)
311 /**
312  * \ingroup gdcmHeader
313  * \brief   Recover the pixel area length (in Bytes)
314  * @return Pixel Element Length, as stored in the header
315  *         (NOT the memory space necessary to hold the Pixels 
316  *          -in case of embeded compressed image-)
317  *         0 : NOT USABLE file. The caller has to check.
318  */
319 size_t gdcmHeader::GetPixelAreaLength(void) { 
320           
321    gdcmDocEntry* PixelElement = GetDocEntryByNumber(GrPixel,NumPixel);
322
323    if (PixelElement) {
324       return PixelElement->GetLength();
325    } else {
326 #ifdef GDCM_DEBUG
327       std::cout << "Big trouble : Pixel Element ("
328                 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found"
329                 << std::endl;
330 #endif //GDCM_DEBUG
331       return 0;
332    }
333 }
334
335 /**
336   * \ingroup gdcmHeader
337   * \brief tells us if LUT are used
338   * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
339   *          are NOT considered as LUT, since nobody knows
340   *          how to deal with them
341   *          Please warn me if you know sbdy that *does* know ... jprx
342   * @return true if LUT Descriptors and LUT Tables were found 
343   */
344 bool gdcmHeader::HasLUT(void) {
345
346    // Check the presence of the LUT Descriptors, and LUT Tables    
347    // LutDescriptorRed    
348    if ( !GetDocEntryByNumber(0x0028,0x1101) )
349       return false;
350    // LutDescriptorGreen 
351    if ( !GetDocEntryByNumber(0x0028,0x1102) )
352       return false;
353    // LutDescriptorBlue 
354    if ( !GetDocEntryByNumber(0x0028,0x1103) )
355       return false;   
356    // Red Palette Color Lookup Table Data
357    if ( !GetDocEntryByNumber(0x0028,0x1201) )
358       return false; 
359    // Green Palette Color Lookup Table Data       
360    if ( !GetDocEntryByNumber(0x0028,0x1202) )
361       return false;
362    // Blue Palette Color Lookup Table Data      
363    if ( !GetDocEntryByNumber(0x0028,0x1203) )
364       return false;
365    // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
366    //         NOT taken into account, but we don't know how to use it ...   
367    return true;
368 }
369
370 /**
371   * \ingroup gdcmHeader
372   * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
373   *             else 0
374   * @return Lookup Table number of Bits , 0 by default
375   *          when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
376   * @ return bit number of each LUT item 
377   */
378 int gdcmHeader::GetLUTNbits(void) {
379    std::vector<std::string> tokens;
380    //int LutLength;
381    //int LutDepth;
382    int LutNbits;
383    //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red = Lookup Table Desc-Blue
384    // Consistency already checked in GetLUTLength
385    std::string LutDescription = GetEntryByNumber(0x0028,0x1101);
386    if (LutDescription == GDCM_UNFOUND)
387       return 0;
388    tokens.erase(tokens.begin(),tokens.end()); // clean any previous value
389    Tokenize (LutDescription, tokens, "\\");
390    //LutLength=atoi(tokens[0].c_str());
391    //LutDepth=atoi(tokens[1].c_str());
392    LutNbits=atoi(tokens[2].c_str());
393    tokens.clear();
394    return LutNbits;
395 }
396
397 /**
398   * \ingroup gdcmHeader
399   * \brief builts Red/Green/Blue/Alpha LUT from Header
400   *         when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
401   *          and (0028,1101),(0028,1102),(0028,1102)  
402   *            - xxx Palette Color Lookup Table Descriptor - are found
403   *          and (0028,1201),(0028,1202),(0028,1202) 
404   *            - xxx Palette Color Lookup Table Data - are found 
405   * \warning does NOT deal with :
406   *   0028 1100 Gray Lookup Table Descriptor (Retired)
407   *   0028 1221 Segmented Red Palette Color Lookup Table Data
408   *   0028 1222 Segmented Green Palette Color Lookup Table Data
409   *   0028 1223 Segmented Blue Palette Color Lookup Table Data 
410   *   no known Dicom reader deals with them :-(
411   * @return a RGBA Lookup Table 
412   */ 
413 unsigned char * gdcmHeader::GetLUTRGBA(void) {
414 // Not so easy : see 
415 // http://www.barre.nom.fr/medical/dicom2/limitations.html#Color%20Lookup%20Tables
416
417 //  if Photometric Interpretation # PALETTE COLOR, no LUT to be done
418    if (GetEntryByNumber(0x0028,0x0004) != "PALETTE COLOR ") {
419       return NULL;
420    }  
421    int lengthR, debR, nbitsR;
422    int lengthG, debG, nbitsG;
423    int lengthB, debB, nbitsB;
424    
425 // Get info from Lut Descriptors
426 // (the 3 LUT descriptors may be different)    
427    std::string LutDescriptionR = GetEntryByNumber(0x0028,0x1101);
428    if (LutDescriptionR == GDCM_UNFOUND)
429       return NULL;
430    std::string LutDescriptionG = GetEntryByNumber(0x0028,0x1102);
431    if (LutDescriptionG == GDCM_UNFOUND)
432       return NULL;   
433    std::string LutDescriptionB = GetEntryByNumber(0x0028,0x1103);
434    if (LutDescriptionB == GDCM_UNFOUND)
435       return NULL;
436       
437    std::vector<std::string> tokens;
438       
439    tokens.erase(tokens.begin(),tokens.end()); // clean any previous value
440    Tokenize (LutDescriptionR, tokens, "\\");
441    lengthR=atoi(tokens[0].c_str()); // Red LUT length in Bytes
442    debR   =atoi(tokens[1].c_str()); // subscript of the first Lut Value
443    nbitsR =atoi(tokens[2].c_str()); // Lut item size (in Bits)
444    tokens.clear();
445    
446    tokens.erase(tokens.begin(),tokens.end()); // clean any previous value
447    Tokenize (LutDescriptionG, tokens, "\\");
448    lengthG=atoi(tokens[0].c_str()); // Green LUT length in Bytes
449    debG   =atoi(tokens[1].c_str()); // subscript of the first Lut Value
450    nbitsG =atoi(tokens[2].c_str()); // Lut item size (in Bits)
451    tokens.clear();  
452    
453    tokens.erase(tokens.begin(),tokens.end()); // clean any previous value
454    Tokenize (LutDescriptionB, tokens, "\\");
455    lengthB=atoi(tokens[0].c_str()); // Blue LUT length in Bytes
456    debB   =atoi(tokens[1].c_str()); // subscript of the first Lut Value
457    nbitsB =atoi(tokens[2].c_str()); // Lut item size (in Bits)
458    tokens.clear();
459  
460    // Load LUTs into memory, (as they were stored on disk)
461    unsigned char *lutR = (unsigned char *)
462                          GetEntryVoidAreaByNumber(0x0028,0x1201);
463    unsigned char *lutG = (unsigned char *)
464                          GetEntryVoidAreaByNumber(0x0028,0x1202);
465    unsigned char *lutB = (unsigned char *)
466                          GetEntryVoidAreaByNumber(0x0028,0x1203); 
467    
468    if (!lutR || !lutG || !lutB ) {
469       return NULL;
470    } 
471    // forge the 4 * 8 Bits Red/Green/Blue/Alpha LUT 
472    
473    unsigned char *LUTRGBA = new unsigned char[1024]; // 256 * 4 (R, G, B, Alpha) 
474    if (!LUTRGBA) {
475       return NULL;
476    }
477    memset(LUTRGBA, 0, 1024);
478    // Bits Allocated
479    int nb;
480    std::string str_nb = GetEntryByNumber(0x0028,0x0100);
481    if (str_nb == GDCM_UNFOUND ) {
482       nb = 16;
483    } else {
484       nb = atoi(str_nb.c_str() );
485    }  
486    int mult;
487
488    if (nbitsR==16 && nb==8) // when LUT item size is different than pixel size
489       mult=2;               // high byte must be = low byte 
490    else                     // See PS 3.3-2003 C.11.1.1.2 p 619
491       mult=1; 
492  
493    // if we get a black image, let's just remove the '+1'
494    // from 'i*mult+1' and check again 
495    // if it works, we shall have to check the 3 Palettes
496    // to see which byte is ==0 (first one, or second one)
497    // and fix the code
498    // We give up the checking to avoid some (useless ?)overhead 
499    // (optimistic asumption)
500    unsigned char *a;      
501    int i;
502
503    a = LUTRGBA+0;
504    for(i=0;i<lengthR;i++) {
505       *a = lutR[i*mult+1]; 
506       a+=4;       
507    }        
508    a = LUTRGBA+1;
509    for(i=0;i<lengthG;i++) {
510       *a = lutG[i*mult+1]; 
511       a+=4;       
512    }  
513    a = LUTRGBA+2;
514    for(i=0;i<lengthB;i++) {
515       *a = lutB[i*mult+1]; 
516       a+=4;       
517    }  
518    a = LUTRGBA+3;
519    for(i=0;i<256;i++) {
520       *a = 1; // Alpha component
521       a+=4; 
522    } 
523    
524    //How to free the now useless LUTs?
525    //free(LutR); free(LutB); free(LutG); // Seg Fault when used
526    return(LUTRGBA);   
527
528
529 /**
530  * \brief Accesses the info from 0002,0010 : Transfert Syntax and gdcmTS
531  *        else 1.
532  * @return The full Transfert Syntax Name (as opposed to Transfert Syntax UID)
533  */
534 std::string gdcmHeader::GetTransfertSyntaxName(void) { 
535    // use the gdcmTS (TS : Transfert Syntax)
536    std::string TransfertSyntax = GetEntryByNumber(0x0002,0x0010);
537    if (TransfertSyntax == GDCM_UNFOUND) {
538       dbg.Verbose(0, "gdcmHeader::GetTransfertSyntaxName:"
539                      " unfound Transfert Syntax (0002,0010)");
540       return "Uncompressed ACR-NEMA";
541    }
542    // we do it only when we need it
543    gdcmTS * ts = gdcmGlobal::GetTS();
544    std::string tsName=ts->GetValue(TransfertSyntax);
545    //delete ts; /// \todo Seg Fault when deleted ?!
546    return tsName;
547 }
548
549 /**
550  * \brief Sets the Pixel Area size in the Header
551  *        --> not-for-rats function
552  * @param ImageDataSize new Pixel Area Size
553  *        warning : nothing else is checked
554  */
555 void gdcmHeader::SetImageDataSize(size_t ImageDataSize) {
556    std::string content1;
557    char car[20];
558
559    sprintf(car,"%d",ImageDataSize);
560  
561    gdcmDocEntry *a = GetDocEntryByNumber(GrPixel, NumPixel);
562    a->SetLength(ImageDataSize);
563
564    ImageDataSize+=8;
565    sprintf(car,"%d",ImageDataSize);
566    content1=car;
567    SetEntryByNumber(content1, GrPixel, NumPixel);
568 }
569
570 //-----------------------------------------------------------------------------
571 // Protected
572
573 /**
574  * \brief anonymize a Header (removes Patient's personal info)
575  *        (read the code to see which ones ...)
576  */
577 bool gdcmHeader::anonymizeHeader() {
578
579   gdcmDocEntry *patientNameHE = GetDocEntryByNumber (0x0010, 0x0010);
580     
581   ReplaceIfExistByNumber ("  ",0x0010, 0x2154); // Telephone   
582   ReplaceIfExistByNumber ("  ",0x0010, 0x1040); // Adress
583   ReplaceIfExistByNumber ("  ",0x0010, 0x0020); // Patient ID
584   
585   if (patientNameHE) {
586      std::string StudyInstanceUID =  GetEntryByNumber (0x0020, 0x000d);
587      if (StudyInstanceUID !=GDCM_UNFOUND)
588         ReplaceOrCreateByNumber(StudyInstanceUID, 0x0010, 0x0010);
589      else
590         ReplaceOrCreateByNumber(std::string("anonymised"), 0x0010, 0x0010);
591   }
592   
593   // Just for fun :-(
594   // (if any) remove or replace all the stuff that contains a Date
595   
596 //0008 0012 DA ID Instance Creation Date
597 //0008 0020 DA ID Study Date
598 //0008 0021 DA ID Series Date
599 //0008 0022 DA ID Acquisition Date
600 //0008 0023 DA ID Content Date
601 //0008 0024 DA ID Overlay Date
602 //0008 0025 DA ID Curve Date
603 //0008 002a DT ID Acquisition Datetime
604 //0018 9074 DT ACQ Frame Acquisition Datetime
605 //0018 9151 DT ACQ Frame Reference Datetime
606 //0018 a002 DT ACQ Contribution Date Time
607 //0020 3403 SH REL Modified Image Date (RET)
608 //0032 0032 DA SDY Study Verified Date
609 //0032 0034 DA SDY Study Read Date
610 //0032 1000 DA SDY Scheduled Study Start Date
611 //0032 1010 DA SDY Scheduled Study Stop Date
612 //0032 1040 DA SDY Study Arrival Date
613 //0032 1050 DA SDY Study Completion Date
614 //0038 001a DA VIS Scheduled Admission Date
615 //0038 001c DA VIS Scheduled Discharge Date
616 //0038 0020 DA VIS Admitting Date
617 //0038 0030 DA VIS Discharge Date
618 //0040 0002 DA PRC Scheduled Procedure Step Start Date
619 //0040 0004 DA PRC Scheduled Procedure Step End Date
620 //0040 0244 DA PRC Performed Procedure Step Start Date
621 //0040 0250 DA PRC Performed Procedure Step End Date
622 //0040 2004 DA PRC Issue Date of Imaging Service Request
623 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
624 //0040 4011 DT PRC Expected Completion Date and Time
625 //0040 a030 DT PRC Verification Date Time
626 //0040 a032 DT PRC Observation Date Time
627 //0040 a120 DT PRC DateTime
628 //0040 a121 DA PRC Date
629 //0040 a13a DT PRC Referenced Datetime
630 //0070 0082 DA ??? Presentation Creation Date
631 //0100 0420 DT ??? SOP Autorization Date and Time
632 //0400 0105 DT ??? Digital Signature DateTime
633 //2100 0040 DA PJ Creation Date
634 //3006 0008 DA SSET Structure Set Date
635 //3008 0024 DA ??? Treatment Control Point Date
636 //3008 0054 DA ??? First Treatment Date
637 //3008 0056 DA ??? Most Recent Treatment Date
638 //3008 0162 DA ??? Safe Position Exit Date
639 //3008 0166 DA ??? Safe Position Return Date
640 //3008 0250 DA ??? Treatment Date
641 //300a 0006 DA RT RT Plan Date
642 //300a 022c DA RT Air Kerma Rate Reference Date
643 //300e 0004 DA RT Review Date
644  return true;  
645 }
646 //-----------------------------------------------------------------------------
647 // Private
648
649 //-----------------------------------------------------------------------------