]> Creatis software - gdcm.git/blob - src/gdcmDataEntry.cxx
Port Mathieu's patch from gdcm 1.2 (in gdcmValEntry.cxx)
[gdcm.git] / src / gdcmDataEntry.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmDataEntry.cxx,v $
5   Language:  C++
6   Date:      $Date: 2006/07/04 09:36:18 $
7   Version:   $Revision: 1.40 $
8                                                                                 
9   Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de
10   l'Image). All rights reserved. See Doc/License.txt or
11   http://www.creatis.insa-lyon.fr/Public/Gdcm/License.html for details.
12                                                                                 
13      This software is distributed WITHOUT ANY WARRANTY; without even
14      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15      PURPOSE.  See the above copyright notices for more information.
16                                                                                 
17 =========================================================================*/
18
19 #include "gdcmDataEntry.h"
20 #include "gdcmVR.h"
21 #include "gdcmTS.h"
22 #include "gdcmGlobal.h"
23 #include "gdcmUtil.h"
24 #include "gdcmDebug.h"
25
26 #include <fstream>
27
28 #if defined(__BORLANDC__)
29  #include <mem.h> // for memcpy
30  #include <stdlib.h> // for atof
31  #include <ctype.h> // for isdigit
32 #endif
33
34 namespace gdcm 
35 {
36 //-----------------------------------------------------------------------------
37 #define MAX_SIZE_PRINT_ELEMENT_VALUE 0x7fffffff
38 uint32_t DataEntry::MaxSizePrintEntry = MAX_SIZE_PRINT_ELEMENT_VALUE;
39
40 //-----------------------------------------------------------------------------
41 // Constructor / Destructor
42 /**
43  * \brief   Constructor for a given DataEntry
44  * @param   group group number of the Data Entry to be created
45  * @param   elem element number of the Data Entry to be created
46  * @param   vr Value Representation of the Data Entry to be created 
47  */
48 DataEntry::DataEntry(uint16_t group,uint16_t elem,
49                                      VRKey const &vr) 
50             : DocEntry(group,elem,vr)
51 {
52    State = STATE_LOADED;
53    Flag = FLAG_NONE;
54
55    BinArea = 0;
56    SelfArea = true;
57 }
58
59 /**
60  * \brief   Constructor for a given DocEntry
61  * @param   e Pointer to existing Doc entry
62  */
63 DataEntry::DataEntry(DocEntry *e)
64             //: DocEntry(e->GetDictEntry())
65             : DocEntry(e->GetGroup(),e->GetElement(), e->GetVR()  )
66 {
67    Flag = FLAG_NONE;
68    BinArea = 0;
69    SelfArea = true;
70
71    Copy(e);
72 }
73
74 /**
75  * \brief   Canonical destructor.
76  */
77 DataEntry::~DataEntry ()
78 {
79    DeleteBinArea();
80 }
81
82 //-----------------------------------------------------------------------------
83 // Print
84
85 //-----------------------------------------------------------------------------
86 // Public
87 /**
88  * \brief Sets the value (non string) of the current DataEntry
89  * @param area area
90  * @param self self
91  */
92 void DataEntry::SetBinArea( uint8_t *area, bool self )  
93
94    DeleteBinArea();
95
96    BinArea = area;
97    SelfArea = self;
98
99    State = STATE_LOADED;
100 }
101 /**
102  * \brief Inserts the value (non string) into the current DataEntry
103  * @param area area
104  * @param length length 
105  */
106 void DataEntry::CopyBinArea( uint8_t *area, uint32_t length )
107 {
108    DeleteBinArea();
109
110    uint32_t lgh = length + length%2;
111    SetLength(lgh);
112
113    if( area && length > 0 )
114    {
115       NewBinArea();
116       memcpy(BinArea,area,length);
117       if( length!=lgh )
118          BinArea[length]=0;
119
120       State = STATE_LOADED;
121    }
122 }
123
124 /**
125  * \brief Inserts the elementary (non string) value into the current DataEntry
126  * @param id index of the elementary value to be set
127  * @param val value, passed as a double 
128  */
129 void DataEntry::SetValue(const uint32_t &id, const double &val)
130 {
131    if( !BinArea )
132       NewBinArea();
133    State = STATE_LOADED;
134
135    if( id > GetValueCount() )
136    {
137       gdcmErrorMacro("Index (" << id << ")is greater than the data size");
138       return;
139    }
140
141    const VRKey &vr = GetVR();
142    if( vr == "US" || vr == "SS" )
143    {
144       uint16_t *data = (uint16_t *)BinArea;
145       data[id] = (uint16_t)val;
146    }
147    else if( vr == "UL" || vr == "SL" )
148    {
149       uint32_t *data = (uint32_t *)BinArea;
150       data[id] = (uint32_t)val;
151    }
152    else if( vr == "FL" )
153    {
154       float *data = (float *)BinArea;
155       data[id] = (float)val;
156    }
157    else if( vr == "FD" )
158    {
159       double *data = (double *)BinArea;
160       data[id] = (double)val;
161    }
162    else if( Global::GetVR()->IsVROfStringRepresentable(vr) )
163    {
164       gdcmErrorMacro("SetValue on String representable not implemented yet");
165    }
166    else
167    {
168       BinArea[id] = (uint8_t)val;
169    }
170 }
171 /**
172  * \brief returns, as a double (?!?) one of the values 
173  *      (when entry is multivaluated), identified by its index.
174  *      Returns 0.0 if index is wrong
175  * @param id id
176  */
177 double DataEntry::GetValue(const uint32_t &id) const
178 {
179    if( !BinArea )
180    {
181       if (GetLength() != 0) // avoid stupid messages
182    /// \todo warn the user there was a problem !
183          gdcmErrorMacro("BinArea not set " << std::hex 
184                      << GetGroup() << " " << GetElement() 
185                      << " Can't get the value");
186       return 0.0;
187    }
188
189    uint32_t count = GetValueCount();
190    if( id > count )
191    {
192       gdcmErrorMacro("Index (" << id << ") is greater than the data size");
193       return 0.0;
194    }
195
196    /// \todo FIX the API : user *knows* that entry contains a US
197    ///               and he receives a double ?!?
198    
199    const VRKey &vr = GetVR();
200    /// \todo FIX the API : user *knows* that entry contains a US,
201    ///       the method is supposed to return a double
202    ///       but sends a US ?!? 
203    if( vr == "US" || vr == "SS" )
204       return ((uint16_t *)BinArea)[id];
205    else if( vr == "UL" || vr == "SL" )
206       return ((uint32_t *)BinArea)[id];
207    else if( vr == "FL" )
208       return ((float *)BinArea)[id];
209    else if( vr == "FD" )
210       return ((double *)BinArea)[id];
211    else if( Global::GetVR()->IsVROfStringRepresentable(vr) )
212    {
213       // this is for VR = "DS", ...
214       if( GetLength() )
215       {
216          // Don't use std::string to accelerate processing
217          double val;
218          char *tmp = new char[GetLength()+1];
219          memcpy(tmp,BinArea,GetLength());
220          tmp[GetLength()]=0;
221
222          if( count == 0 )
223          {
224             val = atof(tmp);
225          }
226          else
227          {
228             count = id;
229             char *beg = tmp;
230             for(uint32_t i=0;i<GetLength();i++)
231             {
232                if( tmp[i] == '\\' )
233                {
234                   if( count == 0 )
235                   {
236                      tmp[i] = 0;
237                      break;
238                   }
239                   else
240                   {
241                      count--;
242                      beg = &(tmp[i+1]);
243                   }
244                }
245             }
246             val = atof(beg);
247          }
248
249          delete[] tmp;
250          return val;
251       }
252       else 
253          return 0.0;
254    }
255    else
256       return BinArea[id];
257 }
258
259 /**
260  * \brief Checks if the multiplicity of the value follows Dictionary VM
261  */
262 bool DataEntry::IsValueCountValid() /*const*/
263 {
264   uint32_t vm;
265   const std::string &strVM = GetVM();
266   uint32_t vc = GetValueCount();
267   bool valid = vc == 0;
268   if( valid )
269     return true;
270   
271   // FIXME : what shall we do with VM = "2-n", "3-n", etc
272   
273   if( strVM == "1-n" )
274   {
275     // make sure there is at least one ??? FIXME
276     valid = vc >= 1;
277   }
278   else
279   {
280     std::istringstream os;
281     os.str( strVM );
282     os >> vm;
283     // Two cases:
284     // vm respects the one from the dict
285     // vm is 0 (we need to check if this element is allowed to be empty) FIXME
286
287     // note  (JPR)
288     // ----    
289     // Entries whose type is 1 are mandatory, with a mandatory value.
290     // Entries whose type is 1c are mandatory-inside-a-Sequence,
291     //                          with a mandatory value.
292     // Entries whose type is 2 are mandatory, with an optional value.
293     // Entries whose type is 2c are mandatory-inside-a-Sequence,
294     //                          with an optional value.
295     // Entries whose type is 3 are optional.
296
297     // case vc == 0 is only applicable for 'type 2' entries.
298     // Problem : entry type may depend on the modality and/or the Sequence
299     //           it's embedded in !
300     //          (Get the information in the 'Conformance Statements' ...)  
301     valid = vc == vm;
302   }
303   return valid;
304 }
305
306 /**
307  * \brief returns the number of elementary values
308  */ 
309 uint32_t DataEntry::GetValueCount( ) const
310 {
311    const VRKey &vr = GetVR();
312    if( vr == "US" || vr == "SS" )
313       return GetLength()/sizeof(uint16_t);
314    else if( vr == "UL" || vr == "SL" )
315       return GetLength()/sizeof(uint32_t);
316    else if( vr == "FL" || vr == "OF" )
317       return GetLength()/4 ; // FL has a *4* length! sizeof(float);
318    else if( vr == "FD" )
319       return GetLength()/8;  // FD has a *8* length! sizeof(double);
320    else if( Global::GetVR()->IsVROfStringRepresentable(vr) )
321    {
322       // Some element in DICOM are allowed to be empty
323       if( !GetLength() ) 
324          return 0;
325       // Don't use std::string to accelerate processing
326       uint32_t count = 1;
327       for(uint32_t i=0;i<GetLength();i++)
328       {
329          if( BinArea[i] == '\\')
330             count++;
331       }
332       return count;
333    }
334    return GetLength();
335 }
336
337 /**
338  * \brief Gets a std::vector <double> holding the value(s) of a DS DataEntry
339  * @param valueVector std::vector double of value(s)
340  * \return false if VR not "DS" or DataEntry empty
341  */
342  bool DataEntry::GetDSValue(std::vector <double> &valueVector)
343  {
344     /// \todo rewrite the whole method, in order *not to use* std::string !
345     std::vector<std::string> tokens;
346     
347     if (GetVR() != "DS") // never trust a user !
348        return false;    
349        
350     Util::Tokenize ( GetString().c_str(), tokens, "\\" );
351         
352     int nbValues= tokens.size();
353     if (nbValues == 0)
354        return false;
355                
356     for (int loop=0; loop<nbValues; loop++) 
357        valueVector.push_back(atof(tokens[loop].c_str()));
358     
359     return true;  
360  }
361  
362 /**
363  * \brief Sets the 'value' of a DataEntry, passed as a std::string
364  * @param value string representation of the value to be set
365  */ 
366 void DataEntry::SetString(std::string const &value)
367 {
368    DeleteBinArea();
369    const VRKey &vr = GetVR();
370    if ( vr == "US" || vr == "SS" )
371    {
372       std::vector<std::string> tokens;
373       Util::Tokenize (value, tokens, "\\");
374       SetLength(tokens.size()*sizeof(uint16_t));
375       NewBinArea();
376
377       uint16_t *data = (uint16_t *)BinArea;
378       for (unsigned int i=0; i<tokens.size();i++)
379          data[i] = atoi(tokens[i].c_str());
380       tokens.clear();
381    }
382    else if ( vr == "UL" || vr == "SL" )
383    {
384       std::vector<std::string> tokens;
385       Util::Tokenize (value, tokens, "\\");
386       SetLength(tokens.size()*sizeof(uint32_t));
387       NewBinArea();
388
389       uint32_t *data = (uint32_t *)BinArea;
390       for (unsigned int i=0; i<tokens.size();i++)
391          data[i] = atoi(tokens[i].c_str());
392       tokens.clear();
393    }
394    else if ( vr == "FL" )
395    {
396       std::vector<std::string> tokens;
397       Util::Tokenize (value, tokens, "\\");
398       SetLength(tokens.size()*sizeof(float));
399       NewBinArea();
400
401       float *data = (float *)BinArea;
402       for (unsigned int i=0; i<tokens.size();i++)
403          data[i] = (float)atof(tokens[i].c_str());
404       tokens.clear();
405    }
406    else if ( vr == "FD" )
407    {
408       std::vector<std::string> tokens;
409       Util::Tokenize (value, tokens, "\\");
410       SetLength(tokens.size()*sizeof(double));
411       NewBinArea();
412
413       double *data = (double *)BinArea;
414       for (unsigned int i=0; i<tokens.size();i++)
415          data[i] = atof(tokens[i].c_str());
416       tokens.clear();
417    }
418    else
419    {      
420       size_t l =  value.size();    
421       SetLength(l + l%2);
422       NewBinArea();
423       memcpy(BinArea, value.c_str(), l);
424       if (l%2) // padded with blank except for UI
425          if ( vr == "UI" ) 
426             BinArea[l] = '\0';
427          else
428             BinArea[l] = ' ';                
429    }
430    State = STATE_LOADED;
431 }
432 /**
433  * \brief   returns as a string (when possible) the value of the DataEntry
434  */
435 std::string const &DataEntry::GetString() const
436 {
437   static std::ostringstream s;
438   const VRKey &vr = GetVR();
439   s.str("");
440   StrArea="";
441
442   if( !BinArea )
443      return StrArea;
444       // When short integer(s) are stored, convert the following (n * 2) characters
445   // as a displayable string, the values being separated by a back-slash
446   if( vr == "US" )
447   {
448      uint16_t *data=(uint16_t *)BinArea;
449      for (unsigned int i=0; i < GetValueCount(); i++)
450      {
451         if( i!=0 )
452            s << '\\';
453         s << data[i];
454      }
455      StrArea=s.str();
456   }
457   else if (vr == "SS" )
458   {
459      int16_t *data=(int16_t *)BinArea;
460      for (unsigned int i=0; i < GetValueCount(); i++)
461      {
462         if( i!=0 )
463            s << '\\';
464         s << data[i];
465      }
466      StrArea=s.str();
467   }      // See above comment on multiple short integers (mutatis mutandis).
468   else if( vr == "UL" )
469   {
470      uint32_t *data=(uint32_t *)BinArea;
471      for (unsigned int i=0; i < GetValueCount(); i++)
472      {
473         if( i!=0 )
474            s << '\\';
475         s << data[i];
476      }
477      StrArea=s.str();
478   }
479   else if( vr == "SL" )
480   {
481      int32_t *data=(int32_t *)BinArea;
482      for (unsigned int i=0; i < GetValueCount(); i++)
483      {
484         if( i!=0 )
485            s << '\\';
486         s << data[i];
487      }
488      StrArea=s.str();
489   }    else if( vr == "FL" )
490   {
491      float *data=(float *)BinArea;
492      for (unsigned int i=0; i < GetValueCount(); i++)
493      {
494         if( i!=0 )
495            s << '\\';
496         s << data[i];
497      }
498      StrArea=s.str();
499   }
500   else if( vr == "FD" )
501   {
502      double *data=(double *)BinArea;
503      for (unsigned int i=0; i < GetValueCount(); i++)
504      {
505         if( i!=0 )
506            s << '\\';
507         s << data[i];
508      }
509      StrArea=s.str();
510   }
511   else
512   {
513      StrArea.append((const char *)BinArea,GetLength());
514      // to avoid gdcm to propagate oddities in lengthes
515      if ( GetLength()%2)
516         StrArea.append(" ",1);   }
517   return StrArea;
518 }
519
520
521 /**
522  * \brief Copies all the attributes from an other DocEntry 
523  * @param doc entry to copy from
524  * @remarks The content BinArea is copied too
525  */
526 void DataEntry::Copy(DocEntry *doc)
527 {
528    DocEntry::Copy(doc);
529
530    DataEntry *entry = dynamic_cast<DataEntry *>(doc);
531    if ( entry )
532    {
533       State = entry->State;
534       Flag = entry->Flag;
535       CopyBinArea(entry->BinArea,entry->GetLength());
536    }
537 }
538
539 /**
540  * \brief   Writes the 'value' area of a DataEntry
541  * @param fp already open ofstream pointer
542  * @param filetype type of the file (ACR, ImplicitVR, ExplicitVR, ...)
543  */
544 void DataEntry::WriteContent(std::ofstream *fp, FileType filetype)
545
546    DocEntry::WriteContent(fp, filetype);
547
548    if ( GetGroup() == 0xfffe )
549    {
550       return; //delimitors have NO value
551    }
552    
553    // --> We only deal with Little Endian writting.
554    // --> forget Big Endian Transfer Syntax writting!
555    //     Next DICOM version will give it up ...
556  
557    // WARNING - For Implicit VR private element,
558    //           we have *no choice* but considering them as
559    //           something like 'OB' values.
560    //           we rewrite them as we found them on disc.
561    //           Some trouble will occur if element was 
562    //           *actually* OW, if image was produced 
563    //           on Big endian based processor, read and writen 
564    //           on Little endian based processor
565    //           and, later on, somebody needs
566    //           this 'OW' Implicit VR private element (?!?)
567    //           (Same stuff, mutatis mutandis, for Little/Big)
568  
569    // 8/16 bits Pixels problem should be solved automatiquely,
570    // since we ensure the VR (OB vs OW) is conform to Pixel size.
571         
572    uint8_t *data = BinArea; //safe notation
573    size_t l = GetLength(); 
574 //   gdcmDebugMacro("in DataEntry::WriteContent " << GetKey() << " AtomicLength: "
575 //              << Global::GetVR()->GetAtomicElementLength(this->GetVR() ) // << " BinArea in :" << &BinArea
576 //             );
577    if (BinArea) // the binArea was *actually* loaded
578    {
579 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
580       unsigned short vrLgth = 
581                         Global::GetVR()->GetAtomicElementLength(this->GetVR());
582       unsigned int i;
583       switch(vrLgth)
584       {
585          case 1:
586          {
587             binary_write (*fp, data, l );           
588             break;
589          }     
590          case 2:
591          {
592             uint16_t *data16 = (uint16_t *)data;
593             for(i=0;i<l/vrLgth;i++)
594                binary_write( *fp, data16[i]);
595             break;
596          }
597          case 4:
598          {
599             uint32_t *data32 = (uint32_t *)data;
600             for(i=0;i<l/vrLgth;i++)
601                binary_write( *fp, data32[i]);
602             break;
603          }
604          case 8:
605          {
606             double *data64 = (double *)data;
607             for(i=0;i<l/vrLgth;i++)
608                binary_write( *fp, data64[i]);
609             break;
610          }
611       }
612 #else
613    binary_write (*fp, data, l );
614 #endif //GDCM_WORDS_BIGENDIAN
615
616    }
617    else
618    {
619       // nothing was loaded, but we need to skip space on disc     
620       if (l != 0)
621       {
622       //  --> WARNING : nothing is written; 
623       //  --> the initial data (on the the source image) is lost
624       //  --> user is *not* informed !      
625          gdcmDebugMacro ("Nothing was loaded, but we need to skip space on disc. "
626                       << "Length =" << l << " for " << GetKey() );   
627          fp->seekp(l, std::ios::cur); // At Write time, for unloaded elems
628       }
629    }
630    // to avoid gdcm to propagate oddities
631    // (length was already modified)  
632    if (l%2)
633       fp->seekp(1, std::ios::cur);  // At Write time, for non even length elems
634 }
635
636 /**
637  * \brief   Compute the full length of the elementary DataEntry (not only value
638  *          length) depending on the VR.
639  */
640 uint32_t DataEntry::ComputeFullLength()
641 {
642    return GetFullLength();
643 }
644
645 //-----------------------------------------------------------------------------
646 // Protected
647 /// \brief Creates a DataEntry owned BinArea (remove previous one if any)
648 void DataEntry::NewBinArea( )
649 {
650    DeleteBinArea();
651    if( GetLength() > 0 )
652       BinArea = new uint8_t[GetLength()];
653    SelfArea = true;
654 }
655 /// \brief Removes the BinArea, if owned by the DataEntry
656 void DataEntry::DeleteBinArea(void)
657 {
658    if (BinArea && SelfArea)
659    {
660       delete[] BinArea;
661       BinArea = NULL;
662    }
663 }
664
665 //-----------------------------------------------------------------------------
666 // Private
667
668 //-----------------------------------------------------------------------------
669 // Print
670 /**
671  * \brief   Prints a DataEntry (Dicom entry)
672  * @param   os ostream we want to print in
673  * @param indent Indentation string to be prepended during printing
674  */
675 void DataEntry::Print(std::ostream &os, std::string const & )
676 {
677    os << "D ";
678    DocEntry::Print(os);
679
680    uint16_t g = GetGroup();
681    if (g == 0xfffe) // delimiters have NO value
682    {          
683       return; // just to avoid identing all the remaining code 
684    }
685
686    std::ostringstream s;
687    TSAtr v;
688
689    if( BinArea )
690    {
691       v = GetString();
692       const VRKey &vr = GetVR();
693
694       if( vr == "US" || vr == "SS" || vr == "UL" || vr == "SL" 
695        || vr == "FL" || vr == "FD")
696          s << " [" << GetString() << "]";
697       else
698       { 
699          if(Global::GetVR()->IsVROfStringRepresentable(vr))
700          {
701             // replace non printable characters by '.'
702             std::string cleanString = Util::CreateCleanString(v);
703             if ( cleanString.length() <= GetMaxSizePrintEntry()
704               || PrintLevel >= 3
705               || IsNotLoaded() )
706            // FIXME : when IsNotLoaded(), you create a Clean String ?!?
707            // FIXME : PrintLevel<2 *does* print the values 
708            //        (3 is only for extra offsets printing)
709            // What do you wanted to do ? JPR
710             {
711                s << " [" << cleanString << "]";
712             }
713             else
714             {
715                s << " [gdcm::too long for print (" << cleanString.length() << ") ]";
716             }
717          }
718          else
719          {
720             // A lot of Private elements (with no VR) contain actually 
721             // only printable characters;
722             // Let's deal with them as is they were VR std::string representable
723     
724             if ( Util::IsCleanArea( GetBinArea(), GetLength()  ) )
725             {
726                // FIXME : since the 'Area' *is* clean, just use
727                //         a 'CreateString' method, to save CPU time.
728                std::string cleanString = 
729                      Util::CreateCleanString( BinArea,GetLength()  );
730                s << " [" << cleanString << "]";
731             }
732             else
733             {
734                s << " [" << GDCM_BINLOADED << ";"
735                << "length = " << GetLength() << "]";
736             }
737          }
738       }
739    }
740    else
741    {
742       if( IsNotLoaded() )
743          s << " [" << GDCM_NOTLOADED << "]";
744       else if( IsUnfound() )
745          s << " [" << GDCM_UNFOUND << "]";
746       else if( IsUnread() )
747          s << " [" << GDCM_UNREAD << "]";
748       else if ( GetLength() == 0 )
749          s << " []";
750    }
751
752    if( IsPixelData() )
753       s << " (" << GDCM_PIXELDATA << ")";
754
755    // Display the UID value (instead of displaying only the rough code)
756    // First 'clean' trailing character (space or zero) 
757    if(BinArea)
758    {
759       const uint16_t &gr = GetGroup();
760       const uint16_t &elt = GetElement();
761       TS *ts = Global::GetTS();
762
763       if (gr == 0x0002)
764       {
765          // Any more to be displayed ?
766          if ( elt == 0x0010 || elt == 0x0002 )
767          {
768             if ( v.length() != 0 )  // for brain damaged headers
769             {
770                if ( ! isdigit((unsigned char)v[v.length()-1]) )
771                {
772                   v.erase(v.length()-1, 1);
773                }
774             }
775             s << "  ==>\t[" << ts->GetValue(v) << "]";
776          }
777       }
778       else if (gr == 0x0008)
779       {
780          if ( elt == 0x0016 || elt == 0x1150 )
781          {
782             if ( v.length() != 0 )  // for brain damaged headers
783             {
784                if ( ! isdigit((unsigned char)v[v.length()-1]) )
785                {
786                   v.erase(v.length()-1, 1);
787                }
788             }
789             s << "  ==>\t[" << ts->GetValue(v) << "]";
790          }
791       }
792       else if (gr == 0x0004)
793       {
794          if ( elt == 0x1510 || elt == 0x1512  )
795          {
796             if ( v.length() != 0 )  // for brain damaged headers  
797             {
798                if ( ! isdigit((unsigned char)v[v.length()-1]) )
799                {
800                   v.erase(v.length()-1, 1);  
801                }
802             }
803             s << "  ==>\t[" << ts->GetValue(v) << "]";
804          }
805       }
806    }
807
808    os << s.str();
809 }
810
811 //-----------------------------------------------------------------------------
812 } // end namespace gdcm
813