]> Creatis software - gdcm.git/blob - src/gdcmFile.cxx
COMP: Try to get read of the float issues
[gdcm.git] / src / gdcmFile.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmFile.cxx,v $
5   Language:  C++
6   Date:      $Date: 2005/07/24 02:10:48 $
7   Version:   $Revision: 1.262 $
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 //
20 // --------------  Remember ! ----------------------------------
21 //
22 // Image Position Patient                              (0020,0032):
23 // If not found (ACR_NEMA) we try Image Position       (0020,0030)
24 // If not found (ACR-NEMA), we consider Slice Location (0020,1041)
25 //                                   or Location       (0020,0050) 
26 //                                   as the Z coordinate, 
27 // 0. for all the coordinates if nothing is found
28 //
29 // Image Position (Patient) (0020,0032) VM=3 What is it used for?
30 // -->
31 //  The attribute Patient Orientation (0020,0020) from the General Image Module 
32 // is of type 2C and has the condition Required if image does not require 
33 // Image Orientation (0020,0037) and Image Position (0020,0032). 
34 // However, if the image does require the attributes 
35 // - Image Orientation (Patient) (0020,0037), VM=6
36 // - Image Position Patient (0020,0032), VM=3
37 // then attribute Patient Orientation (0020,0020) should not be present
38 //  in the images.
39 //
40 // Remember also :
41 // Patient Position (0018,5100) values : HFP   = Head First-Prone
42 //                                       HFS   = Head First-Supine
43 //                                       HFDR  = Head First-Decubitus Right
44 //                                       HFDL = Head First-Decubitus Left
45 //                                       FFDR = Feet First-Decubitus Right
46 //                                       FFDL = Feet First-Decubitus Left
47 //                                       FFP  = Feet First-Prone
48 //                                       FFS  = Feet First-Supine
49 //                    can also find      SEMIERECT
50 //                                       SUPINE
51 // CS 2 Patient Orientation (0020 0020)
52 //               When the coordinates of the image 
53 //               are always present, this field is almost never used.
54 //               Better we don't tust it too much ...
55 //               Found Values are :      L\P
56 //                                       L\FP
57 //                                       P\F
58 //                                       L\F
59 //                                       P\FR
60 //                                       R\F
61 //
62 // (0020|0037) [Image Orientation (Patient)] [1\0\0\0\1\0 ]
63
64                                       
65 // ---------------------------------------------------------------
66 //
67 #include "gdcmFile.h"
68 #include "gdcmGlobal.h"
69 #include "gdcmUtil.h"
70 #include "gdcmDebug.h"
71 #include "gdcmTS.h"
72 #include "gdcmValEntry.h"
73 #include "gdcmBinEntry.h"
74 #include "gdcmSeqEntry.h"
75 #include "gdcmRLEFramesInfo.h"
76 #include "gdcmJPEGFragmentsInfo.h"
77
78 #include <vector>
79 #include <stdio.h> //sscanf
80 #include <stdlib.h> // for atoi
81 #include <math.h> // for pow
82
83 namespace gdcm 
84 {
85
86 //-----------------------------------------------------------------------------
87 // Constructor / Destructor
88
89 /**
90  * \brief Constructor used when we want to generate dicom files from scratch
91  */
92 File::File():
93    Document()
94 {
95    RLEInfo  = new RLEFramesInfo;
96    JPEGInfo = new JPEGFragmentsInfo;
97    GrPixel  = 0x7fe0;  // to avoid further troubles
98    NumPixel = 0x0010;
99 }
100
101
102 /**
103  * \brief   Canonical destructor.
104  */
105 File::~File ()
106 {
107    if ( RLEInfo )
108       delete RLEInfo;
109    if ( JPEGInfo )
110       delete JPEGInfo;
111 }
112
113 //-----------------------------------------------------------------------------
114 // Public
115 /**
116  * \brief   Loader  
117  * @return false if file cannot be open or no swap info was found,
118  *         or no tag was found.
119  */
120 bool File::Load( ) 
121 {
122    if ( ! this->Document::Load( ) )
123       return false;
124
125     return DoTheLoadingJob( );   
126 }
127
128 /**
129  * \brief   Does the Loading Job (internal use only)
130  * @return false if file cannot be open or no swap info was found,
131  *         or no tag was found.
132  */
133 bool File::DoTheLoadingJob( ) 
134 {
135
136    // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
137    // We may encounter the 'RETired' (0x0028, 0x0200) tag
138    // (Image Location") . This entry contains the number of
139    // the group that contains the pixel data (hence the "Pixel Data"
140    // is found by indirection through the "Image Location").
141    // Inside the group pointed by "Image Location" the searched element
142    // is conventionally the element 0x0010 (when the norm is respected).
143    // When the "Image Location" is missing we default to group 0x7fe0.
144    // Note: this IS the right place for the code
145  
146    // Image Location
147    const std::string &imgLocation = GetEntryValue(0x0028, 0x0200);
148    if ( imgLocation == GDCM_UNFOUND )
149    {
150       // default value
151       GrPixel = 0x7fe0;
152    }
153    else
154    {
155       GrPixel = (uint16_t) atoi( imgLocation.c_str() );
156    }   
157
158    // sometimes Image Location value doesn't follow
159    // the supposed processor endianness.
160    // see gdcmData/cr172241.dcm
161    if ( GrPixel == 0xe07f )
162    {
163       GrPixel = 0x7fe0;
164    }
165
166    if ( GrPixel != 0x7fe0 )
167    {
168       // This is a kludge for old dirty Philips imager.
169       NumPixel = 0x1010;
170    }
171    else
172    {
173       NumPixel = 0x0010;
174    }
175
176    // Now, we know GrPixel and NumPixel.
177    // Let's create a VirtualDictEntry to allow a further VR modification
178    // and force VR to match with BitsAllocated.
179    DocEntry *entry = GetDocEntry(GrPixel, NumPixel); 
180    if ( entry != 0 )
181    {
182       // Compute the RLE or JPEG info
183       OpenFile();
184       const std::string &ts = GetTransferSyntax();
185       Fp->seekg( entry->GetOffset(), std::ios::beg );
186       if ( Global::GetTS()->IsRLELossless(ts) ) 
187          ComputeRLEInfo();
188       else if ( Global::GetTS()->IsJPEG(ts) )
189          ComputeJPEGFragmentInfo();
190       CloseFile();
191
192       // Create a new BinEntry to change the DictEntry
193       // The changed DictEntry will have 
194       // - a correct PixelVR OB or OW)
195       // - the name to "Pixel Data"
196       BinEntry *oldEntry = dynamic_cast<BinEntry *>(entry);
197       if (oldEntry)
198       {
199          std::string PixelVR;
200          // 8 bits allocated is a 'O Bytes' , as well as 24 (old ACR-NEMA RGB)
201          // more than 8 (i.e 12, 16) is a 'O Words'
202          if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 ) 
203             PixelVR = "OB";
204          else
205             PixelVR = "OW";
206
207          // Change only made if usefull
208          if ( PixelVR != oldEntry->GetVR() )
209          {
210             DictEntry* newDict = NewVirtualDictEntry(GrPixel,NumPixel,
211                                                      PixelVR,"1","Pixel Data");
212
213             BinEntry *newEntry = new BinEntry(newDict);
214             newEntry->Copy(entry);
215             newEntry->SetBinArea(oldEntry->GetBinArea(),oldEntry->IsSelfArea());
216             oldEntry->SetSelfArea(false);
217
218             RemoveEntry(oldEntry);
219             AddEntry(newEntry);
220          }
221       }
222    }
223    return true;
224 }
225 /**
226  * \brief  This predicate, based on hopefully reasonable heuristics,
227  *         decides whether or not the current File was properly parsed
228  *         and contains the mandatory information for being considered as
229  *         a well formed and usable Dicom/Acr File.
230  * @return true when File is the one of a reasonable Dicom/Acr file,
231  *         false otherwise. 
232  */
233 bool File::IsReadable()
234 {
235    if ( !Document::IsReadable() )
236    {
237       return false;
238    }
239
240    const std::string &res = GetEntryValue(0x0028, 0x0005);
241    if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 )
242    {
243       gdcmWarningMacro("Wrong Image Dimensions" << res);
244       return false; // Image Dimensions
245    }
246    bool b0028_0100 = true;
247    if ( !GetDocEntry(0x0028, 0x0100) )
248    {
249       gdcmWarningMacro("Bits Allocated (0028|0100) not found"); 
250       //return false; // "Bits Allocated"
251       b0028_0100 = false;
252    }
253    bool b0028_0101 = true;
254    if ( !GetDocEntry(0x0028, 0x0101) )
255    {
256       gdcmWarningMacro("Bits Stored (0028|0101) not found");
257       //return false; // "Bits Stored"
258       b0028_0101 = false;
259    }
260    bool b0028_0102 = true;
261    if ( !GetDocEntry(0x0028, 0x0102) )
262    {
263       gdcmWarningMacro("Hight Bit (0028|0102) not found"); 
264       //return false; // "High Bit"
265       b0028_0102 = false;
266    }
267    bool b0028_0103 = true;
268    if ( !GetDocEntry(0x0028, 0x0103) )
269    {
270       gdcmWarningMacro("Pixel Representation (0028|0103) not found");
271       //return false; // "Pixel Representation" i.e. 'Sign' ( 0 : unsigned, 1 : signed)
272       b0028_0103 = false;
273    }
274
275    if ( !b0028_0100 && !b0028_0101 && !b0028_0102 && !b0028_0103)
276    {
277       gdcmWarningMacro("Too much mandatory Tags missing !");
278       return false;
279    }
280
281    if ( !GetDocEntry(GrPixel, NumPixel) )
282    {
283       gdcmWarningMacro("Pixel Dicom Element " << std::hex <<
284                         GrPixel << "|" << NumPixel << "not found");
285       return false; // Pixel Dicom Element not found :-(
286    }
287    return true;
288 }
289
290 /**
291  * \brief gets the info from 0020,0013 : Image Number else 0.
292  * @return image number
293  */
294 int File::GetImageNumber()
295 {
296    //0020 0013 : Image Number
297    std::string strImNumber = GetEntryValue(0x0020,0x0013);
298    if ( strImNumber != GDCM_UNFOUND )
299    {
300       return atoi( strImNumber.c_str() );
301    }
302    return 0;   //Hopeless
303 }
304
305 /**
306  * \brief gets the info from 0008,0060 : Modality
307  * @return Modality Type
308  */
309 ModalityType File::GetModality()
310 {
311    // 0008 0060 : Modality
312    std::string strModality = GetEntryValue(0x0008,0x0060);
313    if ( strModality != GDCM_UNFOUND )
314    {
315            if ( strModality.find("AU")  < strModality.length()) return AU;
316       else if ( strModality.find("AS")  < strModality.length()) return AS;
317       else if ( strModality.find("BI")  < strModality.length()) return BI;
318       else if ( strModality.find("CF")  < strModality.length()) return CF;
319       else if ( strModality.find("CP")  < strModality.length()) return CP;
320       else if ( strModality.find("CR")  < strModality.length()) return CR;
321       else if ( strModality.find("CT")  < strModality.length()) return CT;
322       else if ( strModality.find("CS")  < strModality.length()) return CS;
323       else if ( strModality.find("DD")  < strModality.length()) return DD;
324       else if ( strModality.find("DF")  < strModality.length()) return DF;
325       else if ( strModality.find("DG")  < strModality.length()) return DG;
326       else if ( strModality.find("DM")  < strModality.length()) return DM;
327       else if ( strModality.find("DS")  < strModality.length()) return DS;
328       else if ( strModality.find("DX")  < strModality.length()) return DX;
329       else if ( strModality.find("ECG") < strModality.length()) return ECG;
330       else if ( strModality.find("EPS") < strModality.length()) return EPS;
331       else if ( strModality.find("FA")  < strModality.length()) return FA;
332       else if ( strModality.find("FS")  < strModality.length()) return FS;
333       else if ( strModality.find("HC")  < strModality.length()) return HC;
334       else if ( strModality.find("HD")  < strModality.length()) return HD;
335       else if ( strModality.find("LP")  < strModality.length()) return LP;
336       else if ( strModality.find("LS")  < strModality.length()) return LS;
337       else if ( strModality.find("MA")  < strModality.length()) return MA;
338       else if ( strModality.find("MR")  < strModality.length()) return MR;
339       else if ( strModality.find("NM")  < strModality.length()) return NM;
340       else if ( strModality.find("OT")  < strModality.length()) return OT;
341       else if ( strModality.find("PT")  < strModality.length()) return PT;
342       else if ( strModality.find("RF")  < strModality.length()) return RF;
343       else if ( strModality.find("RG")  < strModality.length()) return RG;
344       else if ( strModality.find("RTDOSE")   
345                                         < strModality.length()) return RTDOSE;
346       else if ( strModality.find("RTIMAGE")  
347                                         < strModality.length()) return RTIMAGE;
348       else if ( strModality.find("RTPLAN")
349                                         < strModality.length()) return RTPLAN;
350       else if ( strModality.find("RTSTRUCT") 
351                                         < strModality.length()) return RTSTRUCT;
352       else if ( strModality.find("SM")  < strModality.length()) return SM;
353       else if ( strModality.find("ST")  < strModality.length()) return ST;
354       else if ( strModality.find("TG")  < strModality.length()) return TG;
355       else if ( strModality.find("US")  < strModality.length()) return US;
356       else if ( strModality.find("VF")  < strModality.length()) return VF;
357       else if ( strModality.find("XA")  < strModality.length()) return XA;
358       else if ( strModality.find("XC")  < strModality.length()) return XC;
359
360       else
361       {
362          /// \todo throw error return value ???
363          /// specified <> unknown in our database
364          return Unknow;
365       }
366    }
367    return Unknow;
368 }
369
370 /**
371  * \brief   Retrieve the number of columns of image.
372  * @return  The encountered size when found, 0 by default.
373  *          0 means the file is NOT USABLE. The caller will have to check
374  */
375 int File::GetXSize()
376 {
377    const std::string &strSize = GetEntryValue(0x0028,0x0011);
378    if ( strSize == GDCM_UNFOUND )
379    {
380       return 0;
381    }
382    return atoi( strSize.c_str() );
383 }
384
385 /**
386  * \brief   Retrieve the number of lines of image.
387  * \warning The defaulted value is 1 as opposed to File::GetXSize()
388  * @return  The encountered size when found, 1 by default 
389  *          (The ACR-NEMA file contains a Signal, not an Image).
390  */
391 int File::GetYSize()
392 {
393    const std::string &strSize = GetEntryValue(0x0028,0x0010);
394    if ( strSize != GDCM_UNFOUND )
395    {
396       return atoi( strSize.c_str() );
397    }
398    if ( IsDicomV3() )
399    {
400       return 0;
401    }
402
403    // The Rows (0028,0010) entry was optional for ACR/NEMA.
404    // (at least some images didn't have it.)
405    // It might hence be a signal (1D image). So we default to 1:
406    return 1;
407 }
408
409 /**
410  * \brief   Retrieve the number of planes of volume or the number
411  *          of frames of a multiframe.
412  * \warning When present we consider the "Number of Frames" as the third
413  *          dimension. When missing we consider the third dimension as
414  *          being the ACR-NEMA "Planes" tag content.
415  * @return  The encountered size when found, 1 by default (single image).
416  */
417 int File::GetZSize()
418 {
419    // Both  DicomV3 and ACR/Nema consider the "Number of Frames"
420    // as the third dimension.
421    const std::string &strSize = GetEntryValue(0x0028,0x0008);
422    if ( strSize != GDCM_UNFOUND )
423    {
424       return atoi( strSize.c_str() );
425    }
426
427    // We then consider the "Planes" entry as the third dimension 
428    const std::string &strSize2 = GetEntryValue(0x0028,0x0012);
429    if ( strSize2 != GDCM_UNFOUND )
430    {
431       return atoi( strSize2.c_str() );
432    }
433    return 1;
434 }
435
436 /**
437   * \brief gets the info from 0018,1164 : ImagerPixelSpacing
438   *                      then 0028,0030 : Pixel Spacing
439   *             else 1.0
440   * @return X dimension of a pixel
441   */
442 float File::GetXSpacing()
443 {
444    float xspacing = 1.0;
445    float yspacing = 1.0;
446    int nbValues;
447
448    // To follow David Clunie's advice, we first check ImagerPixelSpacing
449
450    const std::string &strImagerPixelSpacing = GetEntryValue(0x0018,0x1164);
451    if ( strImagerPixelSpacing != GDCM_UNFOUND )
452    {
453       if ( ( nbValues = sscanf( strImagerPixelSpacing.c_str(), 
454             "%f\\%f", &yspacing, &xspacing)) != 2 )
455       {
456          // if no values, xspacing is set to 1.0
457          if ( nbValues == 0 )
458             xspacing = 1.0;
459          // if single value is found, xspacing is defaulted to yspacing
460          if ( nbValues == 1 )
461             xspacing = yspacing;
462
463          if ( xspacing == 0.0 )
464             xspacing = 1.0;
465
466          return xspacing;
467       }  
468    }
469
470    const std::string &strSpacing = GetEntryValue(0x0028,0x0030);
471
472    if ( strSpacing == GDCM_UNFOUND )
473    {
474       gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
475       return 1.;
476    }
477
478    if ( ( nbValues = sscanf( strSpacing.c_str(), 
479          "%f \\%f ", &yspacing, &xspacing)) != 2 )
480    {
481       // if no values, xspacing is set to 1.0
482       if ( nbValues == 0 )
483          xspacing = 1.0;
484       // if single value is found, xspacing is defaulted to yspacing
485       if ( nbValues == 1 )
486          xspacing = yspacing;
487
488       if ( xspacing == 0.0 )
489          xspacing = 1.0;
490
491       return xspacing;
492    }
493
494    // to avoid troubles with David Clunie's-like images (at least one)
495    if ( xspacing == 0. && yspacing == 0.)
496       return 1.;
497
498    if ( xspacing == 0.)
499    {
500       gdcmWarningMacro("gdcmData/CT-MONO2-8-abdo.dcm-like problem");
501       // seems to be a bug in the header ...
502       nbValues = sscanf( strSpacing.c_str(), "%f \\0\\%f ", &yspacing, &xspacing);
503       gdcmAssertMacro( nbValues == 2 );
504    }
505
506    return xspacing;
507 }
508
509 /**
510   * \brief gets the info from 0018,1164 : ImagerPixelSpacing
511   *               then from   0028,0030 : Pixel Spacing                         
512   *             else 1.0
513   * @return Y dimension of a pixel
514   */
515 float File::GetYSpacing()
516 {
517    float yspacing = 1.;
518    int nbValues;
519    // To follow David Clunie's advice, we first check ImagerPixelSpacing
520
521    const std::string &strImagerPixelSpacing = GetEntryValue(0x0018,0x1164);
522    if ( strImagerPixelSpacing != GDCM_UNFOUND )
523    {
524       nbValues = sscanf( strImagerPixelSpacing.c_str(), "%f", &yspacing);
525    
526    // if sscanf cannot read any float value, it won't affect yspacing
527       if ( nbValues == 0 )
528             yspacing = 1.0;
529
530       if ( yspacing == 0.0 )
531             yspacing = 1.0;
532
533       return yspacing;  
534    }
535
536    std::string strSpacing = GetEntryValue(0x0028,0x0030);  
537    if ( strSpacing == GDCM_UNFOUND )
538    {
539       gdcmWarningMacro("Unfound Pixel Spacing (0028,0030)");
540       return 1.;
541     }
542
543    // if sscanf cannot read any float value, it won't affect yspacing
544    nbValues = sscanf( strSpacing.c_str(), "%f", &yspacing);
545
546    // if no values, yspacing is set to 1.0
547    if ( nbValues == 0 )
548       yspacing = 1.0;
549
550    if ( yspacing == 0.0 )
551       yspacing = 1.0;
552
553    return yspacing;
554
555
556 /**
557  * \brief gets the info from 0018,0088 : Space Between Slices
558  *                 else from 0018,0050 : Slice Thickness
559  *                 else 1.0
560  * @return Z dimension of a voxel-to be
561  */
562 float File::GetZSpacing()
563 {
564    // Spacing Between Slices : distance between the middle of 2 slices
565    // Slices may be :
566    //   jointives     (Spacing between Slices = Slice Thickness)
567    //   overlapping   (Spacing between Slices < Slice Thickness)
568    //   disjointes    (Spacing between Slices > Slice Thickness)
569    // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
570    //   It only concerns the MRI guys, not people wanting to visualize volumes
571    //   If Spacing Between Slices is missing, 
572    //   we suppose slices joint together
573    
574    const std::string &strSpacingBSlices = GetEntryValue(0x0018,0x0088);
575
576    if ( strSpacingBSlices == GDCM_UNFOUND )
577    {
578       gdcmWarningMacro("Unfound Spacing Between Slices (0018,0088)");
579       const std::string &strSliceThickness = GetEntryValue(0x0018,0x0050);       
580       if ( strSliceThickness == GDCM_UNFOUND )
581       {
582          gdcmWarningMacro("Unfound Slice Thickness (0018,0050)");
583          return 1.;
584       }
585       else
586       {
587          // if no 'Spacing Between Slices' is found, 
588          // we assume slices join together
589          // (no overlapping, no interslice gap)
590          // if they don't, we're fucked up
591          return (float)atof( strSliceThickness.c_str() );
592       }
593    }
594    //else
595    return (float)atof( strSpacingBSlices.c_str() );
596 }
597
598 /**
599  * \brief gets the info from 0020,0032 : Image Position Patient
600  *                 else from 0020,0030 : Image Position (RET)
601  *                 else 0.
602  * @return up-left image corner X position
603  */
604 float File::GetXOrigin()
605 {
606    float xImPos, yImPos, zImPos;  
607    std::string strImPos = GetEntryValue(0x0020,0x0032);
608
609    if ( strImPos == GDCM_UNFOUND )
610    {
611       gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
612       strImPos = GetEntryValue(0x0020,0x0030); // For ACR-NEMA images
613       if ( strImPos == GDCM_UNFOUND )
614       {
615          gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
616          return 0.;
617       }
618    }
619
620    if ( sscanf( strImPos.c_str(), "%f \\%f \\%f ", &xImPos, &yImPos, &zImPos) != 3 )
621    {
622       return 0.;
623    }
624
625    return xImPos;
626 }
627
628 /**
629  * \brief gets the info from 0020,0032 : Image Position Patient
630  *                 else from 0020,0030 : Image Position (RET)
631  *                 else 0.
632  * @return up-left image corner Y position
633  */
634 float File::GetYOrigin()
635 {
636    float xImPos, yImPos, zImPos;
637    std::string strImPos = GetEntryValue(0x0020,0x0032);
638
639    if ( strImPos == GDCM_UNFOUND)
640    {
641       gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
642       strImPos = GetEntryValue(0x0020,0x0030); // For ACR-NEMA images
643       if ( strImPos == GDCM_UNFOUND )
644       {
645          gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
646          return 0.;
647       }  
648    }
649
650    if ( sscanf( strImPos.c_str(), "%f \\%f \\%f ", &xImPos, &yImPos, &zImPos) != 3 )
651    {
652       return 0.;
653    }
654
655    return yImPos;
656 }
657
658 /**
659  * \brief gets the info from 0020,0032 : Image Position Patient
660  *                 else from 0020,0030 : Image Position (RET)
661  *                 else from 0020,1041 : Slice Location
662  *                 else from 0020,0050 : Location
663  *                 else 0.
664  * @return up-left image corner Z position
665  */
666 float File::GetZOrigin()
667 {
668    float xImPos, yImPos, zImPos; 
669    std::string strImPos = GetEntryValue(0x0020,0x0032);
670
671    if ( strImPos != GDCM_UNFOUND )
672    {
673       if ( sscanf( strImPos.c_str(), "%f \\%f \\%f ", &xImPos, &yImPos, &zImPos) != 3)
674       {
675          gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)");
676          return 0.;  // bug in the element 0x0020,0x0032
677       }
678       else
679       {
680          return zImPos;
681       }
682    }
683
684    strImPos = GetEntryValue(0x0020,0x0030); // For ACR-NEMA images
685    if ( strImPos != GDCM_UNFOUND )
686    {
687       if ( sscanf( strImPos.c_str(), 
688           "%f \\%f \\%f ", &xImPos, &yImPos, &zImPos ) != 3 )
689       {
690          gdcmWarningMacro( "Wrong Image Position (RET) (0020,0030)");
691          return 0.;  // bug in the element 0x0020,0x0032
692       }
693       else
694       {
695          return zImPos;
696       }
697    }
698
699    // for *very* old ACR-NEMA images
700    std::string strSliceLocation = GetEntryValue(0x0020,0x1041);
701    if ( strSliceLocation != GDCM_UNFOUND )
702    {
703       if ( sscanf( strSliceLocation.c_str(), "%f ", &zImPos) != 1)
704       {
705          gdcmWarningMacro( "Wrong Slice Location (0020,1041)");
706          return 0.;  // bug in the element 0x0020,0x1041
707       }
708       else
709       {
710          return zImPos;
711       }
712    }
713    gdcmWarningMacro( "Unfound Slice Location (0020,1041)");
714
715    std::string strLocation = GetEntryValue(0x0020,0x0050);
716    if ( strLocation != GDCM_UNFOUND )
717    {
718       if ( sscanf( strLocation.c_str(), "%f ", &zImPos) != 1 )
719       {
720          gdcmWarningMacro( "Wrong Location (0020,0050)");
721          return 0.;  // bug in the element 0x0020,0x0050
722       }
723       else
724       {
725          return zImPos;
726       }
727    }
728    gdcmWarningMacro( "Unfound Location (0020,0050)");  
729
730    return 0.; // Hopeless
731 }
732
733 /**
734   * \brief gets the info from 0020,0037 : Image Orientation Patient
735   * (needed to organize DICOM files based on their x,y,z position)
736   * @param iop adress of the (6)float array to receive values
737   * @return cosines of image orientation patient
738   */
739 bool File::GetImageOrientationPatient( float iop[6] )
740 {
741    std::string strImOriPat;
742    //iop is supposed to be float[6]
743    iop[0] = iop[1] = iop[2] = iop[3] = iop[4] = iop[5] = 0.;
744
745    // 0020 0037 DS REL Image Orientation (Patient)
746    if ( (strImOriPat = GetEntryValue(0x0020,0x0037)) != GDCM_UNFOUND )
747    {
748       if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ", 
749           &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
750       {
751          gdcmWarningMacro( "Wrong Image Orientation Patient (0020,0037). Less than 6 values were found." );
752          return false;
753       }
754    }
755    //For ACR-NEMA
756    // 0020 0035 DS REL Image Orientation (RET)
757    else if ( (strImOriPat = GetEntryValue(0x0020,0x0035)) != GDCM_UNFOUND )
758    {
759       if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ", 
760           &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
761       {
762          gdcmWarningMacro( "wrong Image Orientation Patient (0020,0035). Less than 6 values were found." );
763          return false;
764       }
765    }
766    return true;
767 }
768
769 /**
770  * \brief   Retrieve the number of Bits Stored (actually used)
771  *          (as opposed to number of Bits Allocated)
772  * @return  The encountered number of Bits Stored, 0 by default.
773  *          0 means the file is NOT USABLE. The caller has to check it !
774  */
775 int File::GetBitsStored()
776 {
777    std::string strSize = GetEntryValue( 0x0028, 0x0101 );
778    if ( strSize == GDCM_UNFOUND )
779    {
780       gdcmWarningMacro("(0028,0101) is supposed to be mandatory");
781       return 0;  // It's supposed to be mandatory
782                  // the caller will have to check
783    }
784    return atoi( strSize.c_str() );
785 }
786
787 /**
788  * \brief   Retrieve the number of Bits Allocated
789  *          (8, 12 -compacted ACR-NEMA files-, 16, ...)
790  * @return  The encountered number of Bits Allocated, 0 by default.
791  *          0 means the file is NOT USABLE. The caller has to check it !
792  */
793 int File::GetBitsAllocated()
794 {
795    std::string strSize = GetEntryValue(0x0028,0x0100);
796    if ( strSize == GDCM_UNFOUND  )
797    {
798       gdcmWarningMacro( "(0028,0100) is supposed to be mandatory");
799       return 0; // It's supposed to be mandatory
800                 // the caller will have to check
801    }
802    return atoi( strSize.c_str() );
803 }
804
805 /**
806  * \brief   Retrieve the high bit position.
807  * \warning The method defaults to 0 when information is missing.
808  *          The responsability of checking this value is left to the caller.
809  * @return  The high bit position when present. 0 when missing.
810  */
811 int File::GetHighBitPosition()
812 {
813    std::string strSize = GetEntryValue( 0x0028, 0x0102 );
814    if ( strSize == GDCM_UNFOUND )
815    {
816       gdcmWarningMacro( "(0028,0102) is supposed to be mandatory");
817       return 0;
818    }
819    return atoi( strSize.c_str() );
820 }
821
822 /**
823  * \brief   Retrieve the number of Samples Per Pixel
824  *          (1 : gray level, 3 : RGB/YBR -1 or 3 Planes-)
825  * @return  The encountered number of Samples Per Pixel, 1 by default.
826  *          (we assume Gray level Pixels)
827  */
828 int File::GetSamplesPerPixel()
829 {
830    const std::string &strSize = GetEntryValue(0x0028,0x0002);
831    if ( strSize == GDCM_UNFOUND )
832    {
833       gdcmWarningMacro( "(0028,0002) is supposed to be mandatory");
834       return 1; // Well, it's supposed to be mandatory ...
835                 // but sometimes it's missing : *we* assume Gray pixels
836    }
837    return atoi( strSize.c_str() );
838 }
839
840 /**
841  * \brief   Retrieve the Planar Configuration for RGB images
842  *          (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
843  * @return  The encountered Planar Configuration, 0 by default.
844  */
845 int File::GetPlanarConfiguration()
846 {
847    std::string strSize = GetEntryValue(0x0028,0x0006);
848    if ( strSize == GDCM_UNFOUND )
849    {
850       gdcmWarningMacro( "Not found : Planar Configuration (0028,0006)");
851       return 0;
852    }
853    return atoi( strSize.c_str() );
854 }
855
856 /**
857  * \brief   Return the size (in bytes) of a single pixel of data.
858  * @return  The size in bytes of a single pixel of data; 0 by default
859  *          0 means the file is NOT USABLE; the caller will have to check
860  */
861 int File::GetPixelSize()
862 {
863    // 0028 0100 US IMG Bits Allocated
864    // (in order no to be messed up by old ACR-NEMA RGB images)
865    //   if (File::GetEntryValue(0x0028,0x0100) == "24")
866    //      return 3;
867
868    std::string pixelType = GetPixelType();
869    if ( pixelType ==  "8U" || pixelType == "8S" )
870    {
871       return 1;
872    }
873    if ( pixelType == "16U" || pixelType == "16S")
874    {
875       return 2;
876    }
877    if ( pixelType == "32U" || pixelType == "32S")
878    {
879       return 4;
880    }
881    if ( pixelType == "FD" )
882    {
883       return 8;
884    }
885    gdcmWarningMacro( "Unknown pixel type");
886    return 0;
887 }
888
889 /**
890  * \brief   Build the Pixel Type of the image.
891  *          Possible values are:
892  *          - 8U  unsigned  8 bit,
893  *          - 8S    signed  8 bit,
894  *          - 16U unsigned 16 bit,
895  *          - 16S   signed 16 bit,
896  *          - 32U unsigned 32 bit,
897  *          - 32S   signed 32 bit,
898  *          - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
899  * \warning 12 bit images appear as 16 bit.
900  *          24 bit images appear as 8 bit + photochromatic interp ="RGB "
901  *                                        + Planar Configuration = 0
902  * @return  0S if nothing found. NOT USABLE file. The caller has to check
903  */
904 std::string File::GetPixelType()
905 {
906    std::string bitsAlloc = GetEntryValue(0x0028, 0x0100); // Bits Allocated
907    if ( bitsAlloc == GDCM_UNFOUND )
908    {
909       gdcmWarningMacro( "Missing  Bits Allocated (0028,0100)");
910       bitsAlloc = "16"; // default and arbitrary value, not to polute the output
911    }
912
913    if ( bitsAlloc == "64" )
914    {
915       return "FD";
916    }
917    else if ( bitsAlloc == "12" )
918    {
919       // It will be unpacked
920       bitsAlloc = "16";
921    }
922    else if ( bitsAlloc == "24" )
923    {
924       // (in order no to be messed up
925       bitsAlloc = "8";  // by old RGB images)
926    }
927
928    std::string sign = GetEntryValue(0x0028, 0x0103);//"Pixel Representation"
929
930    if (sign == GDCM_UNFOUND )
931    {
932       gdcmWarningMacro( "Missing Pixel Representation (0028,0103)");
933       sign = "U"; // default and arbitrary value, not to polute the output
934    }
935    else if ( sign == "0" )
936    {
937       sign = "U";
938    }
939    else
940    {
941       sign = "S";
942    }
943    return bitsAlloc + sign;
944 }
945
946 /**
947  * \brief   Check whether the pixels are signed (1) or UNsigned (0) data.
948  * \warning The method defaults to false (UNsigned) when tag 0028|0103
949  *          is missing.
950  *          The responsability of checking this value is left to the caller
951  *          (NO transformation is performed on the pixels to make then >0)
952  * @return  True when signed, false when UNsigned
953  */
954 bool File::IsSignedPixelData()
955 {
956    std::string strSign = GetEntryValue( 0x0028, 0x0103 );
957    if ( strSign == GDCM_UNFOUND )
958    {
959       gdcmWarningMacro( "(0028,0103) is supposed to be mandatory");
960       return false;
961    }
962    int sign = atoi( strSign.c_str() );
963    if ( sign == 0 ) 
964    {
965       return false;
966    }
967    return true;
968 }
969
970 /**
971  * \brief   Check whether this a monochrome picture (gray levels) or not,
972  *          using "Photometric Interpretation" tag (0x0028,0x0004).
973  * @return  true when "MONOCHROME1" or "MONOCHROME2". False otherwise.
974  */
975 bool File::IsMonochrome()
976 {
977    const std::string &PhotometricInterp = GetEntryValue( 0x0028, 0x0004 );
978    if (  Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1")
979       || Util::DicomStringEqual(PhotometricInterp, "MONOCHROME2") )
980    {
981       return true;
982    }
983    if ( PhotometricInterp == GDCM_UNFOUND )
984    {
985       gdcmWarningMacro( "Not found : Photometric Interpretation (0028,0004)");
986    }
987    return false;
988 }
989
990 /**
991  * \brief   Check whether this a MONOCHROME1 picture (high values = dark)
992  *            or not using "Photometric Interpretation" tag (0x0028,0x0004).
993  * @return  true when "MONOCHROME1" . False otherwise.
994  */
995 bool File::IsMonochrome1()
996 {
997    const std::string &PhotometricInterp = GetEntryValue( 0x0028, 0x0004 );
998    if (  Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1") )
999    {
1000       return true;
1001    }
1002    if ( PhotometricInterp == GDCM_UNFOUND )
1003    {
1004       gdcmWarningMacro( "Not found : Photometric Interpretation (0028,0004)");
1005    }
1006    return false;
1007 }
1008
1009 /**
1010  * \brief   Check whether this a "PALETTE COLOR" picture or not by accessing
1011  *          the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1012  * @return  true when "PALETTE COLOR". False otherwise.
1013  */
1014 bool File::IsPaletteColor()
1015 {
1016    std::string PhotometricInterp = GetEntryValue( 0x0028, 0x0004 );
1017    if (   PhotometricInterp == "PALETTE COLOR " )
1018    {
1019       return true;
1020    }
1021    if ( PhotometricInterp == GDCM_UNFOUND )
1022    {
1023       gdcmWarningMacro( "Not found : Palette color (0028,0004)");
1024    }
1025    return false;
1026 }
1027
1028 /**
1029  * \brief   Check whether this a "YBR_FULL" color picture or not by accessing
1030  *          the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1031  * @return  true when "YBR_FULL". False otherwise.
1032  */
1033 bool File::IsYBRFull()
1034 {
1035    std::string PhotometricInterp = GetEntryValue( 0x0028, 0x0004 );
1036    if (   PhotometricInterp == "YBR_FULL" )
1037    {
1038       return true;
1039    }
1040    if ( PhotometricInterp == GDCM_UNFOUND )
1041    {
1042       gdcmWarningMacro( "Not found : YBR Full (0028,0004)");
1043    }
1044    return false;
1045 }
1046
1047 /**
1048   * \brief tells us if LUT are used
1049   * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
1050   *          are NOT considered as LUT, since nobody knows
1051   *          how to deal with them
1052   *          Please warn me if you know sbdy that *does* know ... jprx
1053   * @return true if LUT Descriptors and LUT Tables were found 
1054   */
1055 bool File::HasLUT()
1056 {
1057    // Check the presence of the LUT Descriptors, and LUT Tables    
1058    // LutDescriptorRed    
1059    if ( !GetDocEntry(0x0028,0x1101) )
1060    {
1061       return false;
1062    }
1063    // LutDescriptorGreen 
1064    if ( !GetDocEntry(0x0028,0x1102) )
1065    {
1066       return false;
1067    }
1068    // LutDescriptorBlue 
1069    if ( !GetDocEntry(0x0028,0x1103) )
1070    {
1071       return false;
1072    }
1073    // Red Palette Color Lookup Table Data
1074    if ( !GetDocEntry(0x0028,0x1201) )
1075    {
1076       return false;
1077    }
1078    // Green Palette Color Lookup Table Data       
1079    if ( !GetDocEntry(0x0028,0x1202) )
1080    {
1081       return false;
1082    }
1083    // Blue Palette Color Lookup Table Data      
1084    if ( !GetDocEntry(0x0028,0x1203) )
1085    {
1086       return false;
1087    }
1088
1089    // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
1090    //         NOT taken into account, but we don't know how to use it ...   
1091    return true;
1092 }
1093
1094 /**
1095   * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
1096   *             else 0
1097   * @return Lookup Table number of Bits , 0 by default
1098   *          when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
1099   * @ return bit number of each LUT item 
1100   */
1101 int File::GetLUTNbits()
1102 {
1103    std::vector<std::string> tokens;
1104    int lutNbits;
1105
1106    //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red
1107    //                                = Lookup Table Desc-Blue
1108    // Consistency already checked in GetLUTLength
1109    std::string lutDescription = GetEntryValue(0x0028,0x1101);
1110    if ( lutDescription == GDCM_UNFOUND )
1111    {
1112       return 0;
1113    }
1114
1115    tokens.clear(); // clean any previous value
1116    Util::Tokenize ( lutDescription, tokens, "\\" );
1117    //LutLength=atoi(tokens[0].c_str());
1118    //LutDepth=atoi(tokens[1].c_str());
1119
1120    lutNbits = atoi( tokens[2].c_str() );
1121    tokens.clear();
1122
1123    return lutNbits;
1124 }
1125
1126 /**
1127  *\brief gets the info from 0028,1052 : Rescale Intercept
1128  * @return Rescale Intercept
1129  */
1130 float File::GetRescaleIntercept()
1131 {
1132    float resInter = 0.;
1133    /// 0028 1052 DS IMG Rescale Intercept
1134    const std::string &strRescInter = GetEntryValue(0x0028,0x1052);
1135    if ( strRescInter != GDCM_UNFOUND )
1136    {
1137       if ( sscanf( strRescInter.c_str(), "%f ", &resInter) != 1 )
1138       {
1139          // bug in the element 0x0028,0x1052
1140          gdcmWarningMacro( "Rescale Intercept (0028,1052) is empty." );
1141       }
1142    }
1143
1144    return resInter;
1145 }
1146
1147 /**
1148  *\brief   gets the info from 0028,1053 : Rescale Slope
1149  * @return Rescale Slope
1150  */
1151 float File::GetRescaleSlope()
1152 {
1153    float resSlope = 1.;
1154    //0028 1053 DS IMG Rescale Slope
1155    std::string strRescSlope = GetEntryValue(0x0028,0x1053);
1156    if ( strRescSlope != GDCM_UNFOUND )
1157    {
1158       if ( sscanf( strRescSlope.c_str(), "%f ", &resSlope) != 1 )
1159       {
1160          // bug in the element 0x0028,0x1053
1161          gdcmWarningMacro( "Rescale Slope (0028,1053) is empty.");
1162       }
1163    }
1164
1165    return resSlope;
1166 }
1167
1168 /**
1169  * \brief This function is intended to user who doesn't want 
1170  *   to have to manage a LUT and expects to get an RBG Pixel image
1171  *   (or a monochrome one ...) 
1172  * \warning to be used with GetImagePixels()
1173  * @return 1 if Gray level, 3 if Color (RGB, YBR, *or PALETTE COLOR*)
1174  */
1175 int File::GetNumberOfScalarComponents()
1176 {
1177    if ( GetSamplesPerPixel() == 3 )
1178    {
1179       return 3;
1180    }
1181       
1182    // 0028 0100 US IMG Bits Allocated
1183    // (in order no to be messed up by old RGB images)
1184    if ( GetEntryValue(0x0028,0x0100) == "24" )
1185    {
1186       return 3;
1187    }
1188        
1189    std::string strPhotometricInterpretation = GetEntryValue(0x0028,0x0004);
1190
1191    if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
1192    {
1193       if ( HasLUT() )// PALETTE COLOR is NOT enough
1194       {
1195          return 3;
1196       }
1197       else
1198       {
1199          return 1;
1200       }
1201    }
1202
1203    // beware of trailing space at end of string      
1204    // DICOM tags are never of odd length
1205    if ( strPhotometricInterpretation == GDCM_UNFOUND   || 
1206         Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
1207         Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
1208    {
1209       return 1;
1210    }
1211    else
1212    {
1213       // we assume that *all* kinds of YBR are dealt with
1214       return 3;
1215    }
1216 }
1217
1218 /**
1219  * \brief This function is intended to user that DOESN'T want 
1220  *  to get RGB pixels image when it's stored as a PALETTE COLOR image
1221  *   - the (vtk) user is supposed to know how deal with LUTs - 
1222  * \warning to be used with GetImagePixelsRaw()
1223  * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
1224  */
1225 int File::GetNumberOfScalarComponentsRaw()
1226 {
1227    // 0028 0100 US IMG Bits Allocated
1228    // (in order no to be messed up by old RGB images)
1229    if ( File::GetEntryValue(0x0028,0x0100) == "24" )
1230    {
1231       return 3;
1232    }
1233
1234    // we assume that *all* kinds of YBR are dealt with
1235    return GetSamplesPerPixel();
1236 }
1237
1238 /**
1239  * \brief   Recover the offset (from the beginning of the file) 
1240  *          of *image* pixels (not *icone image* pixels, if any !)
1241  * @return Pixel Offset
1242  */
1243 size_t File::GetPixelOffset()
1244 {
1245    DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1246    if ( pxlElement )
1247    {
1248       return pxlElement->GetOffset();
1249    }
1250    else
1251    {
1252       gdcmDebugMacro( "Big trouble : Pixel Element ("
1253                       << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1254       return 0;
1255    }
1256 }
1257
1258 /**
1259  * \brief   Recover the pixel area length (in Bytes)
1260  * @return Pixel Element Length, as stored in the header
1261  *         (NOT the memory space necessary to hold the Pixels 
1262  *          -in case of embeded compressed image-)
1263  *         0 : NOT USABLE file. The caller has to check.
1264  */
1265 size_t File::GetPixelAreaLength()
1266 {
1267    DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1268    if ( pxlElement )
1269    {
1270       return pxlElement->GetLength();
1271    }
1272    else
1273    {
1274       gdcmDebugMacro( "Big trouble : Pixel Element ("
1275                       << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1276       return 0;
1277    }
1278 }
1279
1280 /**
1281  * \brief Adds the characteristics of a new element we want to anonymize
1282  * @param   group  Group number of the target tag.
1283  * @param   elem Element number of the target tag.
1284  * @param   value new value (string) to substitute with 
1285  */
1286 void File::AddAnonymizeElement (uint16_t group, uint16_t elem, 
1287                                 std::string const &value) 
1288
1289    Element el;
1290    el.Group = group;
1291    el.Elem  = elem;
1292    el.Value = value;
1293    AnonymizeList.push_back(el); 
1294 }
1295
1296 /**
1297  * \brief Overwrites in the file the values of the DicomElements
1298  *       held in the list 
1299  */
1300 void File::AnonymizeNoLoad()
1301 {
1302    std::fstream *fp = new std::fstream(Filename.c_str(), 
1303                               std::ios::in | std::ios::out | std::ios::binary); 
1304    gdcm::DocEntry *d;
1305    uint32_t offset;
1306    uint32_t lgth;
1307    uint32_t valLgth = 0;
1308    std::string *spaces;
1309    for (ListElements::iterator it = AnonymizeList.begin();  
1310                                it != AnonymizeList.end();
1311                              ++it)
1312    { 
1313       d = GetDocEntry( (*it).Group, (*it).Elem);
1314
1315       if ( d == NULL)
1316          continue;
1317
1318          if ( dynamic_cast<SeqEntry *>(d) )
1319          {
1320             gdcmWarningMacro( "You cannot 'Anonymize a SeqEntry ");
1321             continue;
1322          }
1323
1324       offset = d->GetOffset();
1325       lgth =   d->GetLength();
1326       if (valLgth < lgth)
1327       {
1328          spaces = new std::string( lgth-valLgth, ' ');
1329          (*it).Value = (*it).Value + *spaces;
1330          delete spaces;
1331       }
1332       fp->seekp( offset, std::ios::beg );
1333       fp->write( (*it).Value.c_str(), lgth );
1334      
1335    }
1336    fp->close();
1337    delete fp;
1338 }
1339
1340 /**
1341  * \brief anonymize a File (remove Patient's personal info passed with
1342  *        AddAnonymizeElement()
1343  * \note You cannot Anonymize a BinEntry (to be fixed)
1344  */
1345 bool File::AnonymizeFile()
1346 {
1347    // If Anonymisation list is empty, let's perform some basic anonymization
1348    if ( AnonymizeList.begin() == AnonymizeList.end() )
1349    {
1350       // If exist, replace by spaces
1351       SetValEntry ("  ",0x0010, 0x2154); // Telephone   
1352       SetValEntry ("  ",0x0010, 0x1040); // Adress
1353       SetValEntry ("  ",0x0010, 0x0020); // Patient ID
1354
1355       DocEntry* patientNameHE = GetDocEntry (0x0010, 0x0010);
1356   
1357       if ( patientNameHE ) // we replace it by Study Instance UID (why not ?)
1358       {
1359          std::string studyInstanceUID =  GetEntryValue (0x0020, 0x000d);
1360          if ( studyInstanceUID != GDCM_UNFOUND )
1361          {
1362             SetValEntry(studyInstanceUID, 0x0010, 0x0010);
1363          }
1364          else
1365          {
1366             SetValEntry("anonymised", 0x0010, 0x0010);
1367          }
1368       }
1369    }
1370    else
1371    {
1372       gdcm::DocEntry *d;
1373       for (ListElements::iterator it = AnonymizeList.begin();  
1374                                   it != AnonymizeList.end();
1375                                 ++it)
1376       {  
1377          d = GetDocEntry( (*it).Group, (*it).Elem);
1378
1379          if ( d == NULL)
1380             continue;
1381
1382          if ( dynamic_cast<SeqEntry *>(d) )
1383          {
1384             gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1385             continue;
1386          }
1387
1388          if ( dynamic_cast<BinEntry *>(d) )
1389          {
1390             gdcmWarningMacro( "To 'Anonymize' a BinEntry, better use AnonymizeNoLoad (FIXME) ");
1391             continue;
1392          }
1393          else
1394             SetValEntry ((*it).Value, (*it).Group, (*it).Elem);
1395       }
1396 }
1397
1398   // In order to make definitively impossible any further identification
1399   // remove or replace all the stuff that contains a Date
1400
1401 //0008 0012 DA ID Instance Creation Date
1402 //0008 0020 DA ID Study Date
1403 //0008 0021 DA ID Series Date
1404 //0008 0022 DA ID Acquisition Date
1405 //0008 0023 DA ID Content Date
1406 //0008 0024 DA ID Overlay Date
1407 //0008 0025 DA ID Curve Date
1408 //0008 002a DT ID Acquisition Datetime
1409 //0018 9074 DT ACQ Frame Acquisition Datetime
1410 //0018 9151 DT ACQ Frame Reference Datetime
1411 //0018 a002 DT ACQ Contribution Date Time
1412 //0020 3403 SH REL Modified Image Date (RET)
1413 //0032 0032 DA SDY Study Verified Date
1414 //0032 0034 DA SDY Study Read Date
1415 //0032 1000 DA SDY Scheduled Study Start Date
1416 //0032 1010 DA SDY Scheduled Study Stop Date
1417 //0032 1040 DA SDY Study Arrival Date
1418 //0032 1050 DA SDY Study Completion Date
1419 //0038 001a DA VIS Scheduled Admission Date
1420 //0038 001c DA VIS Scheduled Discharge Date
1421 //0038 0020 DA VIS Admitting Date
1422 //0038 0030 DA VIS Discharge Date
1423 //0040 0002 DA PRC Scheduled Procedure Step Start Date
1424 //0040 0004 DA PRC Scheduled Procedure Step End Date
1425 //0040 0244 DA PRC Performed Procedure Step Start Date
1426 //0040 0250 DA PRC Performed Procedure Step End Date
1427 //0040 2004 DA PRC Issue Date of Imaging Service Request
1428 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
1429 //0040 4011 DT PRC Expected Completion Date and Time
1430 //0040 a030 DT PRC Verification Date Time
1431 //0040 a032 DT PRC Observation Date Time
1432 //0040 a120 DT PRC DateTime
1433 //0040 a121 DA PRC Date
1434 //0040 a13a DT PRC Referenced Datetime
1435 //0070 0082 DA ??? Presentation Creation Date
1436 //0100 0420 DT ??? SOP Autorization Date and Time
1437 //0400 0105 DT ??? Digital Signature DateTime
1438 //2100 0040 DA PJ Creation Date
1439 //3006 0008 DA SSET Structure Set Date
1440 //3008 0024 DA ??? Treatment Control Point Date
1441 //3008 0054 DA ??? First Treatment Date
1442 //3008 0056 DA ??? Most Recent Treatment Date
1443 //3008 0162 DA ??? Safe Position Exit Date
1444 //3008 0166 DA ??? Safe Position Return Date
1445 //3008 0250 DA ??? Treatment Date
1446 //300a 0006 DA RT RT Plan Date
1447 //300a 022c DA RT Air Kerma Rate Reference Date
1448 //300e 0004 DA RT Review Date
1449
1450    return true;
1451 }
1452
1453 /**
1454  * \brief Performs some consistency checking on various 'File related' 
1455  *       (as opposed to 'DicomDir related') entries 
1456  *       then writes in a file all the (Dicom Elements) included the Pixels 
1457  * @param fileName file name to write to
1458  * @param writetype type of the file to be written 
1459  *          (ACR, ExplicitVR, ImplicitVR)
1460  */
1461 bool File::Write(std::string fileName, FileType writetype)
1462 {
1463    std::ofstream *fp = new std::ofstream(fileName.c_str(), 
1464                                          std::ios::out | std::ios::binary);
1465    if (*fp == NULL)
1466    {
1467       gdcmWarningMacro("Failed to open (write) File: " << fileName.c_str());
1468       return false;
1469    }
1470
1471    // Entry : 0002|0000 = group length -> recalculated
1472    ValEntry*e0000 = GetValEntry(0x0002,0x0000);
1473    if ( e0000 )
1474    {
1475       std::ostringstream sLen;
1476       sLen << ComputeGroup0002Length(writetype);
1477       e0000->SetValue(sLen.str());
1478    }
1479
1480    int i_lgPix = GetEntryLength(GrPixel, NumPixel);
1481    if (i_lgPix != -2)
1482    {
1483       // no (GrPixel, NumPixel) element
1484       std::string s_lgPix = Util::Format("%d", i_lgPix+12);
1485       s_lgPix = Util::DicomString( s_lgPix.c_str() );
1486       InsertValEntry(s_lgPix,GrPixel, 0x0000);
1487    }
1488
1489    Document::WriteContent(fp, writetype);
1490
1491    fp->close();
1492    delete fp;
1493
1494    return true;
1495 }
1496
1497 //-----------------------------------------------------------------------------
1498 // Protected
1499
1500
1501 //-----------------------------------------------------------------------------
1502 // Private
1503 /**
1504  * \brief Parse pixel data from disk of [multi-]fragment RLE encoding.
1505  *        Compute the RLE extra information and store it in \ref RLEInfo
1506  *        for later pixel retrieval usage.
1507  */
1508 void File::ComputeRLEInfo()
1509 {
1510    std::string ts = GetTransferSyntax();
1511    if ( !Global::GetTS()->IsRLELossless(ts) ) 
1512    {
1513       return;
1514    }
1515
1516    // Encoded pixel data: for the time being we are only concerned with
1517    // Jpeg or RLE Pixel data encodings.
1518    // As stated in PS 3.5-2003, section 8.2 p44:
1519    // "If sent in Encapsulated Format (i.e. other than the Native Format) the
1520    //  value representation OB is used".
1521    // Hence we expect an OB value representation. Concerning OB VR,
1522    // the section PS 3.5-2003, section A.4.c p 58-59, states:
1523    // "For the Value Representations OB and OW, the encoding shall meet the
1524    //   following specifications depending on the Data element tag:"
1525    //   [...snip...]
1526    //    - the first item in the sequence of items before the encoded pixel
1527    //      data stream shall be basic offset table item. The basic offset table
1528    //      item value, however, is not required to be present"
1529    ReadAndSkipEncapsulatedBasicOffsetTable();
1530
1531    // Encapsulated RLE Compressed Images (see PS 3.5-2003, Annex G)
1532    // Loop on the individual frame[s] and store the information
1533    // on the RLE fragments in a RLEFramesInfo.
1534    // Note: - when only a single frame is present, this is a
1535    //         classical image.
1536    //       - when more than one frame are present, then we are in 
1537    //         the case of a multi-frame image.
1538    long frameLength;
1539    while ( (frameLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1540    { 
1541       // Parse the RLE Header and store the corresponding RLE Segment
1542       // Offset Table information on fragments of this current Frame.
1543       // Note that the fragment pixels themselves are not loaded
1544       // (but just skipped).
1545       long frameOffset = Fp->tellg();
1546
1547       uint32_t nbRleSegments = ReadInt32();
1548       if ( nbRleSegments > 16 )
1549       {
1550          // There should be at most 15 segments (refer to RLEFrame class)
1551          gdcmWarningMacro( "Too many segments.");
1552       }
1553  
1554       uint32_t rleSegmentOffsetTable[16];
1555       for( int k = 1; k <= 15; k++ )
1556       {
1557          rleSegmentOffsetTable[k] = ReadInt32();
1558       }
1559
1560       // Deduce from both RLE Header and frameLength 
1561       // the fragment length, and again store this info
1562       // in a RLEFramesInfo.
1563       long rleSegmentLength[15];
1564       // skipping (not reading) RLE Segments
1565       if ( nbRleSegments > 1)
1566       {
1567          for(unsigned int k = 1; k <= nbRleSegments-1; k++)
1568          {
1569              rleSegmentLength[k] =  rleSegmentOffsetTable[k+1]
1570                                   - rleSegmentOffsetTable[k];
1571              SkipBytes(rleSegmentLength[k]);
1572           }
1573        }
1574
1575        rleSegmentLength[nbRleSegments] = frameLength 
1576                                       - rleSegmentOffsetTable[nbRleSegments];
1577        SkipBytes(rleSegmentLength[nbRleSegments]);
1578
1579        // Store the collected info
1580        RLEFrame *newFrame = new RLEFrame;
1581        newFrame->SetNumberOfFragments(nbRleSegments);
1582        for( unsigned int uk = 1; uk <= nbRleSegments; uk++ )
1583        {
1584           newFrame->SetOffset(uk,frameOffset + rleSegmentOffsetTable[uk]);
1585           newFrame->SetLength(uk,rleSegmentLength[uk]);
1586        }
1587        RLEInfo->AddFrame(newFrame);
1588    }
1589
1590    // Make sure that  we encounter a 'Sequence Delimiter Item'
1591    // at the end of the item :
1592    if ( !ReadTag(0xfffe, 0xe0dd) )
1593    {
1594       gdcmWarningMacro( "No sequence delimiter item at end of RLE item sequence");
1595    }
1596 }
1597
1598 /**
1599  * \brief Parse pixel data from disk of [multi-]fragment Jpeg encoding.
1600  *        Compute the jpeg extra information (fragment[s] offset[s] and
1601  *        length) and store it[them] in \ref JPEGInfo for later pixel
1602  *        retrieval usage.
1603  */
1604 void File::ComputeJPEGFragmentInfo()
1605 {
1606    // If you need to, look for comments of ComputeRLEInfo().
1607    std::string ts = GetTransferSyntax();
1608    if ( ! Global::GetTS()->IsJPEG(ts) )
1609    {
1610       return;
1611    }
1612
1613    ReadAndSkipEncapsulatedBasicOffsetTable();
1614
1615    // Loop on the fragments[s] and store the parsed information in a
1616    // JPEGInfo.
1617    long fragmentLength;
1618    while ( (fragmentLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1619    { 
1620       long fragmentOffset = Fp->tellg();
1621
1622        // Store the collected info
1623        JPEGFragment *newFragment = new JPEGFragment;
1624        newFragment->SetOffset(fragmentOffset);
1625        newFragment->SetLength(fragmentLength);
1626        JPEGInfo->AddFragment(newFragment);
1627
1628        SkipBytes(fragmentLength);
1629    }
1630
1631    // Make sure that  we encounter a 'Sequence Delimiter Item'
1632    // at the end of the item :
1633    if ( !ReadTag(0xfffe, 0xe0dd) )
1634    {
1635       gdcmWarningMacro( "No sequence delimiter item at end of JPEG item sequence");
1636    }
1637 }
1638
1639 /**
1640  * \brief   Assuming the internal file pointer \ref Document::Fp 
1641  *          is placed at the beginning of a tag check whether this
1642  *          tag is (TestGroup, TestElem).
1643  * \warning On success the internal file pointer \ref Document::Fp
1644  *          is modified to point after the tag.
1645  *          On failure (i.e. when the tag wasn't the expected tag
1646  *          (TestGroup, TestElem) the internal file pointer
1647  *          \ref Document::Fp is restored to it's original position.
1648  * @param   testGroup The expected group   of the tag.
1649  * @param   testElem  The expected Element of the tag.
1650  * @return  True on success, false otherwise.
1651  */
1652 bool File::ReadTag(uint16_t testGroup, uint16_t testElem)
1653 {
1654    long positionOnEntry = Fp->tellg();
1655    long currentPosition = Fp->tellg();          // On debugging purposes
1656
1657    // Read the Item Tag group and element, and make
1658    // sure they are what we expected:
1659    uint16_t itemTagGroup;
1660    uint16_t itemTagElem;
1661    try
1662    {
1663       itemTagGroup = ReadInt16();
1664       itemTagElem  = ReadInt16();
1665    }
1666    catch ( FormatError e )
1667    {
1668       //std::cerr << e << std::endl;
1669       return false;
1670    }
1671    if ( itemTagGroup != testGroup || itemTagElem != testElem )
1672    {
1673       gdcmWarningMacro( "Wrong Item Tag found:"
1674        << "   We should have found tag ("
1675        << std::hex << testGroup << "," << testElem << ")" << std::endl
1676        << "   but instead we encountered tag ("
1677        << std::hex << itemTagGroup << "," << itemTagElem << ")"
1678        << "  at address: " << "  0x(" << (unsigned int)currentPosition  << ")" 
1679        ) ;
1680       Fp->seekg(positionOnEntry, std::ios::beg);
1681
1682       return false;
1683    }
1684    return true;
1685 }
1686
1687 /**
1688  * \brief   Assuming the internal file pointer \ref Document::Fp 
1689  *          is placed at the beginning of a tag (TestGroup, TestElement),
1690  *          read the length associated to the Tag.
1691  * \warning On success the internal file pointer \ref Document::Fp
1692  *          is modified to point after the tag and it's length.
1693  *          On failure (i.e. when the tag wasn't the expected tag
1694  *          (TestGroup, TestElement) the internal file pointer
1695  *          \ref Document::Fp is restored to it's original position.
1696  * @param   testGroup The expected Group   of the tag.
1697  * @param   testElem  The expected Element of the tag.
1698  * @return  On success returns the length associated to the tag. On failure
1699  *          returns 0.
1700  */
1701 uint32_t File::ReadTagLength(uint16_t testGroup, uint16_t testElem)
1702 {
1703
1704    if ( !ReadTag(testGroup, testElem) )
1705    {
1706       return 0;
1707    }
1708                                                                                 
1709    //// Then read the associated Item Length
1710    long currentPosition = Fp->tellg();
1711    uint32_t itemLength  = ReadInt32();
1712    {
1713       gdcmWarningMacro( "Basic Item Length is: "
1714         << itemLength << std::endl
1715         << "  at address: " << std::hex << (unsigned int)currentPosition);
1716    }
1717    return itemLength;
1718 }
1719
1720 /**
1721  * \brief When parsing the Pixel Data of an encapsulated file, read
1722  *        the basic offset table (when present, and BTW dump it).
1723  */
1724 void File::ReadAndSkipEncapsulatedBasicOffsetTable()
1725 {
1726    //// Read the Basic Offset Table Item Tag length...
1727    uint32_t itemLength = ReadTagLength(0xfffe, 0xe000);
1728
1729    // When present, read the basic offset table itself.
1730    // Notes: - since the presence of this basic offset table is optional
1731    //          we can't rely on it for the implementation, and we will simply
1732    //          trash it's content (when present).
1733    //        - still, when present, we could add some further checks on the
1734    //          lengths, but we won't bother with such fuses for the time being.
1735    if ( itemLength != 0 )
1736    {
1737       char *basicOffsetTableItemValue = new char[itemLength + 1];
1738       Fp->read(basicOffsetTableItemValue, itemLength);
1739
1740 #ifdef GDCM_DEBUG
1741       for (unsigned int i=0; i < itemLength; i += 4 )
1742       {
1743          uint32_t individualLength = str2num( &basicOffsetTableItemValue[i],
1744                                               uint32_t);
1745          gdcmWarningMacro( "Read one length: " << 
1746                           std::hex << individualLength );
1747       }
1748 #endif //GDCM_DEBUG
1749
1750       delete[] basicOffsetTableItemValue;
1751    }
1752 }
1753
1754 // These are the deprecated method that one day should be removed (after the next release)
1755
1756 #ifndef GDCM_LEGACY_REMOVE
1757 /**
1758  * \brief  Constructor (DEPRECATED : temporaryly kept not to break the API)
1759  * @param  filename name of the file whose header we want to analyze
1760  * @deprecated do not use any longer
1761  */
1762 File::File( std::string const &filename )
1763      :Document( )
1764 {    
1765    RLEInfo  = new RLEFramesInfo;
1766    JPEGInfo = new JPEGFragmentsInfo;
1767
1768    SetFileName( filename );
1769    Load( ); // gdcm::Document is first Loaded, then the 'File part'
1770 }
1771
1772 /**
1773  * \brief   Loader. (DEPRECATED :  temporaryly kept not to break the API)
1774  * @param   fileName file to be open for parsing
1775  * @return false if file cannot be open or no swap info was found,
1776  *         or no tag was found.
1777  * @deprecated Use the Load() [ + SetLoadMode() ] + SetFileName() functions instead
1778  */
1779 bool File::Load( std::string const &fileName ) 
1780 {
1781    GDCM_LEGACY_REPLACED_BODY(File::Load(std::string), "1.2",
1782                              File::Load());
1783    SetFileName( fileName );
1784    if ( ! this->Document::Load( ) )
1785       return false;
1786
1787    return DoTheLoadingJob( );
1788 }
1789 #endif
1790
1791 // -----------------------------------------------------------------------------------------
1792 //  THERALYS Algorithm to determine the most similar basic orientation
1793 //
1794 //  Transliterated from original Python code.
1795 //  Kept as close as possible to the original code
1796 //  in order to speed up any further modif of Python code :-(
1797 // ------------------------------------------------------------------------------------------
1798
1799 /**
1800  * \brief  THERALYS' Algorithm to determine the most similar basic orientation
1801  *           (Axial, Coronal, Sagital) of the image
1802  * \note Should be run on the first gdcm::File of a 'coherent' Serie
1803  * @return orientation code
1804  * @return orientation code
1805  *   #   0 :   Not Applicable (neither 0020,0037 Image Orientation Patient 
1806  *   #                         nor     0020,0032Image Position     found )
1807  *   #   1 :   Axial
1808  *   #  -1 :   Axial invert
1809  *   #   2 :   Coronal
1810  *   #  -2 :   Coronal invert
1811  *   #   3 :   Sagital
1812  *   #  -3 :   Sagital invert
1813  *   #   4 :   Heart Axial
1814  *   #  -4 :   Heart Axial invert
1815  *   #   5 :   Heart Coronal
1816  *   #  -5 :   Heart Coronal invert
1817  *   #   6 :   Heart Sagital
1818  *   #  -6 :   Heart Sagital invert
1819  */
1820 double File::TypeOrientation( )
1821 {
1822    float iop[6];
1823    bool succ = GetImageOrientationPatient( iop );
1824    if ( !succ )
1825    {
1826       return 0.;
1827    }
1828
1829    vector3D ori1;
1830    vector3D ori2;
1831
1832    ori1.x = iop[0]; ori1.y = iop[1]; ori1.z = iop[2]; 
1833    ori1.x = iop[3]; ori2.y = iop[4]; ori2.z = iop[5];
1834
1835    // two perpendicular vectors describe one plane
1836    double dicPlane[6][2][3] =
1837    { {  {1,    0,    0   },{0,       1,     0     }  },       // Axial
1838      {  {1,    0,    0   },{0,       0,    -1     }  },       // Coronal
1839      {  {0,    1,    0   },{0,       0,    -1     }  },       // Sagittal
1840      {  { 0.8, 0.5,  0.0 },{-0.1,    0.1 , -0.95  }  },       // Axial - HEART
1841      {  { 0.8, 0.5,  0.0 },{-0.6674, 0.687, 0.1794}  },       // Coronal - HEART
1842      {  {-0.1, 0.1, -0.95},{-0.6674, 0.687, 0.1794}  }        // Sagittal - HEART
1843    };
1844
1845    vector3D refA;
1846    vector3D refB;
1847    int i = 0;
1848    Res res;   // [ <result> , <memory of the last succes calcule> ]
1849    res.first = 0;
1850    res.second = 99999;
1851    for (int numDicPlane=0; numDicPlane<6; numDicPlane++)
1852    {
1853        ++i;
1854        // refA=plane[0]
1855        refA.x = dicPlane[numDicPlane][0][0]; 
1856        refA.y = dicPlane[numDicPlane][0][1]; 
1857        refA.z = dicPlane[numDicPlane][0][2];
1858        // refB=plane[1]
1859        refB.x = dicPlane[numDicPlane][1][0]; 
1860        refB.y = dicPlane[numDicPlane][1][1]; 
1861        refB.z = dicPlane[numDicPlane][1][2];
1862        res=VerfCriterion(  i, CalculLikelyhood2Vec(refA,refB,ori1,ori2), res );
1863        res=VerfCriterion( -i, CalculLikelyhood2Vec(refB,refA,ori1,ori2), res );
1864    }
1865    return res.first;
1866 /*
1867 //             i=0
1868 //             res=[0,99999]  ## [ <result> , <memory of the last succes calculus> ]
1869 //             for plane in dicPlane:
1870 //                 i=i+1
1871 //                 refA=plane[0]
1872 //                 refB=plane[1]
1873 //                 res=self.VerfCriterion(  i , self.CalculLikelyhood2Vec(refA,refB,ori1,ori2) , res )
1874 //                 res=self.VerfCriterion( -i , self.CalculLikelyhood2Vec(refB,refA,ori1,ori2) , res )
1875 //             return res[0]
1876 */
1877
1878 }
1879
1880 Res 
1881 File::VerfCriterion(int typeCriterion, double criterionNew, Res const & in)
1882 {
1883    Res res;
1884    double criterion = in.second;
1885    if (criterionNew < criterion)
1886    {
1887       res.first  = criterionNew;
1888       res.second = typeCriterion;
1889    }
1890 /*
1891 //   type = res[0]
1892 //   criterion = res[1]
1893 // #     if criterionNew<0.1 and criterionNew<criterion:
1894 //   if criterionNew<criterion:
1895 //      criterion=criterionNew
1896 //      type=typeCriterion
1897 //   return [ type , criterion ]
1898 */
1899    return res;
1900
1901
1902 inline double square_dist(vector3D const &v1, vector3D const & v2)
1903 {
1904   double res;
1905   res = (v1.x - v2.x)*(v1.x - v2.x) +
1906         (v1.y - v2.y)*(v1.y - v2.y) +
1907         (v1.z - v2.z)*(v1.z - v2.z);
1908   return res;
1909 }
1910
1911 //------------------------- Purpose : -----------------------------------
1912 //- This function determines the orientation similarity of two planes.
1913 //  Each plane is described by two vectors.
1914 //------------------------- Parameters : --------------------------------
1915 //- <refA>  : - type : vector 3D (double)
1916 //- <refB>  : - type : vector 3D (double)
1917 //            - Description of the first plane
1918 //- <ori1>  : - type : vector 3D (double)
1919 //- <ori2>  : - type : vector 3D (double)
1920 //            - Description of the second plane
1921 //------------------------- Return : ------------------------------------
1922 // double :   0 if the planes are perpendicular. While the difference of
1923 //            the orientation between the planes are big more enlarge is
1924 //            the criterion.
1925 //------------------------- Other : -------------------------------------
1926 // The calculus is based with vectors normalice
1927 double
1928 File::CalculLikelyhood2Vec(vector3D const & refA, vector3D const & refB, 
1929                            vector3D const & ori1, vector3D const & ori2 )
1930 {
1931
1932    vector3D ori3 = ProductVectorial(ori1,ori2);
1933    vector3D refC = ProductVectorial(refA,refB);
1934    double res = square_dist(refC, ori3);
1935
1936    return sqrt(res);
1937 }
1938
1939 //------------------------- Purpose : -----------------------------------
1940 //- Calculus of the poduct vectorial between two vectors 3D
1941 //------------------------- Parameters : --------------------------------
1942 //- <vec1>  : - type : vector 3D (double)
1943 //- <vec2>  : - type : vector 3D (double)
1944 //------------------------- Return : ------------------------------------
1945 // (vec) :    - Vector 3D
1946 //------------------------- Other : -------------------------------------
1947 vector3D
1948 File::ProductVectorial(vector3D const & vec1, vector3D const & vec2)
1949 {
1950    vector3D vec3;
1951    vec3.x =    vec1.y*vec2.z - vec1.z*vec2.y;
1952    vec3.y = -( vec1.x*vec2.z - vec1.z*vec2.x);
1953    vec3.z =    vec1.x*vec2.y - vec1.y*vec2.x;
1954
1955    return vec3;
1956 }
1957
1958 //-----------------------------------------------------------------------------
1959 // Print
1960
1961 //-----------------------------------------------------------------------------
1962 } // end namespace gdcm