1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2006/06/21 14:05:00 $
7 Version: $Revision: 1.324 $
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.
146 if ( ! this->Document::Load( ) )
149 return DoTheLoadingJob( );
153 * \brief Does the Loading Job (internal use only)
154 * @return false if file cannot be open or no swap info was found,
155 * or no tag was found.
157 bool File::DoTheLoadingJob( )
159 // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
160 // We may encounter the 'RETired' (0x0028, 0x0200) tag
161 // (Image Location") . This entry contains the number of
162 // the group that contains the pixel data (hence the "Pixel Data"
163 // is found by indirection through the "Image Location").
164 // Inside the group pointed by "Image Location" the searched element
165 // is conventionally the element 0x0010 (when the norm is respected).
166 // When the "Image Location" is missing we default to group 0x7fe0.
167 // Note: this IS the right place for the code
170 const std::string &imgLocation = GetEntryString(0x0028, 0x0200);
171 if ( imgLocation == GDCM_UNFOUND )
178 GrPixel = (uint16_t) atoi( imgLocation.c_str() );
181 // sometimes Image Location value doesn't follow
182 // the supposed processor endianness.
183 // see gdcmData/cr172241.dcm
184 if ( GrPixel == 0xe07f )
189 if ( GrPixel != 0x7fe0 )
191 // This is a kludge for old dirty Philips imager.
199 // Now, we know GrPixel and NumPixel.
200 // Let's create a VirtualDictEntry to allow a further VR modification
201 // and force VR to match with BitsAllocated.
202 DocEntry *entry = GetDocEntry(GrPixel, NumPixel);
205 // Compute the RLE or JPEG info
207 const std::string &ts = GetTransferSyntax();
208 Fp->seekg( entry->GetOffset(), std::ios::beg );
209 if ( Global::GetTS()->IsRLELossless(ts) )
211 else if ( Global::GetTS()->IsJPEG(ts) )
212 ComputeJPEGFragmentInfo();
215 // Create a new DataEntry to change the DictEntry
216 // The changed DictEntry will have
217 // - a correct PixelVR OB or OW)
218 // - the name to "Pixel Data"
221 //==> Just change the VR !
224 DataEntry *oldEntry = dynamic_cast<DataEntry *>(entry);
228 // 8 bits allocated is a 'O Bytes' , as well as 24 (old ACR-NEMA RGB)
229 // more than 8 (i.e 12, 16) is a 'O Words'
230 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
235 // Change only made if usefull
236 if ( PixelVR != oldEntry->GetVR() )
238 //DictEntry* newDict = DictEntry::New(GrPixel,NumPixel,
239 // PixelVR,"1","Pixel Data");
240 //DataEntry *newEntry = DataEntry::New(newDict);
242 //newEntry->Copy(entry);
243 //newEntry->SetBinArea(oldEntry->GetBinArea(),oldEntry->IsSelfArea());
244 //oldEntry->SetSelfArea(false);
246 //RemoveEntry(oldEntry);
247 //AddEntry(newEntry);
248 //newEntry->Delete();
254 // 8 bits allocated is a 'OB(ytes)' , as well as 24 (old ACR-NEMA RGB)
255 // more than 8 (i.e 12, 16) is a 'OW(ords)'
256 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
260 // Change only made if usefull
261 if ( PixelVR != entry->GetVR() )
263 entry->SetVR(PixelVR);
270 * \brief This predicate, based on hopefully reasonable heuristics,
271 * decides whether or not the current File was properly parsed
272 * and contains the mandatory information for being considered as
273 * a well formed and usable Dicom/Acr File.
274 * @return true when File is the one of a reasonable Dicom/Acr file,
277 bool File::IsReadable()
279 if ( !Document::IsReadable() )
284 const std::string &res = GetEntryString(0x0028, 0x0005);
285 if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 )
287 gdcmWarningMacro("Wrong Image Dimensions" << res);
288 return false; // Image Dimensions
290 bool b0028_0100 = true;
291 if ( !GetDocEntry(0x0028, 0x0100) )
293 gdcmWarningMacro("Bits Allocated (0028|0100) not found");
294 //return false; // "Bits Allocated"
297 bool b0028_0101 = true;
298 if ( !GetDocEntry(0x0028, 0x0101) )
300 gdcmWarningMacro("Bits Stored (0028|0101) not found");
301 //return false; // "Bits Stored"
304 bool b0028_0102 = true;
305 if ( !GetDocEntry(0x0028, 0x0102) )
307 gdcmWarningMacro("Hight Bit (0028|0102) not found");
308 //return false; // "High Bit"
311 bool b0028_0103 = true;
312 if ( !GetDocEntry(0x0028, 0x0103) )
314 gdcmWarningMacro("Pixel Representation (0028|0103) not found");
315 //return false; // "Pixel Representation" i.e. 'Sign' ( 0 : unsigned, 1 : signed)
319 if ( !b0028_0100 && !b0028_0101 && !b0028_0102 && !b0028_0103)
321 gdcmWarningMacro("Too much mandatory Tags missing !");
325 if ( !GetDocEntry(GrPixel, NumPixel) )
327 gdcmWarningMacro("Pixel Dicom Element " << std::hex <<
328 GrPixel << "|" << NumPixel << "not found");
329 return false; // Pixel Dicom Element not found :-(
335 * \brief gets the info from 0020,0013 : Image Number else 0.
336 * @return image number
338 int File::GetImageNumber()
340 //0020 0013 : Image Number
341 std::string strImNumber = GetEntryString(0x0020,0x0013);
342 if ( strImNumber != GDCM_UNFOUND )
344 return atoi( strImNumber.c_str() );
350 * \brief gets the info from 0008,0060 : Modality
351 * @return Modality Type
353 ModalityType File::GetModality()
355 // 0008 0060 : Modality
356 std::string strModality = GetEntryString(0x0008,0x0060);
357 if ( strModality != GDCM_UNFOUND )
359 if ( strModality.find("AU") < strModality.length()) return AU;
360 else if ( strModality.find("AS") < strModality.length()) return AS;
361 else if ( strModality.find("BI") < strModality.length()) return BI;
362 else if ( strModality.find("CF") < strModality.length()) return CF;
363 else if ( strModality.find("CP") < strModality.length()) return CP;
364 else if ( strModality.find("CR") < strModality.length()) return CR;
365 else if ( strModality.find("CT") < strModality.length()) return CT;
366 else if ( strModality.find("CS") < strModality.length()) return CS;
367 else if ( strModality.find("DD") < strModality.length()) return DD;
368 else if ( strModality.find("DF") < strModality.length()) return DF;
369 else if ( strModality.find("DG") < strModality.length()) return DG;
370 else if ( strModality.find("DM") < strModality.length()) return DM;
371 else if ( strModality.find("DS") < strModality.length()) return DS;
372 else if ( strModality.find("DX") < strModality.length()) return DX;
373 else if ( strModality.find("ECG") < strModality.length()) return ECG;
374 else if ( strModality.find("EPS") < strModality.length()) return EPS;
375 else if ( strModality.find("FA") < strModality.length()) return FA;
376 else if ( strModality.find("FS") < strModality.length()) return FS;
377 else if ( strModality.find("HC") < strModality.length()) return HC;
378 else if ( strModality.find("HD") < strModality.length()) return HD;
379 else if ( strModality.find("LP") < strModality.length()) return LP;
380 else if ( strModality.find("LS") < strModality.length()) return LS;
381 else if ( strModality.find("MA") < strModality.length()) return MA;
382 else if ( strModality.find("MR") < strModality.length()) return MR;
383 else if ( strModality.find("NM") < strModality.length()) return NM;
384 else if ( strModality.find("OT") < strModality.length()) return OT;
385 else if ( strModality.find("PT") < strModality.length()) return PT;
386 else if ( strModality.find("RF") < strModality.length()) return RF;
387 else if ( strModality.find("RG") < strModality.length()) return RG;
388 else if ( strModality.find("RTDOSE")
389 < strModality.length()) return RTDOSE;
390 else if ( strModality.find("RTIMAGE")
391 < strModality.length()) return RTIMAGE;
392 else if ( strModality.find("RTPLAN")
393 < strModality.length()) return RTPLAN;
394 else if ( strModality.find("RTSTRUCT")
395 < strModality.length()) return RTSTRUCT;
396 else if ( strModality.find("SM") < strModality.length()) return SM;
397 else if ( strModality.find("ST") < strModality.length()) return ST;
398 else if ( strModality.find("TG") < strModality.length()) return TG;
399 else if ( strModality.find("US") < strModality.length()) return US;
400 else if ( strModality.find("VF") < strModality.length()) return VF;
401 else if ( strModality.find("XA") < strModality.length()) return XA;
402 else if ( strModality.find("XC") < strModality.length()) return XC;
406 /// \todo throw error return value ???
407 /// specified <> unknown in our database
415 * \brief Retrieve the number of columns of image.
416 * @return The encountered size when found, 0 by default.
417 * 0 means the file is NOT USABLE. The caller will have to check
421 DataEntry *entry = GetDataEntry(0x0028,0x0011);
423 return (int)entry->GetValue(0);
428 * \brief Retrieve the number of lines of image.
429 * \warning The defaulted value is 1 as opposed to File::GetXSize()
430 * @return The encountered size when found, 1 by default
431 * (The ACR-NEMA file contains a Signal, not an Image).
435 DataEntry *entry = GetDataEntry(0x0028,0x0010);
437 return (int)entry->GetValue(0);
444 // The Rows (0028,0010) entry was optional for ACR/NEMA.
445 // (at least some images didn't have it.)
446 // It might hence be a signal (1D image). So we default to 1:
451 * \brief Retrieve the number of planes of volume or the number
452 * of frames of a multiframe.
453 * \warning When present we consider the "Number of Frames" as the third
454 * dimension. When missing we consider the third dimension as
455 * being the ACR-NEMA "Planes" tag content.
456 * @return The encountered size when found, 1 by default (single image).
460 // Both DicomV3 and ACR/Nema consider the "Number of Frames"
461 // as the third dimension.
462 DataEntry *entry = GetDataEntry(0x0028,0x0008);
464 return (int)entry->GetValue(0);
466 // We then consider the "Planes" entry as the third dimension
467 entry = GetDataEntry(0x0028,0x0012);
469 return (int)entry->GetValue(0);
474 * \brief Retrieve the -unnormalized- number of 'times' of '4D image'.
475 * User has to tell gdcm the location of this '4th Dimension component'
476 * using SetFourthDimensionLocation() method before.
477 * \warning The defaulted value is 1.
478 * @return The encountered size when found, 1 by default
479 * (The file doesn't contain a '4D image'.).
483 if (FourthDimensionLocation == TagKey(0,0) )// 4D location is not set : not a 4D object
486 DataEntry *entry = GetDataEntry(FourthDimensionLocation.GetGroup(),
487 FourthDimensionLocation.GetElement() );
490 gdcmWarningMacro( " FourthDimensionLocation not found at : " <<
491 std::hex << FourthDimensionLocation.GetGroup()
492 << "|" << FourthDimensionLocation.GetElement());
497 return (int)entry->GetValue(0);
502 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
503 * then 0028,0030 : Pixel Spacing
505 * @return X dimension of a pixel
507 float File::GetXSpacing()
509 float xspacing = 1.0;
514 From:David Clunie - view profile
515 Date:Wed, May 24 2006 1:12 pm
516 Email:David Clunie <dclu...@dclunie.com>
517 Groups:comp.protocols.dicom
519 The short answer is that:
521 - (0018,1164) describes a spacing equivalent to that which
522 would be measured off a film in projection radiography
524 - (0018,7022) does not describe the image pixels themselves,
525 since detector elements may have been binned to produce
528 - (0018,7020) may be different from (0018,7022) since there
529 may be non-sensitive material separating individual
530 detectors (i.e. the size is smaller than the spacing
533 Only (0018,1164) is relevant when measuring things; the
534 detector-specific attributes are there to describe the
539 PS. For ultrasound you need to use Region Calibration.
543 It *SHOULD* first find the IOD and then deduce which tags to read
544 Eg: Cross section this is in Pixel Spacing (0028,0030)
545 CR is in Imager Pixel Spacing (0018,1164)
546 US is in Pixel Aspect Ratio (0028,0034)
548 (3002,0011) Image Plane Pixel Spacing
549 (3002,0012) RT Image Position
551 (3004,000c) for deducing Z spacing
554 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
556 /// \todo check the various SOP Class
557 /// to get the Pixel Spacing at the proper location
559 ///\todo find images to check if it *actually* works
561 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
562 // Ultrasound Image Storage (Retired)
563 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
564 // Ultrasound Image Storage
565 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
566 // Ultrasound Multi-Frame Storage (Retired)
567 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
568 // Ultrasound Multi-FrameImage Storage
570 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
571 // - guess how to deduce the spacing (FOV ?, ??)
573 entry = GetDataEntry(0x0028,0x0034);
576 nbValue = entry->GetValueCount();
578 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
579 << "has a wrong number of values :" << nbValue);
581 xspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
589 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.1") )
590 // Computed Radiography Image Storage
592 // CR is in Imager Pixel Spacing (0018,1164)//
595 // go on with old method ...
596 // ---------------------
597 // To follow David Clunie's advice, we first check ImagerPixelSpacing
599 entry = GetDataEntry(0x0018,0x1164);
602 nbValue = entry->GetValueCount();
603 // Can't use IsValueCountValid because of the complex heuristic.
605 gdcmWarningMacro("ImagerPixelSpacing (0x0018,0x1164) "
606 << "has a wrong number of values :" << nbValue);
609 xspacing = (float)entry->GetValue(2);
610 else if( nbValue >= 2 )
611 xspacing = (float)entry->GetValue(1);
613 xspacing = (float)entry->GetValue(0);
615 if ( xspacing == 0.0 )
621 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
624 entry = GetDataEntry(0x0028,0x0030);
627 nbValue = entry->GetValueCount();
629 gdcmWarningMacro("PixelSpacing (0x0018,0x0030) "
630 << "has a wrong number of values :" << nbValue);
633 xspacing = (float)entry->GetValue(2);
634 else if( nbValue >= 2 )
635 xspacing = (float)entry->GetValue(1);
637 xspacing = (float)entry->GetValue(0);
639 if ( xspacing == 0.0 )
645 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
651 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
652 * then from 0028,0030 : Pixel Spacing
654 * @return Y dimension of a pixel
656 float File::GetYSpacing()
663 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
665 /// \todo check the various SOP Class
666 /// to get the Pixel Spacing at the proper location
668 ///\todo find images to check if it *actually* works
670 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
671 // Ultrasound Image Storage (Retired)
672 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
673 // Ultrasound Image Storage
674 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
675 // Ultrasound Multi-Frame Storage (Retired)
676 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
677 // Ultrasound Multi-FrameImage Storage
679 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
680 // - no way to deduce the spacing/
682 entry = GetDataEntry(0x0028,0x0034);
685 nbValue = entry->GetValueCount();
687 yspacing = (float)entry->GetValue(0)/(float)entry->GetValue(1);
688 //std::cout << "ys " << yspacing << std::endl;
693 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
694 << "has a wrong number of values :" << nbValue);
698 else if (nbValue == 1 ) {
699 yspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
709 // go on with old method ...
710 // ---------------------
711 // To follow David Clunie's advice, we first check ImagerPixelSpacing
713 // To follow David Clunie's advice, we first check ImagerPixelSpacing
715 entry = GetDataEntry(0x0018,0x1164);
718 yspacing = (float)entry->GetValue(0);
720 if ( yspacing == 0.0 )
726 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
729 entry = GetDataEntry(0x0028,0x0030);
732 yspacing = (float)entry->GetValue(0);
734 if ( yspacing == 0.0 )
740 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
747 * \brief gets the info from 0018,0088 : Space Between Slices
748 * else from 0018,0050 : Slice Thickness
751 * When an element is missing, we suppose slices join together
752 * (no overlapping, no interslice gap) but we have no way to check it !
753 * For *Dicom* images, ZSpacing *should be* calculated using
754 * XOrigin, YOrigin, ZOrigin (of the top left image corner)
755 * of 2 consecutive images, and the Orientation
756 * Computing ZSpacing on a single image is not really meaningfull !
757 * @return Z dimension of a voxel-to be
759 float File::GetZSpacing()
762 float zspacing = 1.0f;
764 // Spacing Between Slices : distance between the middle of 2 slices
766 // jointives (Spacing between Slices = Slice Thickness)
767 // overlapping (Spacing between Slices < Slice Thickness)
768 // disjointes (Spacing between Slices > Slice Thickness)
769 // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
770 // It only concerns the MRI guys, not people wanting to visualize volumes
771 // If Spacing Between Slices is missing,
772 // we suppose slices joint together
773 DataEntry *entry = GetDataEntry(0x0018,0x0088);
775 { zspacing = (float)entry->GetValue(0);
777 if ( zspacing == 0.0 )
782 gdcmWarningMacro("Unfound Spacing Between Slices (0018,0088)");
784 // if no 'Spacing Between Slices' is found,
785 // we assume slices join together
786 // (no overlapping, no interslice gap)
787 entry = GetDataEntry(0x0018,0x0050);
790 zspacing = (float)entry->GetValue(0);
792 if ( zspacing == 0.0 )
797 gdcmWarningMacro("Unfound Slice Thickness (0018,0050)");
799 // if no 'Spacing Between Slices' is found,
800 // we assume slices join together
801 // (no overlapping, no interslice gap)
802 entry = GetDataEntry(0x3004,0x000c);
805 float z1 = (float)entry->GetValue(0);
806 float z2 = (float)entry->GetValue(1);
807 zspacing = z2 - z1; // can be negative...
809 if ( zspacing == 0.0 )
818 * \brief gets the info from 0020,0032 : Image Position Patient
819 * else from 0020,0030 : Image Position (RET)
821 * @return up-left image corner X position
823 float File::GetXOrigin()
825 DataEntry *entry = GetDataEntry(0x0020,0x0032);
828 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
829 entry = GetDataEntry(0x0020,0x0030);
832 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
837 if( entry->GetValueCount() == 3 )
839 if (!entry->IsValueCountValid() )
841 gdcmErrorMacro( "Invalid Value Count" );
843 return (float)entry->GetValue(0);
849 * \brief gets the info from 0020,0032 : Image Position Patient
850 * else from 0020,0030 : Image Position (RET)
852 * @return up-left image corner Y position
854 float File::GetYOrigin()
856 DataEntry *entry = GetDataEntry(0x0020,0x0032);
859 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
860 entry = GetDataEntry(0x0020,0x0030);
863 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
868 if( entry->GetValueCount() == 3 )
870 if (!entry->IsValueCountValid() )
872 gdcmErrorMacro( "Invalid Value Count" );
874 return (float)entry->GetValue(1);
880 * \brief gets the info from 0020,0032 : Image Position Patient
881 * else from 0020,0030 : Image Position (RET)
882 * else from 0020,1041 : Slice Location
883 * else from 0020,0050 : Location
885 * @return up-left image corner Z position
887 float File::GetZOrigin()
889 DataEntry *entry = GetDataEntry(0x0020,0x0032);
892 if( entry->GetValueCount() == 3 )
894 if (!entry->IsValueCountValid() )
896 gdcmErrorMacro( "Invalid Value Count" );
898 return (float)entry->GetValue(2);
900 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)");
904 entry = GetDataEntry(0x0020,0x0030);
907 if( entry->GetValueCount() == 3 )
909 if (!entry->IsValueCountValid() )
911 gdcmErrorMacro( "Invalid Value Count" );
913 return (float)entry->GetValue(2);
915 gdcmWarningMacro( "Wrong Image Position (RET) (0020,0030)");
919 // for *very* old ACR-NEMA images
920 entry = GetDataEntry(0x0020,0x1041);
923 if( entry->GetValueCount() == 1 )
925 if (!entry->IsValueCountValid() )
927 gdcmErrorMacro( "Invalid Value Count" );
929 return (float)entry->GetValue(0); // VM=1 !
931 gdcmWarningMacro( "Wrong Slice Location (0020,1041)");
935 entry = GetDataEntry(0x0020,0x0050);
938 if( entry->GetValueCount() == 1 )
940 if (!entry->IsValueCountValid() )
942 gdcmErrorMacro( "Invalid Value Count" );
944 return (float)entry->GetValue(0);
946 gdcmWarningMacro( "Wrong Location (0020,0050)");
949 return 0.; // Hopeless
953 * \brief gets the info from 0020,0037 : Image Orientation Patient
954 * or from 0020 0035 : Image Orientation (RET)
956 * (needed to organize DICOM files based on their x,y,z position)
958 * @param iop adress of the (6)float array to receive values.
959 * (defaulted as 1.,0.,0.,0.,1.,0. if nothing -or inconsistent stuff-
961 * @return true when one of the tag -with consistent values- is found
962 * false when nothing or inconsistent stuff - is found
964 bool File::GetImageOrientationPatient( float iop[6] )
966 std::string strImOriPat;
967 //iop is supposed to be float[6]
968 iop[0] = iop[4] = 1.;
969 iop[1] = iop[2] = iop[3] = iop[5] = 0.;
971 // 0020 0037 DS REL Image Orientation (Patient)
972 if ( (strImOriPat = GetEntryString(0x0020,0x0037)) != GDCM_UNFOUND )
974 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
975 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
977 gdcmWarningMacro( "Wrong Image Orientation Patient (0020,0037)."
978 << " Less than 6 values were found." );
984 // 0020 0035 DS REL Image Orientation (RET)
985 else if ( (strImOriPat = GetEntryString(0x0020,0x0035)) != GDCM_UNFOUND )
987 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
988 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
990 gdcmWarningMacro( "wrong Image Orientation Patient (0020,0035). "
991 << "Less than 6 values were found." );
1000 * \brief Retrieve the number of Bits Stored (actually used)
1001 * (as opposed to number of Bits Allocated)
1002 * @return The encountered number of Bits Stored, 0 by default.
1003 * 0 means the file is NOT USABLE. The caller has to check it !
1005 int File::GetBitsStored()
1007 DataEntry *entry = GetDataEntry(0x0028,0x0101);
1010 gdcmWarningMacro("BitsStored (0028,0101) is supposed to be mandatory");
1013 return (int)entry->GetValue(0);
1017 * \brief Retrieve the number of Bits Allocated
1018 * (8, 12 -compacted ACR-NEMA files-, 16, 24 -old RGB ACR-NEMA files-,)
1019 * @return The encountered Number of Bits Allocated, 0 by default.
1020 * 0 means the file is NOT USABLE. The caller has to check it !
1022 int File::GetBitsAllocated()
1024 DataEntry *entry = GetDataEntry(0x0028,0x0100);
1027 gdcmWarningMacro("BitsAllocated (0028,0100) is supposed to be mandatory");
1030 return (int)entry->GetValue(0);
1034 * \brief Retrieve the high bit position.
1035 * \warning The method defaults to 0 when information is missing.
1036 * The responsability of checking this value is left to the caller.
1037 * @return The high bit position when present. 0 when missing.
1039 int File::GetHighBitPosition()
1041 DataEntry *entry = GetDataEntry(0x0028,0x0102);
1044 gdcmWarningMacro("HighBitPosition (0028,0102) is supposed to be mandatory");
1047 return (int)entry->GetValue(0);
1051 * \brief Retrieve the number of Samples Per Pixel
1052 * (1 : gray level, 3 : RGB/YBR -1 or 3 Planes-)
1053 * @return The encountered number of Samples Per Pixel, 1 by default.
1054 * (we assume Gray level Pixels)
1056 int File::GetSamplesPerPixel()
1058 DataEntry *entry = GetDataEntry(0x0028,0x0002);
1061 gdcmWarningMacro("SamplesPerPixel (0028,0002) is supposed to be mandatory");
1062 return 1; // Well, it's supposed to be mandatory ...
1063 // but sometimes it's missing : *we* assume Gray pixels
1065 return (int)entry->GetValue(0);
1069 * \brief Retrieve the Planar Configuration for RGB images
1070 * (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
1071 * @return The encountered Planar Configuration, 0 by default.
1073 int File::GetPlanarConfiguration()
1075 DataEntry *entry = GetDataEntry(0x0028,0x0006);
1080 return (int)entry->GetValue(0);
1084 * \brief Return the size (in bytes) of a single pixel of data.
1085 * @return The size in bytes of a single pixel of data; 0 by default
1086 * 0 means the file is NOT USABLE; the caller will have to check
1088 int File::GetPixelSize()
1090 // 0028 0100 US IMG Bits Allocated
1091 // (in order no to be messed up by old ACR-NEMA RGB images)
1092 assert( !(GetEntryString(0x0028,0x0100) == "24") );
1094 std::string pixelType = GetPixelType();
1095 if ( pixelType == "8U" || pixelType == "8S" )
1099 if ( pixelType == "16U" || pixelType == "16S")
1103 if ( pixelType == "32U" || pixelType == "32S")
1107 if ( pixelType == "FD" )
1111 gdcmWarningMacro( "Unknown pixel type: " << pixelType);
1116 * \brief Build the Pixel Type of the image.
1117 * Possible values are:
1118 * - 8U unsigned 8 bit,
1119 * - 8S signed 8 bit,
1120 * - 16U unsigned 16 bit,
1121 * - 16S signed 16 bit,
1122 * - 32U unsigned 32 bit,
1123 * - 32S signed 32 bit,
1124 * - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
1125 * \warning 12 bit images appear as 16 bit.
1126 * 24 bit images appear as 8 bit + photochromatic interp ="RGB "
1127 * + Planar Configuration = 0
1128 * @return 0S if nothing found. NOT USABLE file. The caller has to check
1130 std::string File::GetPixelType()
1132 std::string bitsAlloc = GetEntryString(0x0028, 0x0100); // Bits Allocated
1133 if ( bitsAlloc == GDCM_UNFOUND )
1135 gdcmWarningMacro( "Bits Allocated (0028,0100) supposed to be mandatory");
1136 bitsAlloc = "16"; // default and arbitrary value, not to polute the output
1139 if ( bitsAlloc == "64" )
1143 else if ( bitsAlloc == "12" )
1145 // It will be unpacked
1148 else if ( bitsAlloc == "24" )
1150 // (in order no to be messed up by old RGB images)
1155 if( IsSignedPixelData() )
1163 return bitsAlloc + sign;
1167 * \brief Check whether the pixels are signed (1) or UNsigned (0) data.
1168 * \warning The method defaults to false (UNsigned) when tag 0028|0103
1170 * The responsability of checking this value is left to the caller
1171 * (NO transformation is performed on the pixels to make then >0)
1172 * @return True when signed, false when UNsigned
1174 bool File::IsSignedPixelData()
1176 DataEntry *entry = GetDataEntry(0x0028, 0x0103);//"Pixel Representation"
1179 gdcmWarningMacro( "Pixel Representation (0028,0103) supposed to be "
1183 return entry->GetValue(0) != 0;
1187 * \brief Check whether this a monochrome picture (gray levels) or not,
1188 * using "Photometric Interpretation" tag (0x0028,0x0004).
1189 * @return true when "MONOCHROME1" or "MONOCHROME2". False otherwise.
1191 bool File::IsMonochrome()
1193 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1194 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1")
1195 || Util::DicomStringEqual(PhotometricInterp, "MONOCHROME2") )
1199 if ( PhotometricInterp == GDCM_UNFOUND )
1201 gdcmWarningMacro( "Photometric Interpretation (0028,0004) supposed to be "
1208 * \brief Check whether this a MONOCHROME1 picture (high values = dark)
1209 * or not using "Photometric Interpretation" tag (0x0028,0x0004).
1210 * @return true when "MONOCHROME1" . False otherwise.
1212 bool File::IsMonochrome1()
1214 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1215 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1") )
1219 if ( PhotometricInterp == GDCM_UNFOUND )
1221 gdcmWarningMacro( "Photometric Interpretation (0028,0004) : supposed to"
1222 << " be mandatory! ");
1228 * \brief Check whether this a "PALETTE COLOR" picture or not by accessing
1229 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1230 * @return true when "PALETTE COLOR". False otherwise.
1232 bool File::IsPaletteColor()
1234 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1235 if ( PhotometricInterp == "PALETTE COLOR " )
1239 if ( PhotometricInterp == GDCM_UNFOUND )
1241 gdcmDebugMacro( "Not found : Palette color (0028,0004)");
1247 * \brief Check whether this a "YBR_FULL" color picture or not by accessing
1248 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1249 * @return true when "YBR_FULL". False otherwise.
1251 bool File::IsYBRFull()
1253 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1254 if ( PhotometricInterp == "YBR_FULL" )
1258 if ( PhotometricInterp == GDCM_UNFOUND )
1260 gdcmDebugMacro( "Not found : YBR Full (0028,0004)");
1266 * \brief tells us if LUT are used
1267 * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
1268 * are NOT considered as LUT, since nobody knows
1269 * how to deal with them
1270 * Please warn me if you know sbdy that *does* know ... jprx
1271 * @return true if LUT Descriptors and LUT Tables were found
1275 // Check the presence of the LUT Descriptors, and LUT Tables
1277 if ( !GetDocEntry(0x0028,0x1101) )
1281 // LutDescriptorGreen
1282 if ( !GetDocEntry(0x0028,0x1102) )
1286 // LutDescriptorBlue
1287 if ( !GetDocEntry(0x0028,0x1103) )
1291 // Red Palette Color Lookup Table Data
1292 if ( !GetDocEntry(0x0028,0x1201) )
1296 // Green Palette Color Lookup Table Data
1297 if ( !GetDocEntry(0x0028,0x1202) )
1301 // Blue Palette Color Lookup Table Data
1302 if ( !GetDocEntry(0x0028,0x1203) )
1307 // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
1308 // NOT taken into account, but we don't know how to use it ...
1313 * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
1315 * @return Lookup Table number of Bits , 0 by default
1316 * when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
1317 * @ return bit number of each LUT item
1319 int File::GetLUTNbits()
1321 std::vector<std::string> tokens;
1324 //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red
1325 // = Lookup Table Desc-Blue
1326 // Consistency already checked in GetLUTLength
1327 std::string lutDescription = GetEntryString(0x0028,0x1101);
1328 if ( lutDescription == GDCM_UNFOUND )
1333 tokens.clear(); // clean any previous value
1334 Util::Tokenize ( lutDescription, tokens, "\\" );
1335 //LutLength=atoi(tokens[0].c_str());
1336 //LutDepth=atoi(tokens[1].c_str());
1338 lutNbits = atoi( tokens[2].c_str() );
1345 *\brief gets the info from 0028,1052 : Rescale Intercept
1346 * @return Rescale Intercept. defaulted to 0.0 is not found or empty
1348 float File::GetRescaleIntercept()
1350 // 0028 1052 DS IMG Rescale Intercept
1351 DataEntry *entry = GetDataEntry(0x0028, 0x1052);
1354 gdcmWarningMacro( "Missing Rescale Intercept (0028,1052)");
1357 return (float)entry->GetValue(0);
1362 *\brief gets the info from 0028,1053 : Rescale Slope
1363 * @return Rescale Slope. defaulted to 1.0 is not found or empty
1365 float File::GetRescaleSlope()
1367 // 0028 1053 DS IMG Rescale Slope
1368 DataEntry *entry = GetDataEntry(0x0028, 0x1053);
1371 gdcmDebugMacro( "Missing Rescale Slope (0028,1053)");
1374 return (float)entry->GetValue(0);
1378 * \brief This function is intended to user who doesn't want
1379 * to have to manage a LUT and expects to get an RBG Pixel image
1380 * (or a monochrome one, if no LUT found ...)
1381 * \warning to be used with GetImagePixels()
1382 * @return 1 if Gray level, 3 if Color (RGB, YBR, *or PALETTE COLOR*)
1384 int File::GetNumberOfScalarComponents()
1386 if ( GetSamplesPerPixel() == 3 )
1391 // 0028 0100 US IMG Bits Allocated
1392 // (in order no to be messed up by old RGB images)
1393 if ( GetEntryString(0x0028,0x0100) == "24" )
1398 std::string strPhotometricInterpretation = GetEntryString(0x0028,0x0004);
1400 if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
1402 if ( HasLUT() )// PALETTE COLOR is NOT enough
1412 // beware of trailing space at end of string
1413 // DICOM tags are never of odd length
1414 if ( strPhotometricInterpretation == GDCM_UNFOUND ||
1415 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
1416 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
1422 // we assume that *all* kinds of YBR are dealt with
1428 * \brief This function is intended to user that DOESN'T want
1429 * to get RGB pixels image when it's stored as a PALETTE COLOR image
1430 * - the (vtk) user is supposed to know how deal with LUTs -
1431 * \warning to be used with GetImagePixelsRaw()
1432 * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
1434 int File::GetNumberOfScalarComponentsRaw()
1436 // 0028 0100 US IMG Bits Allocated
1437 // (in order no to be messed up by old RGB images)
1438 if ( File::GetEntryString(0x0028,0x0100) == "24" )
1443 // we assume that *all* kinds of YBR are dealt with
1444 return GetSamplesPerPixel();
1448 * \brief Recover the offset (from the beginning of the file)
1449 * of *image* pixels (not *icone image* pixels, if any !)
1450 * @return Pixel Offset
1452 size_t File::GetPixelOffset()
1454 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1457 return pxlElement->GetOffset();
1461 gdcmWarningMacro( "Big trouble : Pixel Element ("
1462 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1468 * \brief Recover the pixel area length (in Bytes)
1469 * @return Pixel Element Length, as stored in the header
1470 * (NOT the memory space necessary to hold the Pixels
1471 * -in case of embeded compressed image-)
1472 * 0 : NOT USABLE file. The caller has to check.
1474 size_t File::GetPixelAreaLength()
1476 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1479 return pxlElement->GetLength();
1483 gdcmWarningMacro( "Big trouble : Pixel Element ("
1484 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1490 * \brief Adds the characteristics of a new element we want to anonymize
1491 * @param group Group number of the target tag.
1492 * @param elem Element number of the target tag.
1493 * @param value new value (string) to substitute with
1495 void File::AddAnonymizeElement (uint16_t group, uint16_t elem,
1496 std::string const &value)
1502 UserAnonymizeList.push_back(el);
1506 * \brief Overwrites in the file the values of the DicomElements
1509 void File::AnonymizeNoLoad()
1511 std::fstream *fp = new std::fstream(Filename.c_str(),
1512 std::ios::in | std::ios::out | std::ios::binary);
1516 uint32_t valLgth = 0;
1517 std::string *spaces;
1518 for (ListElements::iterator it = UserAnonymizeList.begin();
1519 it != UserAnonymizeList.end();
1523 //std::cout << "File::AnonymizeNoLoad -------" << std::hex <<(*it).Group <<"|"<<
1525 // << "[" << (*it).Value << "] "<< std::dec << std::endl;
1526 d = GetDocEntry( (*it).Group, (*it).Elem);
1531 if ( dynamic_cast<SeqEntry *>(d) )
1533 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1537 valLgth = (*it).Value.size();
1541 offset = d->GetOffset();
1542 lgth = d->GetLength();
1544 //std::cout << "lgth " << lgth << " valLgth " << valLgth << std::endl;
1547 spaces = new std::string( lgth-valLgth, ' ');
1548 (*it).Value = (*it).Value + *spaces;
1549 //std::cout << "[" << (*it).Value << "] " << lgth << std::endl;
1552 fp->seekp( offset, std::ios::beg );
1553 fp->write( (*it).Value.c_str(), lgth );
1561 * \brief anonymize a File (remove Patient's personal info passed with
1562 * AddAnonymizeElement()
1563 * \note You cannot Anonymize a DataEntry (to be fixed)
1565 bool File::AnonymizeFile()
1567 // If Anonymisation list is empty, let's perform some basic anonymization
1568 if ( UserAnonymizeList.begin() == UserAnonymizeList.end() )
1570 // If exist, replace by spaces
1571 SetEntryString(" ",0x0010, 0x2154); // Telephone
1572 SetEntryString(" ",0x0010, 0x1040); // Adress
1573 SetEntryString(" ",0x0010, 0x0020); // Patient ID
1575 DocEntry *patientNameHE = GetDocEntry (0x0010, 0x0010);
1577 if ( patientNameHE ) // we replace it by Study Instance UID (why not ?)
1579 std::string studyInstanceUID = GetEntryString (0x0020, 0x000d);
1580 if ( studyInstanceUID != GDCM_UNFOUND )
1582 SetEntryString(studyInstanceUID, 0x0010, 0x0010);
1586 SetEntryString("anonymized", 0x0010, 0x0010);
1593 for (ListElements::iterator it = UserAnonymizeList.begin();
1594 it != UserAnonymizeList.end();
1597 d = GetDocEntry( (*it).Group, (*it).Elem);
1602 if ( dynamic_cast<SeqEntry *>(d) )
1604 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1608 if ( dynamic_cast<DataEntry *>(d) )
1610 gdcmWarningMacro( "To 'Anonymize' a DataEntry, better use AnonymizeNoLoad (FIXME) ");
1614 SetEntryString ((*it).Value, (*it).Group, (*it).Elem);
1618 // In order to make definitively impossible any further identification
1619 // remove or replace all the stuff that contains a Date
1621 //0008 0012 DA ID Instance Creation Date
1622 //0008 0020 DA ID Study Date
1623 //0008 0021 DA ID Series Date
1624 //0008 0022 DA ID Acquisition Date
1625 //0008 0023 DA ID Content Date
1626 //0008 0024 DA ID Overlay Date
1627 //0008 0025 DA ID Curve Date
1628 //0008 002a DT ID Acquisition Datetime
1629 //0018 9074 DT ACQ Frame Acquisition Datetime
1630 //0018 9151 DT ACQ Frame Reference Datetime
1631 //0018 a002 DT ACQ Contribution Date Time
1632 //0020 3403 SH REL Modified Image Date (RET)
1633 //0032 0032 DA SDY Study Verified Date
1634 //0032 0034 DA SDY Study Read Date
1635 //0032 1000 DA SDY Scheduled Study Start Date
1636 //0032 1010 DA SDY Scheduled Study Stop Date
1637 //0032 1040 DA SDY Study Arrival Date
1638 //0032 1050 DA SDY Study Completion Date
1639 //0038 001a DA VIS Scheduled Admission Date
1640 //0038 001c DA VIS Scheduled Discharge Date
1641 //0038 0020 DA VIS Admitting Date
1642 //0038 0030 DA VIS Discharge Date
1643 //0040 0002 DA PRC Scheduled Procedure Step Start Date
1644 //0040 0004 DA PRC Scheduled Procedure Step End Date
1645 //0040 0244 DA PRC Performed Procedure Step Start Date
1646 //0040 0250 DA PRC Performed Procedure Step End Date
1647 //0040 2004 DA PRC Issue Date of Imaging Service Request
1648 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
1649 //0040 4011 DT PRC Expected Completion Date and Time
1650 //0040 a030 DT PRC Verification Date Time
1651 //0040 a032 DT PRC Observation Date Time
1652 //0040 a120 DT PRC DateTime
1653 //0040 a121 DA PRC Date
1654 //0040 a13a DT PRC Referenced Datetime
1655 //0070 0082 DA ??? Presentation Creation Date
1656 //0100 0420 DT ??? SOP Autorization Date and Time
1657 //0400 0105 DT ??? Digital Signature DateTime
1658 //2100 0040 DA PJ Creation Date
1659 //3006 0008 DA SSET Structure Set Date
1660 //3008 0024 DA ??? Treatment Control Point Date
1661 //3008 0054 DA ??? First Treatment Date
1662 //3008 0056 DA ??? Most Recent Treatment Date
1663 //3008 0162 DA ??? Safe Position Exit Date
1664 //3008 0166 DA ??? Safe Position Return Date
1665 //3008 0250 DA ??? Treatment Date
1666 //300a 0006 DA RT RT Plan Date
1667 //300a 022c DA RT Air Kerma Rate Reference Date
1668 //300e 0004 DA RT Review Date
1674 * \brief Performs some consistency checking on various 'File related'
1675 * (as opposed to 'DicomDir related') entries
1676 * then writes in a file all the (Dicom Elements) included the Pixels
1677 * @param fileName file name to write to
1678 * @param writetype type of the file to be written
1679 * (ACR, ExplicitVR, ImplicitVR)
1681 bool File::Write(std::string fileName, FileType writetype)
1683 std::ofstream *fp = new std::ofstream(fileName.c_str(),
1684 std::ios::out | std::ios::binary);
1687 gdcmWarningMacro("Failed to open (write) File: " << fileName.c_str());
1691 // Entry : 0002|0000 = group length -> recalculated
1692 DataEntry *e0000 = GetDataEntry(0x0002,0x0000);
1695 std::ostringstream sLen;
1696 sLen << ComputeGroup0002Length( );
1697 e0000->SetString(sLen.str());
1700 /// \todo FIXME : Derma?.dcm does not have it...let's remove it ?!? JPRx
1701 if( writetype != JPEG )
1703 int i_lgPix = GetEntryLength(GrPixel, NumPixel);
1706 // no (GrPixel, NumPixel) element
1707 std::string s_lgPix = Util::Format("%d", i_lgPix+12);
1708 s_lgPix = Util::DicomString( s_lgPix.c_str() );
1709 InsertEntryString(s_lgPix,GrPixel, 0x0000, "UL");
1712 Document::WriteContent(fp, writetype);
1720 //-----------------------------------------------------------------------------
1724 //-----------------------------------------------------------------------------
1727 * \brief Parse pixel data from disk of [multi-]fragment RLE encoding.
1728 * Compute the RLE extra information and store it in \ref RLEInfo
1729 * for later pixel retrieval usage.
1731 void File::ComputeRLEInfo()
1733 std::string ts = GetTransferSyntax();
1734 if ( !Global::GetTS()->IsRLELossless(ts) )
1739 // Encoded pixel data: for the time being we are only concerned with
1740 // Jpeg or RLE Pixel data encodings.
1741 // As stated in PS 3.5-2003, section 8.2 p44:
1742 // "If sent in Encapsulated Format (i.e. other than the Native Format) the
1743 // value representation OB is used".
1744 // Hence we expect an OB value representation. Concerning OB VR,
1745 // the section PS 3.5-2003, section A.4.c p 58-59, states:
1746 // "For the Value Representations OB and OW, the encoding shall meet the
1747 // following specifications depending on the Data element tag:"
1749 // - the first item in the sequence of items before the encoded pixel
1750 // data stream shall be basic offset table item. The basic offset table
1751 // item value, however, is not required to be present"
1752 ReadEncapsulatedBasicOffsetTable();
1754 // Encapsulated RLE Compressed Images (see PS 3.5-2003, Annex G)
1755 // Loop on the individual frame[s] and store the information
1756 // on the RLE fragments in a RLEFramesInfo.
1757 // Note: - when only a single frame is present, this is a
1759 // - when more than one frame are present, then we are in
1760 // the case of a multi-frame image.
1764 while ( (frameLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1766 // Since we have read the basic offset table, let's check the value were correct
1767 // or else produce a warning:
1768 if ( BasicOffsetTableItemValue )
1770 // If a BasicOffsetTableItemValue was read
1771 uint32_t individualLength = BasicOffsetTableItemValue[i];
1772 assert( individualLength == sum ); // REMOVE that if this is a problem
1773 if( individualLength != sum )
1775 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght" );
1777 sum += frameLength + 8;
1780 // Parse the RLE Header and store the corresponding RLE Segment
1781 // Offset Table information on fragments of this current Frame.
1782 // Note that the fragment pixels themselves are not loaded
1783 // (but just skipped).
1784 long frameOffset = Fp->tellg(); // once per fragment
1786 uint32_t nbRleSegments = ReadInt32();
1787 if ( nbRleSegments > 16 )
1789 // There should be at most 15 segments (refer to RLEFrame class)
1790 gdcmWarningMacro( "Too many segments.");
1793 uint32_t rleSegmentOffsetTable[16];
1794 for( int k = 1; k <= 15; k++ )
1796 rleSegmentOffsetTable[k] = ReadInt32();
1799 // Deduce from both RLE Header and frameLength
1800 // the fragment length, and again store this info
1801 // in a RLEFramesInfo.
1802 long rleSegmentLength[15];
1803 // skipping (not reading) RLE Segments
1804 if ( nbRleSegments > 1)
1806 for(unsigned int k = 1; k <= nbRleSegments-1; k++)
1808 rleSegmentLength[k] = rleSegmentOffsetTable[k+1]
1809 - rleSegmentOffsetTable[k];
1810 SkipBytes(rleSegmentLength[k]);
1814 rleSegmentLength[nbRleSegments] = frameLength
1815 - rleSegmentOffsetTable[nbRleSegments];
1816 SkipBytes(rleSegmentLength[nbRleSegments]);
1818 // Store the collected info
1819 RLEFrame *newFrame = new RLEFrame;
1820 newFrame->SetNumberOfFragments(nbRleSegments);
1821 for( unsigned int uk = 1; uk <= nbRleSegments; uk++ )
1823 newFrame->SetOffset(uk,frameOffset + rleSegmentOffsetTable[uk]);
1824 newFrame->SetLength(uk,rleSegmentLength[uk]);
1826 RLEInfo->AddFrame(newFrame);
1829 // Make sure that we encounter a 'Sequence Delimiter Item'
1830 // at the end of the item :
1831 if ( !ReadTag(0xfffe, 0xe0dd) ) // once per RLE File
1833 gdcmWarningMacro( "No sequence delimiter item at end of RLE item sequence");
1838 * \brief Parse pixel data from disk of [multi-]fragment Jpeg encoding.
1839 * Compute the jpeg extra information (fragment[s] offset[s] and
1840 * length) and store it[them] in \ref JPEGInfo for later pixel
1843 void File::ComputeJPEGFragmentInfo()
1845 // If you need to, look for comments of ComputeRLEInfo().
1846 std::string ts = GetTransferSyntax();
1847 if ( ! Global::GetTS()->IsJPEG(ts) )
1852 ReadEncapsulatedBasicOffsetTable();
1854 // Loop on the fragments[s] and store the parsed information in a
1856 long fragmentLength;
1859 while ( (fragmentLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1861 // Since we have read the basic offset table, let's check the value were correct
1862 // or else produce a warning:
1863 // A.4 Transfer syntaxes for encapsulation of encoded pixel data:
1864 // When the Item Value is present, the Basic Offset Table Item Value shall contain
1865 // concatenated 32-bit unsigned integer values that are byte offsets to the first
1866 // byte of the Item Tag of the first fragment for each frame in the Sequence of
1867 // Items. These offsets are measured from the first byte of the first Item Tag
1868 // following the Basic Offset Table item (See Table A.4-2).
1870 if ( BasicOffsetTableItemValue )
1872 // If a BasicOffsetTableItemValue was read
1873 uint32_t individualLength = BasicOffsetTableItemValue[i];
1874 //assert( individualLength == sum ); // Seems like 00191113.dcm is off by one ??
1875 if( individualLength != sum )
1877 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght:" <<
1878 individualLength << " != " << sum );
1880 sum += fragmentLength + 8;
1884 long fragmentOffset = Fp->tellg(); // Once per fragment
1885 // Store the collected info
1886 JPEGFragment *newFragment = new JPEGFragment;
1887 newFragment->SetOffset(fragmentOffset);
1888 newFragment->SetLength(fragmentLength);
1889 JPEGInfo->AddFragment(newFragment);
1891 SkipBytes(fragmentLength);
1894 // Make sure that we encounter a 'Sequence Delimiter Item'
1895 // at the end of the item :
1896 if ( !ReadTag(0xfffe, 0xe0dd) )
1898 gdcmWarningMacro( "No sequence delimiter item at end of JPEG item sequence");
1903 * \brief Assuming the internal file pointer \ref Document::Fp
1904 * is placed at the beginning of a tag, check whether this
1905 * tag is (TestGroup, TestElem).
1906 * \warning On success the internal file pointer \ref Document::Fp
1907 * is modified to point after the tag.
1908 * On failure (i.e. when the tag wasn't the expected tag
1909 * (TestGroup, TestElem) the internal file pointer
1910 * \ref Document::Fp is restored to it's original position.
1911 * @param testGroup The expected group of the tag.
1912 * @param testElem The expected Element of the tag.
1913 * @return True on success, false otherwise.
1915 bool File::ReadTag(uint16_t testGroup, uint16_t testElem)
1917 long positionOnEntry = Fp->tellg(); // Only when reading fragments
1918 //long currentPosition = positionOnEntry; // On debugging purposes
1920 // Read the Item Tag group and element, and make
1921 // sure they are what we expected:
1922 uint16_t itemTagGroup;
1923 uint16_t itemTagElem;
1926 itemTagGroup = ReadInt16();
1927 itemTagElem = ReadInt16();
1929 catch ( FormatError )
1931 gdcmErrorMacro( "Can not read tag for "
1932 << " We should have found tag ("
1933 << DictEntry::TranslateToKey(testGroup,testElem) << ")"
1938 if ( itemTagGroup != testGroup || itemTagElem != testElem )
1940 // in order not to pollute output we don't warn on 'delimitors'
1941 if (itemTagGroup != 0xfffe || testGroup != 0xfffe )
1942 gdcmWarningMacro( "Wrong Item Tag found:"
1943 << " We should have found tag ("
1944 << DictEntry::TranslateToKey(testGroup,testElem) << ")" << std::endl
1945 << " but instead we encountered tag ("
1946 << DictEntry::TranslateToKey(itemTagGroup,itemTagElem) << ")"
1947 << " at address: " << " 0x(" << std::hex
1948 << (unsigned int)positionOnEntry << std::dec << ")"
1950 Fp->seekg(positionOnEntry, std::ios::beg);
1958 * \brief Assuming the internal file pointer \ref Document::Fp
1959 * is placed at the beginning of a tag (TestGroup, TestElement),
1960 * read the length associated to the Tag.
1961 * \warning On success the internal file pointer \ref Document::Fp
1962 * is modified to point after the tag and it's length.
1963 * On failure (i.e. when the tag wasn't the expected tag
1964 * (TestGroup, TestElement) the internal file pointer
1965 * \ref Document::Fp is restored to it's original position.
1966 * @param testGroup The expected Group of the tag.
1967 * @param testElem The expected Element of the tag.
1968 * @return On success returns the length associated to the tag. On failure
1971 uint32_t File::ReadTagLength(uint16_t testGroup, uint16_t testElem)
1974 if ( !ReadTag(testGroup, testElem) )
1976 // Avoid polutting output
1977 if ( testGroup != 0xfffe )
1978 gdcmErrorMacro( "ReadTag did not succeed for ("
1979 << DictEntry::TranslateToKey(testGroup,testElem)
1984 //// Then read the associated Item Length
1986 // long currentPosition = Fp->tellg(); // save time // JPRx
1987 uint32_t itemLength = ReadInt32();
1988 gdcmDebugMacro( "Basic Item Length is: " << itemLength
1989 // << " at address: " << std::hex << (unsigned int)currentPosition
1995 * \brief When parsing the Pixel Data of an encapsulated file, read
1996 * the basic offset table (when present, and BTW dump it).
1998 void File::ReadEncapsulatedBasicOffsetTable()
2000 //// Read the Basic Offset Table Item Tag length...
2001 uint32_t itemLength = ReadTagLength(0xfffe, 0xe000);
2003 // When present, read the basic offset table itself.
2004 // Notes: - since the presence of this basic offset table is optional
2005 // we can't rely on it for the implementation, and we will simply
2006 // trash it's content (when present).
2007 // - still, when present, we could add some further checks on the
2008 // lengths, but we won't bother with such fuses for the time being.
2009 if ( itemLength != 0 )
2011 char *charBasicOffsetTableItemValue = new char[itemLength];
2012 Fp->read(charBasicOffsetTableItemValue, itemLength);
2013 unsigned int nbEntries = itemLength/4;
2014 assert( nbEntries*4 == itemLength); // Make sure this is a multiple
2015 BasicOffsetTableItemValue = new uint32_t[nbEntries];
2017 for (unsigned int i=0; i < nbEntries; i++ )
2019 BasicOffsetTableItemValue[i] = *((uint32_t*)(&charBasicOffsetTableItemValue[4*i]));
2020 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
2021 uint32_t val = BasicOffsetTableItemValue[i];
2022 BasicOffsetTableItemValue[i]
2023 = ( (val<<24) | ((val<<8) & 0x00ff0000) |
2024 ( (val>>8) & 0x0000ff00) | (val>>24) );
2026 gdcmDebugMacro( "Read one length for: " <<
2027 std::hex << BasicOffsetTableItemValue[i] );
2030 delete[] charBasicOffsetTableItemValue;
2034 // These are the deprecated method that one day should be removed (after the next release)
2036 //#ifndef GDCM_LEGACY_REMOVE
2038 * \ brief Loader. (DEPRECATED : temporaryly kept not to break the API)
2039 * @ param fileName file to be open for parsing
2040 * @ return false if file cannot be open or no swap info was found,
2041 * or no tag was found.
2042 * @deprecated Use the Load() [ + SetLoadMode() ] + SetFileName() functions instead
2045 bool File::Load( std::string const &fileName )
2047 GDCM_LEGACY_REPLACED_BODY(File::Load(std::string), "1.2",
2049 SetFileName( fileName );
2050 if ( ! this->Document::Load( ) )
2053 return DoTheLoadingJob( );
2057 //-----------------------------------------------------------------------------
2060 //-----------------------------------------------------------------------------
2061 } // end namespace gdcm