1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2007/03/23 15:05:11 $
7 Version: $Revision: 1.328 $
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.
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.
17 =========================================================================*/
20 // -------------- Remember ! ----------------------------------
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
29 // Image Position (Patient) (0020,0032) VM=3
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
41 // Patient Position (0018,5100) values :
43 // HFS = Head First-Supine, where increasing (positive axis direction) :
44 // X -> to the direction pointed to by the patient's oustretched left arm
45 // Y -> to the anterior-to-posterior direction in the patient's body
46 // Z -> to the feet-to-head direction in the patient's body
48 // HFP = Head First-Prone, where increasing (positive axis direction) :
49 // X -> to the direction pointed to by the patient's oustretched left arm
50 // Y -> to the anterior-to-posterior direction in the patient's body
51 // Z -> to the feet-to-head direction in the patient's body
53 // FFS = Feet First-Supine, where increasing (positive axis direction) :
54 // X -> to the direction pointed to by the patient's oustretched left arm
55 // Y -> to the anterior-to-posterion direction in the patient's body
56 // Z -> to the feet-to-head direction in the patient's body
58 // FFP = Feet First-Prone, where increasing (positive axis direction) :
59 // X -> to the direction pointed to by the patient's oustretched left arm
60 // Y -> to the posterior-to-anterior direction in the patient's body
61 // Z -> to the feet-to-head direction in the patient's body
63 // HFDR = Head First-Decubitus Right
64 // HFDL = Head First-Decubitus Left
65 // FFDR = Feet First-Decubitus Right
66 // FFDL = Feet First-Decubitus Left
68 // we can also find (non standard!)
73 // CS 2 Patient Orientation (0020 0020)
74 // When the coordinates of the image
75 // are always present, this field is almost never used.
76 // Better we don't trust it too much ...
85 // (0020|0037) [Image Orientation (Patient)] [1\0\0\0\1\0 ]
88 // ---------------------------------------------------------------
91 #include "gdcmGlobal.h"
93 #include "gdcmDebug.h"
95 #include "gdcmSeqEntry.h"
96 #include "gdcmRLEFramesInfo.h"
97 #include "gdcmJPEGFragmentsInfo.h"
98 #include "gdcmDataEntry.h"
101 #include <stdio.h> //sscanf
102 #include <stdlib.h> // for atoi
107 //-----------------------------------------------------------------------------
108 // Constructor / Destructor
111 * \brief Constructor used when we want to generate dicom files from scratch
116 RLEInfo = new RLEFramesInfo;
117 JPEGInfo = new JPEGFragmentsInfo;
118 GrPixel = 0x7fe0; // to avoid further troubles
120 BasicOffsetTableItemValue = 0;
121 FourthDimensionLocation = TagKey(0,0);
126 * \brief Canonical destructor.
134 delete[] BasicOffsetTableItemValue;
137 //-----------------------------------------------------------------------------
141 * @return false if file cannot be open or no swap info was found,
142 * or no tag was found.
147 if ( ! this->Document::Load( ) )
150 return DoTheLoadingJob( );
154 * \brief Does the Loading Job (internal use only)
155 * @return false if file cannot be open or no swap info was found,
156 * or no tag was found.
158 bool File::DoTheLoadingJob( )
160 // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
161 // We may encounter the 'RETired' (0x0028, 0x0200) tag
162 // (Image Location") . This entry contains the number of
163 // the group that contains the pixel data (hence the "Pixel Data"
164 // is found by indirection through the "Image Location").
165 // Inside the group pointed by "Image Location" the searched element
166 // is conventionally the element 0x0010 (when the norm is respected).
167 // When the "Image Location" is missing we default to group 0x7fe0.
168 // Note: this IS the right place for the code
171 const std::string &imgLocation = GetEntryString(0x0028, 0x0200);
172 if ( imgLocation == GDCM_UNFOUND )
180 GrPixel = (uint16_t) atoi( imgLocation.c_str() );
183 // sometimes Image Location value doesn't follow
184 // the supposed processor endianness.
185 // see gdcmData/cr172241.dcm
186 if ( GrPixel == 0xe07f )
191 if ( GrPixel != 0x7fe0 )
193 // This is a kludge for old dirty Philips imager.
201 // Now, we know GrPixel and NumPixel.
202 // Let's create a VirtualDictEntry to allow a further VR modification
203 // and force VR to match with BitsAllocated.
204 DocEntry *entry = GetDocEntry(GrPixel, NumPixel);
207 // Compute the RLE or JPEG info
209 const std::string &ts = GetTransferSyntax();
210 Fp->seekg( entry->GetOffset(), std::ios::beg );
211 if ( Global::GetTS()->IsRLELossless(ts) )
213 else if ( Global::GetTS()->IsJPEG(ts) )
214 ComputeJPEGFragmentInfo();
217 // Create a new DataEntry to change the DictEntry
218 // The changed DictEntry will have
219 // - a correct PixelVR OB or OW)
220 // - the name to "Pixel Data"
223 //==> Just change the VR !
226 DataEntry *oldEntry = dynamic_cast<DataEntry *>(entry);
230 // 8 bits allocated is a 'O Bytes' , as well as 24 (old ACR-NEMA RGB)
231 // more than 8 (i.e 12, 16) is a 'O Words'
232 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
237 // Change only made if usefull
238 if ( PixelVR != oldEntry->GetVR() )
240 //DictEntry* newDict = DictEntry::New(GrPixel,NumPixel,
241 // PixelVR,"1","Pixel Data");
242 //DataEntry *newEntry = DataEntry::New(newDict);
244 //newEntry->Copy(entry);
245 //newEntry->SetBinArea(oldEntry->GetBinArea(),oldEntry->IsSelfArea());
246 //oldEntry->SetSelfArea(false);
248 //RemoveEntry(oldEntry);
249 //AddEntry(newEntry);
250 //newEntry->Delete();
256 // 8 bits allocated is a 'OB(ytes)' , as well as 24 (old ACR-NEMA RGB)
257 // more than 8 (i.e 12, 16) is a 'OW(ords)'
258 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
262 // Change only made if usefull
263 if ( PixelVR != entry->GetVR() )
265 entry->SetVR(PixelVR);
272 * \brief This predicate, based on hopefully reasonable heuristics,
273 * decides whether or not the current File was properly parsed
274 * and contains the mandatory information for being considered as
275 * a well formed and usable Dicom/Acr File.
276 * @return true when File is the one of a reasonable Dicom/Acr file,
279 bool File::IsReadable()
281 if ( !Document::IsReadable() )
286 const std::string &res = GetEntryString(0x0028, 0x0005);
287 if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 )
289 gdcmWarningMacro("Wrong Image Dimensions" << res);
290 return false; // Image Dimensions
292 bool b0028_0100 = true;
293 if ( !GetDocEntry(0x0028, 0x0100) )
295 gdcmWarningMacro("Bits Allocated (0028|0100) not found");
296 //return false; // "Bits Allocated"
299 bool b0028_0101 = true;
300 if ( !GetDocEntry(0x0028, 0x0101) )
302 gdcmWarningMacro("Bits Stored (0028|0101) not found");
303 //return false; // "Bits Stored"
306 bool b0028_0102 = true;
307 if ( !GetDocEntry(0x0028, 0x0102) )
309 gdcmWarningMacro("Hight Bit (0028|0102) not found");
310 //return false; // "High Bit"
313 bool b0028_0103 = true;
314 if ( !GetDocEntry(0x0028, 0x0103) )
316 gdcmWarningMacro("Pixel Representation (0028|0103) not found");
317 //return false; // "Pixel Representation" i.e. 'Sign' ( 0 : unsigned, 1 : signed)
321 if ( !b0028_0100 && !b0028_0101 && !b0028_0102 && !b0028_0103)
323 gdcmWarningMacro("Too much mandatory Tags missing !");
327 if ( !GetDocEntry(GrPixel, NumPixel) )
329 gdcmWarningMacro("Pixel Dicom Element " << std::hex <<
330 GrPixel << "|" << NumPixel << "not found");
331 return false; // Pixel Dicom Element not found :-(
337 * \brief gets the info from 0020,0013 : Image Number else 0.
338 * @return image number
340 int File::GetImageNumber()
342 //0020 0013 : Image Number
343 std::string strImNumber = GetEntryString(0x0020,0x0013);
344 if ( strImNumber != GDCM_UNFOUND )
346 return atoi( strImNumber.c_str() );
352 * \brief gets the info from 0008,0060 : Modality
353 * @return Modality Type
355 ModalityType File::GetModality()
357 // 0008 0060 : Modality
358 std::string strModality = GetEntryString(0x0008,0x0060);
359 if ( strModality != GDCM_UNFOUND )
361 if ( strModality.find("AU") < strModality.length()) return AU;
362 else if ( strModality.find("AS") < strModality.length()) return AS;
363 else if ( strModality.find("BI") < strModality.length()) return BI;
364 else if ( strModality.find("CF") < strModality.length()) return CF;
365 else if ( strModality.find("CP") < strModality.length()) return CP;
366 else if ( strModality.find("CR") < strModality.length()) return CR;
367 else if ( strModality.find("CT") < strModality.length()) return CT;
368 else if ( strModality.find("CS") < strModality.length()) return CS;
369 else if ( strModality.find("DD") < strModality.length()) return DD;
370 else if ( strModality.find("DF") < strModality.length()) return DF;
371 else if ( strModality.find("DG") < strModality.length()) return DG;
372 else if ( strModality.find("DM") < strModality.length()) return DM;
373 else if ( strModality.find("DS") < strModality.length()) return DS;
374 else if ( strModality.find("DX") < strModality.length()) return DX;
375 else if ( strModality.find("ECG") < strModality.length()) return ECG;
376 else if ( strModality.find("EPS") < strModality.length()) return EPS;
377 else if ( strModality.find("FA") < strModality.length()) return FA;
378 else if ( strModality.find("FS") < strModality.length()) return FS;
379 else if ( strModality.find("HC") < strModality.length()) return HC;
380 else if ( strModality.find("HD") < strModality.length()) return HD;
381 else if ( strModality.find("LP") < strModality.length()) return LP;
382 else if ( strModality.find("LS") < strModality.length()) return LS;
383 else if ( strModality.find("MA") < strModality.length()) return MA;
384 else if ( strModality.find("MR") < strModality.length()) return MR;
385 else if ( strModality.find("NM") < strModality.length()) return NM;
386 else if ( strModality.find("OT") < strModality.length()) return OT;
387 else if ( strModality.find("PT") < strModality.length()) return PT;
388 else if ( strModality.find("RF") < strModality.length()) return RF;
389 else if ( strModality.find("RG") < strModality.length()) return RG;
390 else if ( strModality.find("RTDOSE")
391 < strModality.length()) return RTDOSE;
392 else if ( strModality.find("RTIMAGE")
393 < strModality.length()) return RTIMAGE;
394 else if ( strModality.find("RTPLAN")
395 < strModality.length()) return RTPLAN;
396 else if ( strModality.find("RTSTRUCT")
397 < strModality.length()) return RTSTRUCT;
398 else if ( strModality.find("SM") < strModality.length()) return SM;
399 else if ( strModality.find("ST") < strModality.length()) return ST;
400 else if ( strModality.find("TG") < strModality.length()) return TG;
401 else if ( strModality.find("US") < strModality.length()) return US;
402 else if ( strModality.find("VF") < strModality.length()) return VF;
403 else if ( strModality.find("XA") < strModality.length()) return XA;
404 else if ( strModality.find("XC") < strModality.length()) return XC;
408 /// \todo throw error return value ???
409 /// specified <> unknown in our database
417 * \brief Retrieve the number of columns of image.
418 * @return The encountered size when found, 0 by default.
419 * 0 means the file is NOT USABLE. The caller will have to check
423 DataEntry *entry = GetDataEntry(0x0028,0x0011);
425 return (int)entry->GetValue(0);
430 * \brief Retrieve the number of lines of image.
431 * \warning The defaulted value is 1 as opposed to File::GetXSize()
432 * @return The encountered size when found, 1 by default
433 * (The ACR-NEMA file contains a Signal, not an Image).
437 DataEntry *entry = GetDataEntry(0x0028,0x0010);
439 return (int)entry->GetValue(0);
446 // The Rows (0028,0010) entry was optional for ACR/NEMA.
447 // (at least some images didn't have it.)
448 // It might hence be a signal (1D image). So we default to 1:
453 * \brief Retrieve the number of planes of volume or the number
454 * of frames of a multiframe.
455 * \warning When present we consider the "Number of Frames" as the third
456 * dimension. When missing we consider the third dimension as
457 * being the ACR-NEMA "Planes" tag content.
458 * @return The encountered size when found, 1 by default (single image).
462 // Both DicomV3 and ACR/Nema consider the "Number of Frames"
463 // as the third dimension.
464 DataEntry *entry = GetDataEntry(0x0028,0x0008);
466 return (int)entry->GetValue(0);
468 // We then consider the "Planes" entry as the third dimension
469 entry = GetDataEntry(0x0028,0x0012);
471 return (int)entry->GetValue(0);
476 * \brief Retrieve the -unnormalized- number of 'times' of '4D image'.
477 * User has to tell gdcm the location of this '4th Dimension component'
478 * using SetFourthDimensionLocation() method before.
479 * \warning The defaulted value is 1.
480 * @return The encountered size when found, 1 by default
481 * (The file doesn't contain a '4D image'.).
485 if (FourthDimensionLocation == TagKey(0,0) )// 4D location is not set : not a 4D object
488 DataEntry *entry = GetDataEntry(FourthDimensionLocation.GetGroup(),
489 FourthDimensionLocation.GetElement() );
492 gdcmWarningMacro( " FourthDimensionLocation not found at : " <<
493 std::hex << FourthDimensionLocation.GetGroup()
494 << "|" << FourthDimensionLocation.GetElement());
499 return (int)entry->GetValue(0);
504 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
505 * then 0028,0030 : Pixel Spacing
507 * @return X dimension of a pixel
509 float File::GetXSpacing()
511 float xspacing = 1.0;
516 From:David Clunie - view profile
517 Date:Wed, May 24 2006 1:12 pm
518 Email:David Clunie <dclu...@dclunie.com>
519 Groups:comp.protocols.dicom
521 The short answer is that:
523 - (0018,1164) describes a spacing equivalent to that which
524 would be measured off a film in projection radiography
526 - (0018,7022) does not describe the image pixels themselves,
527 since detector elements may have been binned to produce
530 - (0018,7020) may be different from (0018,7022) since there
531 may be non-sensitive material separating individual
532 detectors (i.e. the size is smaller than the spacing
535 Only (0018,1164) is relevant when measuring things; the
536 detector-specific attributes are there to describe the
541 PS. For ultrasound you need to use Region Calibration.
545 It *SHOULD* first find the IOD and then deduce which tags to read
546 Eg: Cross section this is in Pixel Spacing (0028,0030)
547 CR is in Imager Pixel Spacing (0018,1164)
548 US is in Pixel Aspect Ratio (0028,0034)
550 (3002,0011) Image Plane Pixel Spacing
551 (3002,0012) RT Image Position
553 (3004,000c) for deducing Z spacing
556 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
558 /// \todo check the various SOP Class
559 /// to get the Pixel Spacing at the proper location
561 ///\todo find images to check if it *actually* works
563 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
564 // Ultrasound Image Storage (Retired)
565 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
566 // Ultrasound Image Storage
567 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
568 // Ultrasound Multi-Frame Storage (Retired)
569 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
570 // Ultrasound Multi-FrameImage Storage
572 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
573 // - guess how to deduce the spacing (FOV ?, ??)
575 entry = GetDataEntry(0x0028,0x0034);
578 nbValue = entry->GetValueCount();
580 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
581 << "has a wrong number of values :" << nbValue);
583 xspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
591 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.1") )
592 // Computed Radiography Image Storage
594 // CR is in Imager Pixel Spacing (0018,1164)//
597 // go on with old method ...
598 // ---------------------
599 // To follow David Clunie's advice, we first check ImagerPixelSpacing
601 entry = GetDataEntry(0x0018,0x1164);
604 nbValue = entry->GetValueCount();
605 // Can't use IsValueCountValid because of the complex heuristic.
607 gdcmWarningMacro("ImagerPixelSpacing (0x0018,0x1164) "
608 << "has a wrong number of values :" << nbValue);
611 xspacing = (float)entry->GetValue(2);
612 else if( nbValue >= 2 )
613 xspacing = (float)entry->GetValue(1);
615 xspacing = (float)entry->GetValue(0);
617 if ( xspacing == 0.0 )
623 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
626 entry = GetDataEntry(0x0028,0x0030);
629 nbValue = entry->GetValueCount();
631 gdcmWarningMacro("PixelSpacing (0x0018,0x0030) "
632 << "has a wrong number of values :" << nbValue);
635 xspacing = (float)entry->GetValue(2);
636 else if( nbValue >= 2 )
637 xspacing = (float)entry->GetValue(1);
639 xspacing = (float)entry->GetValue(0);
641 if ( xspacing == 0.0 )
647 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
653 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
654 * then from 0028,0030 : Pixel Spacing
656 * @return Y dimension of a pixel
658 float File::GetYSpacing()
665 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
667 /// \todo check the various SOP Class
668 /// to get the Pixel Spacing at the proper location
670 ///\todo find images to check if it *actually* works
672 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
673 // Ultrasound Image Storage (Retired)
674 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
675 // Ultrasound Image Storage
676 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
677 // Ultrasound Multi-Frame Storage (Retired)
678 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
679 // Ultrasound Multi-FrameImage Storage
681 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
682 // - no way to deduce the spacing/
684 entry = GetDataEntry(0x0028,0x0034);
687 nbValue = entry->GetValueCount();
689 yspacing = (float)entry->GetValue(0)/(float)entry->GetValue(1);
690 //std::cout << "ys " << yspacing << std::endl;
695 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
696 << "has a wrong number of values :" << nbValue);
700 else if (nbValue == 1 ) {
701 yspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
711 // go on with old method ...
712 // ---------------------
713 // To follow David Clunie's advice, we first check ImagerPixelSpacing
715 // To follow David Clunie's advice, we first check ImagerPixelSpacing
717 entry = GetDataEntry(0x0018,0x1164);
720 yspacing = (float)entry->GetValue(0);
722 if ( yspacing == 0.0 )
728 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
731 entry = GetDataEntry(0x0028,0x0030);
734 yspacing = (float)entry->GetValue(0);
736 if ( yspacing == 0.0 )
742 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
749 * \brief gets the info from 0018,0088 : Space Between Slices
750 * else from 0018,0050 : Slice Thickness
753 * When an element is missing, we suppose slices join together
754 * (no overlapping, no interslice gap) but we have no way to check it !
755 * For *Dicom* images, ZSpacing *should be* calculated using
756 * XOrigin, YOrigin, ZOrigin (of the top left image corner)
757 * of 2 consecutive images, and the Orientation
758 * Computing ZSpacing on a single image is not really meaningfull !
759 * @return Z dimension of a voxel-to be
761 float File::GetZSpacing()
764 float zspacing = 1.0f;
766 // Spacing Between Slices : distance between the middle of 2 slices
768 // jointives (Spacing between Slices = Slice Thickness)
769 // overlapping (Spacing between Slices < Slice Thickness)
770 // disjointes (Spacing between Slices > Slice Thickness)
771 // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
772 // It only concerns the MRI guys, not people wanting to visualize volumes
773 // If Spacing Between Slices is missing,
774 // we suppose slices joint together
775 DataEntry *entry = GetDataEntry(0x0018,0x0088);
777 { zspacing = (float)entry->GetValue(0);
779 if ( zspacing == 0.0 )
784 gdcmWarningMacro("Unfound Spacing Between Slices (0018,0088)");
786 // if no 'Spacing Between Slices' is found,
787 // we assume slices join together
788 // (no overlapping, no interslice gap)
789 entry = GetDataEntry(0x0018,0x0050);
792 zspacing = (float)entry->GetValue(0);
794 if ( zspacing == 0.0 )
799 gdcmWarningMacro("Unfound Slice Thickness (0018,0050)");
801 // if no 'Spacing Between Slices' is found,
802 // we assume slices join together
803 // (no overlapping, no interslice gap)
804 entry = GetDataEntry(0x3004,0x000c);
807 float z1 = (float)entry->GetValue(0);
808 float z2 = (float)entry->GetValue(1);
809 zspacing = z2 - z1; // can be negative...
811 if ( zspacing == 0.0 )
820 * \brief gets the info from 0020,0032 : Image Position Patient
821 * else from 0020,0030 : Image Position (RET)
823 * @return up-left image corner X position
825 float File::GetXOrigin()
827 DataEntry *entry = GetDataEntry(0x0020,0x0032);
830 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
831 entry = GetDataEntry(0x0020,0x0030);
834 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
839 if( entry->GetValueCount() == 3 )
841 if (!entry->IsValueCountValid() )
843 gdcmErrorMacro( "Invalid Value Count" );
845 return (float)entry->GetValue(0);
851 * \brief gets the info from 0020,0032 : Image Position Patient
852 * else from 0020,0030 : Image Position (RET)
854 * @return up-left image corner Y position
856 float File::GetYOrigin()
858 DataEntry *entry = GetDataEntry(0x0020,0x0032);
861 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
862 entry = GetDataEntry(0x0020,0x0030);
865 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
870 if( entry->GetValueCount() == 3 )
872 if (!entry->IsValueCountValid() )
874 gdcmErrorMacro( "Invalid Value Count" );
876 return (float)entry->GetValue(1);
882 * \brief gets the info from 0020,0032 : Image Position Patient
883 * else from 0020,0030 : Image Position (RET)
884 * else from 0020,1041 : Slice Location
885 * else from 0020,0050 : Location
887 * @return up-left image corner Z position
889 float File::GetZOrigin()
891 DataEntry *entry = GetDataEntry(0x0020,0x0032);
894 if( entry->GetValueCount() == 3 )
896 if (!entry->IsValueCountValid() )
898 gdcmErrorMacro( "Invalid Value Count" );
900 return (float)entry->GetValue(2);
902 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)");
906 entry = GetDataEntry(0x0020,0x0030);
909 if( entry->GetValueCount() == 3 )
911 if (!entry->IsValueCountValid() )
913 gdcmErrorMacro( "Invalid Value Count" );
915 return (float)entry->GetValue(2);
917 gdcmWarningMacro( "Wrong Image Position (RET) (0020,0030)");
921 // for *very* old ACR-NEMA images
922 entry = GetDataEntry(0x0020,0x1041);
925 if( entry->GetValueCount() == 1 )
927 if (!entry->IsValueCountValid() )
929 gdcmErrorMacro( "Invalid Value Count" );
931 return (float)entry->GetValue(0); // VM=1 !
933 gdcmWarningMacro( "Wrong Slice Location (0020,1041)");
937 entry = GetDataEntry(0x0020,0x0050);
940 if( entry->GetValueCount() == 1 )
942 if (!entry->IsValueCountValid() )
944 gdcmErrorMacro( "Invalid Value Count" );
946 return (float)entry->GetValue(0);
948 gdcmWarningMacro( "Wrong Location (0020,0050)");
951 return 0.; // Hopeless
955 * \brief gets the info from 0020,0037 : Image Orientation Patient
956 * or from 0020 0035 : Image Orientation (RET)
958 * (needed to organize DICOM files based on their x,y,z position)
960 * @param iop adress of the (6)float array to receive values.
961 * (defaulted as 1.,0.,0.,0.,1.,0. if nothing -or inconsistent stuff-
963 * @return true when one of the tag -with consistent values- is found
964 * false when nothing or inconsistent stuff - is found
966 bool File::GetImageOrientationPatient( float iop[6] )
968 std::string strImOriPat;
969 //iop is supposed to be float[6]
970 iop[0] = iop[4] = 1.;
971 iop[1] = iop[2] = iop[3] = iop[5] = 0.;
973 // 0020 0037 DS REL Image Orientation (Patient)
974 if ( (strImOriPat = GetEntryString(0x0020,0x0037)) != GDCM_UNFOUND )
976 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
977 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
979 gdcmWarningMacro( "Wrong Image Orientation Patient (0020,0037)."
980 << " Less than 6 values were found." );
986 // 0020 0035 DS REL Image Orientation (RET)
987 else if ( (strImOriPat = GetEntryString(0x0020,0x0035)) != GDCM_UNFOUND )
989 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
990 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
992 gdcmWarningMacro( "wrong Image Orientation Patient (0020,0035). "
993 << "Less than 6 values were found." );
1002 * \brief gets the cosine of image X axis, against patient X axis
1003 * (Sorry, but Python needs it :-( )
1004 * @return cosine of image X axis, against patient X axis
1006 float File::GetXCosineOnX()
1009 GetImageOrientationPatient( iop );
1014 * \brief gets the cosine of image X axis, against patient Y axis
1015 * (Sorry, but Python needs it :-( )
1016 * @return cosine of image X axis, against patient Y axis
1018 float File::GetXCosineOnY()
1021 GetImageOrientationPatient( iop );
1026 * \brief gets the cosine of image X axis, against patient Z axis
1027 * (Sorry, but Python needs it :-( )
1028 * @return cosine of image X axis, against patient Z axis
1030 float File::GetXCosineOnZ()
1033 GetImageOrientationPatient( iop );
1038 * \brief gets the cosine of image Y axis, against patient X axis
1039 * (Sorry, but Python needs it :-( )
1040 * @return cosine of image Y axis, against patient X axis
1042 float File::GetYCosineOnX()
1045 GetImageOrientationPatient( iop );
1050 * \brief gets the cosine of image Y axis, against patient Y axis
1051 * (Sorry, but Python needs it :-( )
1052 * @return cosine of image Y axis, against patient Y axis
1054 float File::GetYCosineOnY()
1057 GetImageOrientationPatient( iop );
1062 * \brief gets the cosine of image Y axis, against patient Z axis
1063 * (Sorry, but Python needs it :-( )
1064 * @return cosine of image Y axis, against patient Z axis
1066 float File::GetYCosineOnZ()
1069 GetImageOrientationPatient( iop );
1073 * \brief gets the info from 0020,0032 : Image Position Patient
1074 * or from 0020 0030 : Image Position (RET)
1076 * @param ipp adress of the (3)float array to receive values.
1077 * (defaulted as 0.,0.,0. if nothing -or inconsistent stuff-
1079 * @return true when one of the tag -with consistent values- is found
1080 * false when nothing or inconsistent stuff - is found
1082 bool File::GetImagePositionPatient( float ipp[3] )
1084 std::string strImPosiPat;
1085 //ipp is supposed to be float[3]
1086 ipp[0] = ipp[1] = ipp[2] = 0.;
1088 // 0020 0032 DS REL Image Position (Patient)
1089 strImPosiPat = GetEntryString(0x0020,0x0032);
1090 if ( strImPosiPat != GDCM_UNFOUND )
1092 if ( sscanf( strImPosiPat.c_str(), "%f \\ %f \\%f ",
1093 &ipp[0], &ipp[1], &ipp[2]) != 3 )
1095 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)."
1096 << " Less than 3 values were found." );
1102 // 0020 0030 DS REL Image Position (RET)
1103 else if ( (strImPosiPat = GetEntryString(0x0020,0x0030)) != GDCM_UNFOUND )
1105 if ( sscanf( strImPosiPat.c_str(), "%f \\ %f \\%f ",
1106 &ipp[0], &ipp[1], &ipp[2]) != 3 )
1108 gdcmWarningMacro( "wrong Image Position Patient (0020,0030). "
1109 << "Less than 3 values were found." );
1118 * \brief Retrieve the number of Bits Stored (actually used)
1119 * (as opposed to number of Bits Allocated)
1120 * @return The encountered number of Bits Stored, 0 by default.
1121 * 0 means the file is NOT USABLE. The caller has to check it !
1123 int File::GetBitsStored()
1125 DataEntry *entry = GetDataEntry(0x0028,0x0101);
1128 gdcmWarningMacro("BitsStored (0028,0101) is supposed to be mandatory");
1131 return (int)entry->GetValue(0);
1135 * \brief Retrieve the number of Bits Allocated
1136 * (8, 12 -compacted ACR-NEMA files-, 16, 24 -old RGB ACR-NEMA files-,)
1137 * @return The encountered Number of Bits Allocated, 0 by default.
1138 * 0 means the file is NOT USABLE. The caller has to check it !
1140 int File::GetBitsAllocated()
1142 DataEntry *entry = GetDataEntry(0x0028,0x0100);
1145 gdcmWarningMacro("BitsAllocated (0028,0100) is supposed to be mandatory");
1148 return (int)entry->GetValue(0);
1152 * \brief Retrieve the high bit position.
1153 * \warning The method defaults to 0 when information is missing.
1154 * The responsability of checking this value is left to the caller.
1155 * @return The high bit position when present. 0 when missing.
1157 int File::GetHighBitPosition()
1159 DataEntry *entry = GetDataEntry(0x0028,0x0102);
1162 gdcmWarningMacro("HighBitPosition (0028,0102) is supposed to be mandatory");
1165 return (int)entry->GetValue(0);
1169 * \brief Retrieve the number of Samples Per Pixel
1170 * (1 : gray level, 3 : RGB/YBR -1 or 3 Planes-)
1171 * @return The encountered number of Samples Per Pixel, 1 by default.
1172 * (we assume Gray level Pixels)
1174 int File::GetSamplesPerPixel()
1176 DataEntry *entry = GetDataEntry(0x0028,0x0002);
1179 gdcmWarningMacro("SamplesPerPixel (0028,0002) is supposed to be mandatory");
1180 return 1; // Well, it's supposed to be mandatory ...
1181 // but sometimes it's missing : *we* assume Gray pixels
1183 return (int)entry->GetValue(0);
1187 * \brief Retrieve the Planar Configuration for RGB images
1188 * (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
1189 * @return The encountered Planar Configuration, 0 by default.
1191 int File::GetPlanarConfiguration()
1193 DataEntry *entry = GetDataEntry(0x0028,0x0006);
1198 return (int)entry->GetValue(0);
1202 * \brief Return the size (in bytes) of a single pixel of data.
1203 * @return The size in bytes of a single pixel of data; 0 by default
1204 * 0 means the file is NOT USABLE; the caller will have to check
1206 int File::GetPixelSize()
1208 // 0028 0100 US IMG Bits Allocated
1209 // (in order no to be messed up by old ACR-NEMA RGB images)
1210 assert( !(GetEntryString(0x0028,0x0100) == "24") );
1212 std::string pixelType = GetPixelType();
1213 if ( pixelType == "8U" || pixelType == "8S" )
1217 if ( pixelType == "16U" || pixelType == "16S")
1221 if ( pixelType == "32U" || pixelType == "32S")
1225 if ( pixelType == "FD" )
1229 gdcmWarningMacro( "Unknown pixel type: " << pixelType);
1234 * \brief Build the Pixel Type of the image.
1235 * Possible values are:
1236 * - 8U unsigned 8 bit,
1237 * - 8S signed 8 bit,
1238 * - 16U unsigned 16 bit,
1239 * - 16S signed 16 bit,
1240 * - 32U unsigned 32 bit,
1241 * - 32S signed 32 bit,
1242 * - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
1243 * \warning 12 bit images appear as 16 bit.
1244 * 24 bit images appear as 8 bit + photochromatic interp ="RGB "
1245 * + Planar Configuration = 0
1246 * @return 0S if nothing found. NOT USABLE file. The caller has to check
1248 std::string File::GetPixelType()
1250 std::string bitsAlloc = GetEntryString(0x0028, 0x0100); // Bits Allocated
1251 if ( bitsAlloc == GDCM_UNFOUND )
1253 gdcmWarningMacro( "Bits Allocated (0028,0100) supposed to be mandatory");
1254 bitsAlloc = "16"; // default and arbitrary value, not to polute the output
1257 else if ( bitsAlloc == "64" )
1261 // useless since we have to bypass a bug ( >8 && < 16)
1262 else if ( bitsAlloc == "12" )
1264 // It will be unpacked
1268 else if ( bitsAlloc == "24" )
1270 // (in order no to be messed up by old RGB images)
1274 int i= atoi(bitsAlloc.c_str()); // fix a bug in some headers
1275 if ( i > 8 && i < 16 )
1281 if( IsSignedPixelData() )
1289 return bitsAlloc + sign;
1293 * \brief Check whether the pixels are signed (1) or UNsigned (0) data.
1294 * \warning The method defaults to false (UNsigned) when tag 0028|0103
1296 * The responsability of checking this value is left to the caller
1297 * (NO transformation is performed on the pixels to make then >0)
1298 * @return True when signed, false when UNsigned
1300 bool File::IsSignedPixelData()
1302 DataEntry *entry = GetDataEntry(0x0028, 0x0103);//"Pixel Representation"
1305 gdcmWarningMacro( "Pixel Representation (0028,0103) supposed to be "
1309 return entry->GetValue(0) != 0;
1313 * \brief Check whether this a monochrome picture (gray levels) or not,
1314 * using "Photometric Interpretation" tag (0x0028,0x0004).
1315 * @return true when "MONOCHROME1" or "MONOCHROME2". False otherwise.
1317 bool File::IsMonochrome()
1319 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1320 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1")
1321 || Util::DicomStringEqual(PhotometricInterp, "MONOCHROME2") )
1325 if ( PhotometricInterp == GDCM_UNFOUND )
1327 gdcmWarningMacro( "Photometric Interpretation (0028,0004) supposed to be "
1334 * \brief Check whether this a MONOCHROME1 picture (high values = dark)
1335 * or not using "Photometric Interpretation" tag (0x0028,0x0004).
1336 * @return true when "MONOCHROME1" . False otherwise.
1338 bool File::IsMonochrome1()
1340 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1341 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1") )
1345 if ( PhotometricInterp == GDCM_UNFOUND )
1347 gdcmWarningMacro( "Photometric Interpretation (0028,0004) : supposed to"
1348 << " be mandatory! ");
1354 * \brief Check whether this a "PALETTE COLOR" picture or not by accessing
1355 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1356 * @return true when "PALETTE COLOR". False otherwise.
1358 bool File::IsPaletteColor()
1360 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1361 if ( PhotometricInterp == "PALETTE COLOR " )
1365 if ( PhotometricInterp == GDCM_UNFOUND )
1367 gdcmDebugMacro( "Not found : Palette color (0028,0004)");
1373 * \brief Check whether this a "YBR_FULL" color picture or not by accessing
1374 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1375 * @return true when "YBR_FULL". False otherwise.
1377 bool File::IsYBRFull()
1379 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1380 if ( PhotometricInterp == "YBR_FULL" )
1384 if ( PhotometricInterp == GDCM_UNFOUND )
1386 gdcmDebugMacro( "Not found : YBR Full (0028,0004)");
1392 * \brief tells us if LUT are used
1393 * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
1394 * are NOT considered as LUT, since nobody knows
1395 * how to deal with them
1396 * Please warn me if you know sbdy that *does* know ... jprx
1397 * @return true if LUT Descriptors and LUT Tables were found
1401 // Check the presence of the LUT Descriptors, and LUT Tables
1403 if ( !GetDocEntry(0x0028,0x1101) )
1407 // LutDescriptorGreen
1408 if ( !GetDocEntry(0x0028,0x1102) )
1412 // LutDescriptorBlue
1413 if ( !GetDocEntry(0x0028,0x1103) )
1417 // Red Palette Color Lookup Table Data
1418 if ( !GetDocEntry(0x0028,0x1201) )
1422 // Green Palette Color Lookup Table Data
1423 if ( !GetDocEntry(0x0028,0x1202) )
1427 // Blue Palette Color Lookup Table Data
1428 if ( !GetDocEntry(0x0028,0x1203) )
1433 // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
1434 // NOT taken into account, but we don't know how to use it ...
1439 * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
1441 * @return Lookup Table number of Bits , 0 by default
1442 * when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
1443 * @ return bit number of each LUT item
1445 int File::GetLUTNbits()
1447 std::vector<std::string> tokens;
1450 //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red
1451 // = Lookup Table Desc-Blue
1452 // Consistency already checked in GetLUTLength
1453 std::string lutDescription = GetEntryString(0x0028,0x1101);
1454 if ( lutDescription == GDCM_UNFOUND )
1459 tokens.clear(); // clean any previous value
1460 Util::Tokenize ( lutDescription, tokens, "\\" );
1461 //LutLength=atoi(tokens[0].c_str());
1462 //LutDepth=atoi(tokens[1].c_str());
1464 lutNbits = atoi( tokens[2].c_str() );
1471 *\brief gets the info from 0028,1052 : Rescale Intercept
1472 * @return Rescale Intercept. defaulted to 0.0 is not found or empty
1474 float File::GetRescaleIntercept()
1476 // 0028 1052 DS IMG Rescale Intercept
1477 DataEntry *entry = GetDataEntry(0x0028, 0x1052);
1480 gdcmWarningMacro( "Missing Rescale Intercept (0028,1052)");
1483 return (float)entry->GetValue(0);
1488 *\brief gets the info from 0028,1053 : Rescale Slope
1489 * @return Rescale Slope. defaulted to 1.0 is not found or empty
1491 float File::GetRescaleSlope()
1493 // 0028 1053 DS IMG Rescale Slope
1494 DataEntry *entry = GetDataEntry(0x0028, 0x1053);
1497 gdcmDebugMacro( "Missing Rescale Slope (0028,1053)");
1500 return (float)entry->GetValue(0);
1504 * \brief This function is intended to user who doesn't want
1505 * to have to manage a LUT and expects to get an RBG Pixel image
1506 * (or a monochrome one, if no LUT found ...)
1507 * \warning to be used with GetImagePixels()
1508 * @return 1 if Gray level, 3 if Color (RGB, YBR, *or PALETTE COLOR*)
1510 int File::GetNumberOfScalarComponents()
1512 if ( GetSamplesPerPixel() == 3 )
1517 // 0028 0100 US IMG Bits Allocated
1518 // (in order no to be messed up by old RGB images)
1519 if ( GetEntryString(0x0028,0x0100) == "24" )
1524 std::string strPhotometricInterpretation = GetEntryString(0x0028,0x0004);
1526 if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
1528 if ( HasLUT() )// PALETTE COLOR is NOT enough
1538 // beware of trailing space at end of string
1539 // DICOM tags are never of odd length
1540 if ( strPhotometricInterpretation == GDCM_UNFOUND ||
1541 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
1542 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
1548 // we assume that *all* kinds of YBR are dealt with
1554 * \brief This function is intended to user that DOESN'T want
1555 * to get RGB pixels image when it's stored as a PALETTE COLOR image
1556 * - the (vtk) user is supposed to know how deal with LUTs -
1557 * \warning to be used with GetImagePixelsRaw()
1558 * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
1560 int File::GetNumberOfScalarComponentsRaw()
1562 // 0028 0100 US IMG Bits Allocated
1563 // (in order no to be messed up by old RGB images)
1564 if ( File::GetEntryString(0x0028,0x0100) == "24" )
1569 // we assume that *all* kinds of YBR are dealt with
1570 return GetSamplesPerPixel();
1574 * \brief Recover the offset (from the beginning of the file)
1575 * of *image* pixels (not *icone image* pixels, if any !)
1576 * @return Pixel Offset
1578 size_t File::GetPixelOffset()
1580 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1583 return pxlElement->GetOffset();
1587 gdcmWarningMacro( "Big trouble : Pixel Element ("
1588 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1594 * \brief Recover the pixel area length (in Bytes)
1595 * @return Pixel Element Length, as stored in the header
1596 * (NOT the memory space necessary to hold the Pixels
1597 * -in case of embeded compressed image-)
1598 * 0 : NOT USABLE file. The caller has to check.
1600 size_t File::GetPixelAreaLength()
1602 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1605 return pxlElement->GetLength();
1609 gdcmWarningMacro( "Big trouble : Pixel Element ("
1610 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1616 * \brief Adds the characteristics of a new element we want to anonymize
1617 * @param group Group number of the target tag.
1618 * @param elem Element number of the target tag.
1619 * @param value new value (string) to substitute with
1621 void File::AddAnonymizeElement (uint16_t group, uint16_t elem,
1622 std::string const &value)
1628 UserAnonymizeList.push_back(el);
1632 * \brief Overwrites in the file the values of the DicomElements
1635 void File::AnonymizeNoLoad()
1637 std::fstream *fp = new std::fstream(Filename.c_str(),
1638 std::ios::in | std::ios::out | std::ios::binary);
1642 uint32_t valLgth = 0;
1643 std::string *spaces;
1644 for (ListElements::iterator it = UserAnonymizeList.begin();
1645 it != UserAnonymizeList.end();
1649 //std::cout << "File::AnonymizeNoLoad -------" << std::hex <<(*it).Group <<"|"<<
1651 // << "[" << (*it).Value << "] "<< std::dec << std::endl;
1652 d = GetDocEntry( (*it).Group, (*it).Elem);
1657 if ( dynamic_cast<SeqEntry *>(d) )
1659 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1663 valLgth = (*it).Value.size();
1667 offset = d->GetOffset();
1668 lgth = d->GetLength();
1670 //std::cout << "lgth " << lgth << " valLgth " << valLgth << std::endl;
1673 spaces = new std::string( lgth-valLgth, ' ');
1674 (*it).Value = (*it).Value + *spaces;
1675 //std::cout << "[" << (*it).Value << "] " << lgth << std::endl;
1678 fp->seekp( offset, std::ios::beg );
1679 fp->write( (*it).Value.c_str(), lgth );
1687 * \brief anonymize a File (remove Patient's personal info passed with
1688 * AddAnonymizeElement()
1689 * \note You cannot Anonymize a DataEntry (to be fixed)
1691 bool File::AnonymizeFile()
1693 // If Anonymisation list is empty, let's perform some basic anonymization
1694 if ( UserAnonymizeList.begin() == UserAnonymizeList.end() )
1696 // If exist, replace by spaces
1697 SetEntryString(" ",0x0010, 0x2154); // Telephone
1698 SetEntryString(" ",0x0010, 0x1040); // Adress
1699 SetEntryString(" ",0x0010, 0x0020); // Patient ID
1701 DocEntry *patientNameHE = GetDocEntry (0x0010, 0x0010);
1703 if ( patientNameHE ) // we replace it by Study Instance UID (why not ?)
1705 std::string studyInstanceUID = GetEntryString (0x0020, 0x000d);
1706 if ( studyInstanceUID != GDCM_UNFOUND )
1708 SetEntryString(studyInstanceUID, 0x0010, 0x0010);
1712 SetEntryString("anonymized", 0x0010, 0x0010);
1719 for (ListElements::iterator it = UserAnonymizeList.begin();
1720 it != UserAnonymizeList.end();
1723 d = GetDocEntry( (*it).Group, (*it).Elem);
1728 if ( dynamic_cast<SeqEntry *>(d) )
1730 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1734 if ( dynamic_cast<DataEntry *>(d) )
1736 gdcmWarningMacro( "To 'Anonymize' a DataEntry, better use AnonymizeNoLoad (FIXME) ");
1740 SetEntryString ((*it).Value, (*it).Group, (*it).Elem);
1744 // In order to make definitively impossible any further identification
1745 // remove or replace all the stuff that contains a Date
1747 //0008 0012 DA ID Instance Creation Date
1748 //0008 0020 DA ID Study Date
1749 //0008 0021 DA ID Series Date
1750 //0008 0022 DA ID Acquisition Date
1751 //0008 0023 DA ID Content Date
1752 //0008 0024 DA ID Overlay Date
1753 //0008 0025 DA ID Curve Date
1754 //0008 002a DT ID Acquisition Datetime
1755 //0018 9074 DT ACQ Frame Acquisition Datetime
1756 //0018 9151 DT ACQ Frame Reference Datetime
1757 //0018 a002 DT ACQ Contribution Date Time
1758 //0020 3403 SH REL Modified Image Date (RET)
1759 //0032 0032 DA SDY Study Verified Date
1760 //0032 0034 DA SDY Study Read Date
1761 //0032 1000 DA SDY Scheduled Study Start Date
1762 //0032 1010 DA SDY Scheduled Study Stop Date
1763 //0032 1040 DA SDY Study Arrival Date
1764 //0032 1050 DA SDY Study Completion Date
1765 //0038 001a DA VIS Scheduled Admission Date
1766 //0038 001c DA VIS Scheduled Discharge Date
1767 //0038 0020 DA VIS Admitting Date
1768 //0038 0030 DA VIS Discharge Date
1769 //0040 0002 DA PRC Scheduled Procedure Step Start Date
1770 //0040 0004 DA PRC Scheduled Procedure Step End Date
1771 //0040 0244 DA PRC Performed Procedure Step Start Date
1772 //0040 0250 DA PRC Performed Procedure Step End Date
1773 //0040 2004 DA PRC Issue Date of Imaging Service Request
1774 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
1775 //0040 4011 DT PRC Expected Completion Date and Time
1776 //0040 a030 DT PRC Verification Date Time
1777 //0040 a032 DT PRC Observation Date Time
1778 //0040 a120 DT PRC DateTime
1779 //0040 a121 DA PRC Date
1780 //0040 a13a DT PRC Referenced Datetime
1781 //0070 0082 DA ??? Presentation Creation Date
1782 //0100 0420 DT ??? SOP Autorization Date and Time
1783 //0400 0105 DT ??? Digital Signature DateTime
1784 //2100 0040 DA PJ Creation Date
1785 //3006 0008 DA SSET Structure Set Date
1786 //3008 0024 DA ??? Treatment Control Point Date
1787 //3008 0054 DA ??? First Treatment Date
1788 //3008 0056 DA ??? Most Recent Treatment Date
1789 //3008 0162 DA ??? Safe Position Exit Date
1790 //3008 0166 DA ??? Safe Position Return Date
1791 //3008 0250 DA ??? Treatment Date
1792 //300a 0006 DA RT RT Plan Date
1793 //300a 022c DA RT Air Kerma Rate Reference Date
1794 //300e 0004 DA RT Review Date
1800 * \brief Performs some consistency checking on various 'File related'
1801 * (as opposed to 'DicomDir related') entries
1802 * then writes in a file all the (Dicom Elements) included the Pixels
1803 * @param fileName file name to write to
1804 * @param writetype type of the file to be written
1805 * (ACR, ExplicitVR, ImplicitVR)
1807 bool File::Write(std::string fileName, FileType writetype)
1809 std::ofstream *fp = new std::ofstream(fileName.c_str(),
1810 std::ios::out | std::ios::binary);
1813 gdcmWarningMacro("Failed to open (write) File: " << fileName.c_str());
1817 // Entry : 0002|0000 = group length -> recalculated
1818 DataEntry *e0000 = GetDataEntry(0x0002,0x0000);
1821 std::ostringstream sLen;
1822 sLen << ComputeGroup0002Length( );
1823 e0000->SetString(sLen.str());
1826 /// \todo FIXME : Derma?.dcm does not have it...let's remove it ?!? JPRx
1827 if( writetype != JPEG )
1829 int i_lgPix = GetEntryLength(GrPixel, NumPixel);
1832 // no (GrPixel, NumPixel) element
1833 std::string s_lgPix = Util::Format("%d", i_lgPix+12);
1834 s_lgPix = Util::DicomString( s_lgPix.c_str() );
1835 InsertEntryString(s_lgPix,GrPixel, 0x0000, "UL");
1838 Document::WriteContent(fp, writetype);
1846 //-----------------------------------------------------------------------------
1850 //-----------------------------------------------------------------------------
1853 * \brief Parse pixel data from disk of [multi-]fragment RLE encoding.
1854 * Compute the RLE extra information and store it in \ref RLEInfo
1855 * for later pixel retrieval usage.
1857 void File::ComputeRLEInfo()
1859 std::string ts = GetTransferSyntax();
1860 if ( !Global::GetTS()->IsRLELossless(ts) )
1865 // Encoded pixel data: for the time being we are only concerned with
1866 // Jpeg or RLE Pixel data encodings.
1867 // As stated in PS 3.5-2003, section 8.2 p44:
1868 // "If sent in Encapsulated Format (i.e. other than the Native Format) the
1869 // value representation OB is used".
1870 // Hence we expect an OB value representation. Concerning OB VR,
1871 // the section PS 3.5-2003, section A.4.c p 58-59, states:
1872 // "For the Value Representations OB and OW, the encoding shall meet the
1873 // following specifications depending on the Data element tag:"
1875 // - the first item in the sequence of items before the encoded pixel
1876 // data stream shall be basic offset table item. The basic offset table
1877 // item value, however, is not required to be present"
1878 ReadEncapsulatedBasicOffsetTable();
1880 // Encapsulated RLE Compressed Images (see PS 3.5-2003, Annex G)
1881 // Loop on the individual frame[s] and store the information
1882 // on the RLE fragments in a RLEFramesInfo.
1883 // Note: - when only a single frame is present, this is a
1885 // - when more than one frame are present, then we are in
1886 // the case of a multi-frame image.
1890 while ( (frameLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1892 // Since we have read the basic offset table, let's check the value were correct
1893 // or else produce a warning:
1894 if ( BasicOffsetTableItemValue )
1896 // If a BasicOffsetTableItemValue was read
1897 uint32_t individualLength = BasicOffsetTableItemValue[i];
1898 assert( individualLength == sum ); // REMOVE that if this is a problem
1899 if( individualLength != sum )
1901 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght" );
1903 sum += frameLength + 8;
1906 // Parse the RLE Header and store the corresponding RLE Segment
1907 // Offset Table information on fragments of this current Frame.
1908 // Note that the fragment pixels themselves are not loaded
1909 // (but just skipped).
1910 long frameOffset = Fp->tellg(); // once per fragment
1912 uint32_t nbRleSegments = ReadInt32();
1913 if ( nbRleSegments > 16 )
1915 // There should be at most 15 segments (refer to RLEFrame class)
1916 gdcmWarningMacro( "Too many segments.");
1919 uint32_t rleSegmentOffsetTable[16];
1920 for( int k = 1; k <= 15; k++ )
1922 rleSegmentOffsetTable[k] = ReadInt32();
1925 // Deduce from both RLE Header and frameLength
1926 // the fragment length, and again store this info
1927 // in a RLEFramesInfo.
1928 long rleSegmentLength[15];
1929 // skipping (not reading) RLE Segments
1930 if ( nbRleSegments > 1)
1932 for(unsigned int k = 1; k <= nbRleSegments-1; k++)
1934 rleSegmentLength[k] = rleSegmentOffsetTable[k+1]
1935 - rleSegmentOffsetTable[k];
1936 SkipBytes(rleSegmentLength[k]);
1940 rleSegmentLength[nbRleSegments] = frameLength
1941 - rleSegmentOffsetTable[nbRleSegments];
1942 SkipBytes(rleSegmentLength[nbRleSegments]);
1944 // Store the collected info
1945 RLEFrame *newFrame = new RLEFrame;
1946 newFrame->SetNumberOfFragments(nbRleSegments);
1947 for( unsigned int uk = 1; uk <= nbRleSegments; uk++ )
1949 newFrame->SetOffset(uk,frameOffset + rleSegmentOffsetTable[uk]);
1950 newFrame->SetLength(uk,rleSegmentLength[uk]);
1952 RLEInfo->AddFrame(newFrame);
1955 // Make sure that we encounter a 'Sequence Delimiter Item'
1956 // at the end of the item :
1957 if ( !ReadTag(0xfffe, 0xe0dd) ) // once per RLE File
1959 gdcmWarningMacro( "No sequence delimiter item at end of RLE item sequence");
1964 * \brief Parse pixel data from disk of [multi-]fragment Jpeg encoding.
1965 * Compute the jpeg extra information (fragment[s] offset[s] and
1966 * length) and store it[them] in \ref JPEGInfo for later pixel
1969 void File::ComputeJPEGFragmentInfo()
1971 // If you need to, look for comments of ComputeRLEInfo().
1972 std::string ts = GetTransferSyntax();
1973 if ( ! Global::GetTS()->IsJPEG(ts) )
1978 ReadEncapsulatedBasicOffsetTable();
1980 // Loop on the fragments[s] and store the parsed information in a
1982 long fragmentLength;
1985 while ( (fragmentLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1987 // Since we have read the basic offset table, let's check the value were correct
1988 // or else produce a warning:
1989 // A.4 Transfer syntaxes for encapsulation of encoded pixel data:
1990 // When the Item Value is present, the Basic Offset Table Item Value shall contain
1991 // concatenated 32-bit unsigned integer values that are byte offsets to the first
1992 // byte of the Item Tag of the first fragment for each frame in the Sequence of
1993 // Items. These offsets are measured from the first byte of the first Item Tag
1994 // following the Basic Offset Table item (See Table A.4-2).
1996 if ( BasicOffsetTableItemValue )
1998 // If a BasicOffsetTableItemValue was read
1999 uint32_t individualLength = BasicOffsetTableItemValue[i];
2000 //assert( individualLength == sum ); // Seems like 00191113.dcm is off by one ??
2001 if( individualLength != sum )
2003 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght:" <<
2004 individualLength << " != " << sum );
2006 sum += fragmentLength + 8;
2010 long fragmentOffset = Fp->tellg(); // Once per fragment
2011 // Store the collected info
2012 JPEGFragment *newFragment = new JPEGFragment;
2013 newFragment->SetOffset(fragmentOffset);
2014 newFragment->SetLength(fragmentLength);
2015 JPEGInfo->AddFragment(newFragment);
2017 SkipBytes(fragmentLength);
2020 // Make sure that we encounter a 'Sequence Delimiter Item'
2021 // at the end of the item :
2022 if ( !ReadTag(0xfffe, 0xe0dd) )
2024 gdcmWarningMacro( "No sequence delimiter item at end of JPEG item sequence");
2029 * \brief Assuming the internal file pointer \ref Document::Fp
2030 * is placed at the beginning of a tag, check whether this
2031 * tag is (TestGroup, TestElem).
2032 * \warning On success the internal file pointer \ref Document::Fp
2033 * is modified to point after the tag.
2034 * On failure (i.e. when the tag wasn't the expected tag
2035 * (TestGroup, TestElem) the internal file pointer
2036 * \ref Document::Fp is restored to it's original position.
2037 * @param testGroup The expected group of the tag.
2038 * @param testElem The expected Element of the tag.
2039 * @return True on success, false otherwise.
2041 bool File::ReadTag(uint16_t testGroup, uint16_t testElem)
2043 long positionOnEntry = Fp->tellg(); // Only when reading fragments
2044 //long currentPosition = positionOnEntry; // On debugging purposes
2046 // Read the Item Tag group and element, and make
2047 // sure they are what we expected:
2048 uint16_t itemTagGroup;
2049 uint16_t itemTagElem;
2052 itemTagGroup = ReadInt16();
2053 itemTagElem = ReadInt16();
2055 catch ( FormatError )
2057 gdcmErrorMacro( "Can not read tag for "
2058 << " We should have found tag ("
2059 << DictEntry::TranslateToKey(testGroup,testElem) << ")"
2064 if ( itemTagGroup != testGroup || itemTagElem != testElem )
2066 // in order not to pollute output we don't warn on 'delimitors'
2067 if (itemTagGroup != 0xfffe || testGroup != 0xfffe )
2068 gdcmWarningMacro( "Wrong Item Tag found:"
2069 << " We should have found tag ("
2070 << DictEntry::TranslateToKey(testGroup,testElem) << ")" << std::endl
2071 << " but instead we encountered tag ("
2072 << DictEntry::TranslateToKey(itemTagGroup,itemTagElem) << ")"
2073 << " at address: " << " 0x(" << std::hex
2074 << (unsigned int)positionOnEntry << std::dec << ")"
2076 Fp->seekg(positionOnEntry, std::ios::beg);
2084 * \brief Assuming the internal file pointer \ref Document::Fp
2085 * is placed at the beginning of a tag (TestGroup, TestElement),
2086 * read the length associated to the Tag.
2087 * \warning On success the internal file pointer \ref Document::Fp
2088 * is modified to point after the tag and it's length.
2089 * On failure (i.e. when the tag wasn't the expected tag
2090 * (TestGroup, TestElement) the internal file pointer
2091 * \ref Document::Fp is restored to it's original position.
2092 * @param testGroup The expected Group of the tag.
2093 * @param testElem The expected Element of the tag.
2094 * @return On success returns the length associated to the tag. On failure
2097 uint32_t File::ReadTagLength(uint16_t testGroup, uint16_t testElem)
2100 if ( !ReadTag(testGroup, testElem) )
2102 // Avoid polutting output
2103 if ( testGroup != 0xfffe )
2104 gdcmErrorMacro( "ReadTag did not succeed for ("
2105 << DictEntry::TranslateToKey(testGroup,testElem)
2110 //// Then read the associated Item Length
2112 // long currentPosition = Fp->tellg(); // save time // JPRx
2113 uint32_t itemLength = ReadInt32();
2114 gdcmDebugMacro( "Basic Item Length is: " << itemLength
2115 // << " at address: " << std::hex << (unsigned int)currentPosition
2121 * \brief When parsing the Pixel Data of an encapsulated file, read
2122 * the basic offset table (when present, and BTW dump it).
2124 void File::ReadEncapsulatedBasicOffsetTable()
2126 //// Read the Basic Offset Table Item Tag length...
2127 uint32_t itemLength = ReadTagLength(0xfffe, 0xe000);
2129 // When present, read the basic offset table itself.
2130 // Notes: - since the presence of this basic offset table is optional
2131 // we can't rely on it for the implementation, and we will simply
2132 // trash it's content (when present).
2133 // - still, when present, we could add some further checks on the
2134 // lengths, but we won't bother with such fuses for the time being.
2135 if ( itemLength != 0 )
2137 char *charBasicOffsetTableItemValue = new char[itemLength];
2138 Fp->read(charBasicOffsetTableItemValue, itemLength);
2139 unsigned int nbEntries = itemLength/4;
2140 assert( nbEntries*4 == itemLength); // Make sure this is a multiple
2141 BasicOffsetTableItemValue = new uint32_t[nbEntries];
2143 for (unsigned int i=0; i < nbEntries; i++ )
2145 BasicOffsetTableItemValue[i] = *((uint32_t*)(&charBasicOffsetTableItemValue[4*i]));
2146 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
2147 uint32_t val = BasicOffsetTableItemValue[i];
2148 BasicOffsetTableItemValue[i]
2149 = ( (val<<24) | ((val<<8) & 0x00ff0000) |
2150 ( (val>>8) & 0x0000ff00) | (val>>24) );
2152 gdcmDebugMacro( "Read one length for: " <<
2153 std::hex << BasicOffsetTableItemValue[i] );
2156 delete[] charBasicOffsetTableItemValue;
2160 // These are the deprecated method that one day should be removed (after the next release)
2162 //#ifndef GDCM_LEGACY_REMOVE
2164 * \ brief Loader. (DEPRECATED : temporaryly kept not to break the API)
2165 * @ param fileName file to be open for parsing
2166 * @ return false if file cannot be open or no swap info was found,
2167 * or no tag was found.
2168 * @deprecated Use the Load() [ + SetLoadMode() ] + SetFileName() functions instead
2171 bool File::Load( std::string const &fileName )
2173 GDCM_LEGACY_REPLACED_BODY(File::Load(std::string), "1.2",
2175 SetFileName( fileName );
2176 if ( ! this->Document::Load( ) )
2179 return DoTheLoadingJob( );
2183 //-----------------------------------------------------------------------------
2186 //-----------------------------------------------------------------------------
2187 } // end namespace gdcm