1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2006/08/29 10:21:20 $
7 Version: $Revision: 1.325 $
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
108 //-----------------------------------------------------------------------------
109 // Constructor / Destructor
112 * \brief Constructor used when we want to generate dicom files from scratch
117 RLEInfo = new RLEFramesInfo;
118 JPEGInfo = new JPEGFragmentsInfo;
119 GrPixel = 0x7fe0; // to avoid further troubles
121 BasicOffsetTableItemValue = 0;
122 FourthDimensionLocation = TagKey(0,0);
127 * \brief Canonical destructor.
135 delete[] BasicOffsetTableItemValue;
138 //-----------------------------------------------------------------------------
142 * @return false if file cannot be open or no swap info was found,
143 * or no tag was found.
148 if ( ! this->Document::Load( ) )
151 return DoTheLoadingJob( );
155 * \brief Does the Loading Job (internal use only)
156 * @return false if file cannot be open or no swap info was found,
157 * or no tag was found.
159 bool File::DoTheLoadingJob( )
161 // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
162 // We may encounter the 'RETired' (0x0028, 0x0200) tag
163 // (Image Location") . This entry contains the number of
164 // the group that contains the pixel data (hence the "Pixel Data"
165 // is found by indirection through the "Image Location").
166 // Inside the group pointed by "Image Location" the searched element
167 // is conventionally the element 0x0010 (when the norm is respected).
168 // When the "Image Location" is missing we default to group 0x7fe0.
169 // Note: this IS the right place for the code
172 const std::string &imgLocation = GetEntryString(0x0028, 0x0200);
173 if ( imgLocation == GDCM_UNFOUND )
181 GrPixel = (uint16_t) atoi( imgLocation.c_str() );
184 // sometimes Image Location value doesn't follow
185 // the supposed processor endianness.
186 // see gdcmData/cr172241.dcm
187 if ( GrPixel == 0xe07f )
192 if ( GrPixel != 0x7fe0 )
194 // This is a kludge for old dirty Philips imager.
202 // Now, we know GrPixel and NumPixel.
203 // Let's create a VirtualDictEntry to allow a further VR modification
204 // and force VR to match with BitsAllocated.
205 DocEntry *entry = GetDocEntry(GrPixel, NumPixel);
208 // Compute the RLE or JPEG info
210 const std::string &ts = GetTransferSyntax();
211 Fp->seekg( entry->GetOffset(), std::ios::beg );
212 if ( Global::GetTS()->IsRLELossless(ts) )
214 else if ( Global::GetTS()->IsJPEG(ts) )
215 ComputeJPEGFragmentInfo();
218 // Create a new DataEntry to change the DictEntry
219 // The changed DictEntry will have
220 // - a correct PixelVR OB or OW)
221 // - the name to "Pixel Data"
224 //==> Just change the VR !
227 DataEntry *oldEntry = dynamic_cast<DataEntry *>(entry);
231 // 8 bits allocated is a 'O Bytes' , as well as 24 (old ACR-NEMA RGB)
232 // more than 8 (i.e 12, 16) is a 'O Words'
233 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
238 // Change only made if usefull
239 if ( PixelVR != oldEntry->GetVR() )
241 //DictEntry* newDict = DictEntry::New(GrPixel,NumPixel,
242 // PixelVR,"1","Pixel Data");
243 //DataEntry *newEntry = DataEntry::New(newDict);
245 //newEntry->Copy(entry);
246 //newEntry->SetBinArea(oldEntry->GetBinArea(),oldEntry->IsSelfArea());
247 //oldEntry->SetSelfArea(false);
249 //RemoveEntry(oldEntry);
250 //AddEntry(newEntry);
251 //newEntry->Delete();
257 // 8 bits allocated is a 'OB(ytes)' , as well as 24 (old ACR-NEMA RGB)
258 // more than 8 (i.e 12, 16) is a 'OW(ords)'
259 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
263 // Change only made if usefull
264 if ( PixelVR != entry->GetVR() )
266 entry->SetVR(PixelVR);
273 * \brief This predicate, based on hopefully reasonable heuristics,
274 * decides whether or not the current File was properly parsed
275 * and contains the mandatory information for being considered as
276 * a well formed and usable Dicom/Acr File.
277 * @return true when File is the one of a reasonable Dicom/Acr file,
280 bool File::IsReadable()
282 if ( !Document::IsReadable() )
287 const std::string &res = GetEntryString(0x0028, 0x0005);
288 if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 )
290 gdcmWarningMacro("Wrong Image Dimensions" << res);
291 return false; // Image Dimensions
293 bool b0028_0100 = true;
294 if ( !GetDocEntry(0x0028, 0x0100) )
296 gdcmWarningMacro("Bits Allocated (0028|0100) not found");
297 //return false; // "Bits Allocated"
300 bool b0028_0101 = true;
301 if ( !GetDocEntry(0x0028, 0x0101) )
303 gdcmWarningMacro("Bits Stored (0028|0101) not found");
304 //return false; // "Bits Stored"
307 bool b0028_0102 = true;
308 if ( !GetDocEntry(0x0028, 0x0102) )
310 gdcmWarningMacro("Hight Bit (0028|0102) not found");
311 //return false; // "High Bit"
314 bool b0028_0103 = true;
315 if ( !GetDocEntry(0x0028, 0x0103) )
317 gdcmWarningMacro("Pixel Representation (0028|0103) not found");
318 //return false; // "Pixel Representation" i.e. 'Sign' ( 0 : unsigned, 1 : signed)
322 if ( !b0028_0100 && !b0028_0101 && !b0028_0102 && !b0028_0103)
324 gdcmWarningMacro("Too much mandatory Tags missing !");
328 if ( !GetDocEntry(GrPixel, NumPixel) )
330 gdcmWarningMacro("Pixel Dicom Element " << std::hex <<
331 GrPixel << "|" << NumPixel << "not found");
332 return false; // Pixel Dicom Element not found :-(
338 * \brief gets the info from 0020,0013 : Image Number else 0.
339 * @return image number
341 int File::GetImageNumber()
343 //0020 0013 : Image Number
344 std::string strImNumber = GetEntryString(0x0020,0x0013);
345 if ( strImNumber != GDCM_UNFOUND )
347 return atoi( strImNumber.c_str() );
353 * \brief gets the info from 0008,0060 : Modality
354 * @return Modality Type
356 ModalityType File::GetModality()
358 // 0008 0060 : Modality
359 std::string strModality = GetEntryString(0x0008,0x0060);
360 if ( strModality != GDCM_UNFOUND )
362 if ( strModality.find("AU") < strModality.length()) return AU;
363 else if ( strModality.find("AS") < strModality.length()) return AS;
364 else if ( strModality.find("BI") < strModality.length()) return BI;
365 else if ( strModality.find("CF") < strModality.length()) return CF;
366 else if ( strModality.find("CP") < strModality.length()) return CP;
367 else if ( strModality.find("CR") < strModality.length()) return CR;
368 else if ( strModality.find("CT") < strModality.length()) return CT;
369 else if ( strModality.find("CS") < strModality.length()) return CS;
370 else if ( strModality.find("DD") < strModality.length()) return DD;
371 else if ( strModality.find("DF") < strModality.length()) return DF;
372 else if ( strModality.find("DG") < strModality.length()) return DG;
373 else if ( strModality.find("DM") < strModality.length()) return DM;
374 else if ( strModality.find("DS") < strModality.length()) return DS;
375 else if ( strModality.find("DX") < strModality.length()) return DX;
376 else if ( strModality.find("ECG") < strModality.length()) return ECG;
377 else if ( strModality.find("EPS") < strModality.length()) return EPS;
378 else if ( strModality.find("FA") < strModality.length()) return FA;
379 else if ( strModality.find("FS") < strModality.length()) return FS;
380 else if ( strModality.find("HC") < strModality.length()) return HC;
381 else if ( strModality.find("HD") < strModality.length()) return HD;
382 else if ( strModality.find("LP") < strModality.length()) return LP;
383 else if ( strModality.find("LS") < strModality.length()) return LS;
384 else if ( strModality.find("MA") < strModality.length()) return MA;
385 else if ( strModality.find("MR") < strModality.length()) return MR;
386 else if ( strModality.find("NM") < strModality.length()) return NM;
387 else if ( strModality.find("OT") < strModality.length()) return OT;
388 else if ( strModality.find("PT") < strModality.length()) return PT;
389 else if ( strModality.find("RF") < strModality.length()) return RF;
390 else if ( strModality.find("RG") < strModality.length()) return RG;
391 else if ( strModality.find("RTDOSE")
392 < strModality.length()) return RTDOSE;
393 else if ( strModality.find("RTIMAGE")
394 < strModality.length()) return RTIMAGE;
395 else if ( strModality.find("RTPLAN")
396 < strModality.length()) return RTPLAN;
397 else if ( strModality.find("RTSTRUCT")
398 < strModality.length()) return RTSTRUCT;
399 else if ( strModality.find("SM") < strModality.length()) return SM;
400 else if ( strModality.find("ST") < strModality.length()) return ST;
401 else if ( strModality.find("TG") < strModality.length()) return TG;
402 else if ( strModality.find("US") < strModality.length()) return US;
403 else if ( strModality.find("VF") < strModality.length()) return VF;
404 else if ( strModality.find("XA") < strModality.length()) return XA;
405 else if ( strModality.find("XC") < strModality.length()) return XC;
409 /// \todo throw error return value ???
410 /// specified <> unknown in our database
418 * \brief Retrieve the number of columns of image.
419 * @return The encountered size when found, 0 by default.
420 * 0 means the file is NOT USABLE. The caller will have to check
424 DataEntry *entry = GetDataEntry(0x0028,0x0011);
426 return (int)entry->GetValue(0);
431 * \brief Retrieve the number of lines of image.
432 * \warning The defaulted value is 1 as opposed to File::GetXSize()
433 * @return The encountered size when found, 1 by default
434 * (The ACR-NEMA file contains a Signal, not an Image).
438 DataEntry *entry = GetDataEntry(0x0028,0x0010);
440 return (int)entry->GetValue(0);
447 // The Rows (0028,0010) entry was optional for ACR/NEMA.
448 // (at least some images didn't have it.)
449 // It might hence be a signal (1D image). So we default to 1:
454 * \brief Retrieve the number of planes of volume or the number
455 * of frames of a multiframe.
456 * \warning When present we consider the "Number of Frames" as the third
457 * dimension. When missing we consider the third dimension as
458 * being the ACR-NEMA "Planes" tag content.
459 * @return The encountered size when found, 1 by default (single image).
463 // Both DicomV3 and ACR/Nema consider the "Number of Frames"
464 // as the third dimension.
465 DataEntry *entry = GetDataEntry(0x0028,0x0008);
467 return (int)entry->GetValue(0);
469 // We then consider the "Planes" entry as the third dimension
470 entry = GetDataEntry(0x0028,0x0012);
472 return (int)entry->GetValue(0);
477 * \brief Retrieve the -unnormalized- number of 'times' of '4D image'.
478 * User has to tell gdcm the location of this '4th Dimension component'
479 * using SetFourthDimensionLocation() method before.
480 * \warning The defaulted value is 1.
481 * @return The encountered size when found, 1 by default
482 * (The file doesn't contain a '4D image'.).
486 if (FourthDimensionLocation == TagKey(0,0) )// 4D location is not set : not a 4D object
489 DataEntry *entry = GetDataEntry(FourthDimensionLocation.GetGroup(),
490 FourthDimensionLocation.GetElement() );
493 gdcmWarningMacro( " FourthDimensionLocation not found at : " <<
494 std::hex << FourthDimensionLocation.GetGroup()
495 << "|" << FourthDimensionLocation.GetElement());
500 return (int)entry->GetValue(0);
505 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
506 * then 0028,0030 : Pixel Spacing
508 * @return X dimension of a pixel
510 float File::GetXSpacing()
512 float xspacing = 1.0;
517 From:David Clunie - view profile
518 Date:Wed, May 24 2006 1:12 pm
519 Email:David Clunie <dclu...@dclunie.com>
520 Groups:comp.protocols.dicom
522 The short answer is that:
524 - (0018,1164) describes a spacing equivalent to that which
525 would be measured off a film in projection radiography
527 - (0018,7022) does not describe the image pixels themselves,
528 since detector elements may have been binned to produce
531 - (0018,7020) may be different from (0018,7022) since there
532 may be non-sensitive material separating individual
533 detectors (i.e. the size is smaller than the spacing
536 Only (0018,1164) is relevant when measuring things; the
537 detector-specific attributes are there to describe the
542 PS. For ultrasound you need to use Region Calibration.
546 It *SHOULD* first find the IOD and then deduce which tags to read
547 Eg: Cross section this is in Pixel Spacing (0028,0030)
548 CR is in Imager Pixel Spacing (0018,1164)
549 US is in Pixel Aspect Ratio (0028,0034)
551 (3002,0011) Image Plane Pixel Spacing
552 (3002,0012) RT Image Position
554 (3004,000c) for deducing Z spacing
557 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
559 /// \todo check the various SOP Class
560 /// to get the Pixel Spacing at the proper location
562 ///\todo find images to check if it *actually* works
564 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
565 // Ultrasound Image Storage (Retired)
566 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
567 // Ultrasound Image Storage
568 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
569 // Ultrasound Multi-Frame Storage (Retired)
570 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
571 // Ultrasound Multi-FrameImage Storage
573 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
574 // - guess how to deduce the spacing (FOV ?, ??)
576 entry = GetDataEntry(0x0028,0x0034);
579 nbValue = entry->GetValueCount();
581 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
582 << "has a wrong number of values :" << nbValue);
584 xspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
592 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.1") )
593 // Computed Radiography Image Storage
595 // CR is in Imager Pixel Spacing (0018,1164)//
598 // go on with old method ...
599 // ---------------------
600 // To follow David Clunie's advice, we first check ImagerPixelSpacing
602 entry = GetDataEntry(0x0018,0x1164);
605 nbValue = entry->GetValueCount();
606 // Can't use IsValueCountValid because of the complex heuristic.
608 gdcmWarningMacro("ImagerPixelSpacing (0x0018,0x1164) "
609 << "has a wrong number of values :" << nbValue);
612 xspacing = (float)entry->GetValue(2);
613 else if( nbValue >= 2 )
614 xspacing = (float)entry->GetValue(1);
616 xspacing = (float)entry->GetValue(0);
618 if ( xspacing == 0.0 )
624 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
627 entry = GetDataEntry(0x0028,0x0030);
630 nbValue = entry->GetValueCount();
632 gdcmWarningMacro("PixelSpacing (0x0018,0x0030) "
633 << "has a wrong number of values :" << nbValue);
636 xspacing = (float)entry->GetValue(2);
637 else if( nbValue >= 2 )
638 xspacing = (float)entry->GetValue(1);
640 xspacing = (float)entry->GetValue(0);
642 if ( xspacing == 0.0 )
648 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
654 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
655 * then from 0028,0030 : Pixel Spacing
657 * @return Y dimension of a pixel
659 float File::GetYSpacing()
666 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
668 /// \todo check the various SOP Class
669 /// to get the Pixel Spacing at the proper location
671 ///\todo find images to check if it *actually* works
673 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
674 // Ultrasound Image Storage (Retired)
675 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
676 // Ultrasound Image Storage
677 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
678 // Ultrasound Multi-Frame Storage (Retired)
679 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
680 // Ultrasound Multi-FrameImage Storage
682 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
683 // - no way to deduce the spacing/
685 entry = GetDataEntry(0x0028,0x0034);
688 nbValue = entry->GetValueCount();
690 yspacing = (float)entry->GetValue(0)/(float)entry->GetValue(1);
691 //std::cout << "ys " << yspacing << std::endl;
696 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
697 << "has a wrong number of values :" << nbValue);
701 else if (nbValue == 1 ) {
702 yspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
712 // go on with old method ...
713 // ---------------------
714 // To follow David Clunie's advice, we first check ImagerPixelSpacing
716 // To follow David Clunie's advice, we first check ImagerPixelSpacing
718 entry = GetDataEntry(0x0018,0x1164);
721 yspacing = (float)entry->GetValue(0);
723 if ( yspacing == 0.0 )
729 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
732 entry = GetDataEntry(0x0028,0x0030);
735 yspacing = (float)entry->GetValue(0);
737 if ( yspacing == 0.0 )
743 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
750 * \brief gets the info from 0018,0088 : Space Between Slices
751 * else from 0018,0050 : Slice Thickness
754 * When an element is missing, we suppose slices join together
755 * (no overlapping, no interslice gap) but we have no way to check it !
756 * For *Dicom* images, ZSpacing *should be* calculated using
757 * XOrigin, YOrigin, ZOrigin (of the top left image corner)
758 * of 2 consecutive images, and the Orientation
759 * Computing ZSpacing on a single image is not really meaningfull !
760 * @return Z dimension of a voxel-to be
762 float File::GetZSpacing()
765 float zspacing = 1.0f;
767 // Spacing Between Slices : distance between the middle of 2 slices
769 // jointives (Spacing between Slices = Slice Thickness)
770 // overlapping (Spacing between Slices < Slice Thickness)
771 // disjointes (Spacing between Slices > Slice Thickness)
772 // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
773 // It only concerns the MRI guys, not people wanting to visualize volumes
774 // If Spacing Between Slices is missing,
775 // we suppose slices joint together
776 DataEntry *entry = GetDataEntry(0x0018,0x0088);
778 { zspacing = (float)entry->GetValue(0);
780 if ( zspacing == 0.0 )
785 gdcmWarningMacro("Unfound Spacing Between Slices (0018,0088)");
787 // if no 'Spacing Between Slices' is found,
788 // we assume slices join together
789 // (no overlapping, no interslice gap)
790 entry = GetDataEntry(0x0018,0x0050);
793 zspacing = (float)entry->GetValue(0);
795 if ( zspacing == 0.0 )
800 gdcmWarningMacro("Unfound Slice Thickness (0018,0050)");
802 // if no 'Spacing Between Slices' is found,
803 // we assume slices join together
804 // (no overlapping, no interslice gap)
805 entry = GetDataEntry(0x3004,0x000c);
808 float z1 = (float)entry->GetValue(0);
809 float z2 = (float)entry->GetValue(1);
810 zspacing = z2 - z1; // can be negative...
812 if ( zspacing == 0.0 )
821 * \brief gets the info from 0020,0032 : Image Position Patient
822 * else from 0020,0030 : Image Position (RET)
824 * @return up-left image corner X position
826 float File::GetXOrigin()
828 DataEntry *entry = GetDataEntry(0x0020,0x0032);
831 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
832 entry = GetDataEntry(0x0020,0x0030);
835 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
840 if( entry->GetValueCount() == 3 )
842 if (!entry->IsValueCountValid() )
844 gdcmErrorMacro( "Invalid Value Count" );
846 return (float)entry->GetValue(0);
852 * \brief gets the info from 0020,0032 : Image Position Patient
853 * else from 0020,0030 : Image Position (RET)
855 * @return up-left image corner Y position
857 float File::GetYOrigin()
859 DataEntry *entry = GetDataEntry(0x0020,0x0032);
862 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
863 entry = GetDataEntry(0x0020,0x0030);
866 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
871 if( entry->GetValueCount() == 3 )
873 if (!entry->IsValueCountValid() )
875 gdcmErrorMacro( "Invalid Value Count" );
877 return (float)entry->GetValue(1);
883 * \brief gets the info from 0020,0032 : Image Position Patient
884 * else from 0020,0030 : Image Position (RET)
885 * else from 0020,1041 : Slice Location
886 * else from 0020,0050 : Location
888 * @return up-left image corner Z position
890 float File::GetZOrigin()
892 DataEntry *entry = GetDataEntry(0x0020,0x0032);
895 if( entry->GetValueCount() == 3 )
897 if (!entry->IsValueCountValid() )
899 gdcmErrorMacro( "Invalid Value Count" );
901 return (float)entry->GetValue(2);
903 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)");
907 entry = GetDataEntry(0x0020,0x0030);
910 if( entry->GetValueCount() == 3 )
912 if (!entry->IsValueCountValid() )
914 gdcmErrorMacro( "Invalid Value Count" );
916 return (float)entry->GetValue(2);
918 gdcmWarningMacro( "Wrong Image Position (RET) (0020,0030)");
922 // for *very* old ACR-NEMA images
923 entry = GetDataEntry(0x0020,0x1041);
926 if( entry->GetValueCount() == 1 )
928 if (!entry->IsValueCountValid() )
930 gdcmErrorMacro( "Invalid Value Count" );
932 return (float)entry->GetValue(0); // VM=1 !
934 gdcmWarningMacro( "Wrong Slice Location (0020,1041)");
938 entry = GetDataEntry(0x0020,0x0050);
941 if( entry->GetValueCount() == 1 )
943 if (!entry->IsValueCountValid() )
945 gdcmErrorMacro( "Invalid Value Count" );
947 return (float)entry->GetValue(0);
949 gdcmWarningMacro( "Wrong Location (0020,0050)");
952 return 0.; // Hopeless
956 * \brief gets the info from 0020,0037 : Image Orientation Patient
957 * or from 0020 0035 : Image Orientation (RET)
959 * (needed to organize DICOM files based on their x,y,z position)
961 * @param iop adress of the (6)float array to receive values.
962 * (defaulted as 1.,0.,0.,0.,1.,0. if nothing -or inconsistent stuff-
964 * @return true when one of the tag -with consistent values- is found
965 * false when nothing or inconsistent stuff - is found
967 bool File::GetImageOrientationPatient( float iop[6] )
969 std::string strImOriPat;
970 //iop is supposed to be float[6]
971 iop[0] = iop[4] = 1.;
972 iop[1] = iop[2] = iop[3] = iop[5] = 0.;
974 // 0020 0037 DS REL Image Orientation (Patient)
975 if ( (strImOriPat = GetEntryString(0x0020,0x0037)) != GDCM_UNFOUND )
977 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
978 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
980 gdcmWarningMacro( "Wrong Image Orientation Patient (0020,0037)."
981 << " Less than 6 values were found." );
987 // 0020 0035 DS REL Image Orientation (RET)
988 else if ( (strImOriPat = GetEntryString(0x0020,0x0035)) != GDCM_UNFOUND )
990 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
991 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
993 gdcmWarningMacro( "wrong Image Orientation Patient (0020,0035). "
994 << "Less than 6 values were found." );
1003 * \brief Retrieve the number of Bits Stored (actually used)
1004 * (as opposed to number of Bits Allocated)
1005 * @return The encountered number of Bits Stored, 0 by default.
1006 * 0 means the file is NOT USABLE. The caller has to check it !
1008 int File::GetBitsStored()
1010 DataEntry *entry = GetDataEntry(0x0028,0x0101);
1013 gdcmWarningMacro("BitsStored (0028,0101) is supposed to be mandatory");
1016 return (int)entry->GetValue(0);
1020 * \brief Retrieve the number of Bits Allocated
1021 * (8, 12 -compacted ACR-NEMA files-, 16, 24 -old RGB ACR-NEMA files-,)
1022 * @return The encountered Number of Bits Allocated, 0 by default.
1023 * 0 means the file is NOT USABLE. The caller has to check it !
1025 int File::GetBitsAllocated()
1027 DataEntry *entry = GetDataEntry(0x0028,0x0100);
1030 gdcmWarningMacro("BitsAllocated (0028,0100) is supposed to be mandatory");
1033 return (int)entry->GetValue(0);
1037 * \brief Retrieve the high bit position.
1038 * \warning The method defaults to 0 when information is missing.
1039 * The responsability of checking this value is left to the caller.
1040 * @return The high bit position when present. 0 when missing.
1042 int File::GetHighBitPosition()
1044 DataEntry *entry = GetDataEntry(0x0028,0x0102);
1047 gdcmWarningMacro("HighBitPosition (0028,0102) is supposed to be mandatory");
1050 return (int)entry->GetValue(0);
1054 * \brief Retrieve the number of Samples Per Pixel
1055 * (1 : gray level, 3 : RGB/YBR -1 or 3 Planes-)
1056 * @return The encountered number of Samples Per Pixel, 1 by default.
1057 * (we assume Gray level Pixels)
1059 int File::GetSamplesPerPixel()
1061 DataEntry *entry = GetDataEntry(0x0028,0x0002);
1064 gdcmWarningMacro("SamplesPerPixel (0028,0002) is supposed to be mandatory");
1065 return 1; // Well, it's supposed to be mandatory ...
1066 // but sometimes it's missing : *we* assume Gray pixels
1068 return (int)entry->GetValue(0);
1072 * \brief Retrieve the Planar Configuration for RGB images
1073 * (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
1074 * @return The encountered Planar Configuration, 0 by default.
1076 int File::GetPlanarConfiguration()
1078 DataEntry *entry = GetDataEntry(0x0028,0x0006);
1083 return (int)entry->GetValue(0);
1087 * \brief Return the size (in bytes) of a single pixel of data.
1088 * @return The size in bytes of a single pixel of data; 0 by default
1089 * 0 means the file is NOT USABLE; the caller will have to check
1091 int File::GetPixelSize()
1093 // 0028 0100 US IMG Bits Allocated
1094 // (in order no to be messed up by old ACR-NEMA RGB images)
1095 assert( !(GetEntryString(0x0028,0x0100) == "24") );
1097 std::string pixelType = GetPixelType();
1098 if ( pixelType == "8U" || pixelType == "8S" )
1102 if ( pixelType == "16U" || pixelType == "16S")
1106 if ( pixelType == "32U" || pixelType == "32S")
1110 if ( pixelType == "FD" )
1114 gdcmWarningMacro( "Unknown pixel type: " << pixelType);
1119 * \brief Build the Pixel Type of the image.
1120 * Possible values are:
1121 * - 8U unsigned 8 bit,
1122 * - 8S signed 8 bit,
1123 * - 16U unsigned 16 bit,
1124 * - 16S signed 16 bit,
1125 * - 32U unsigned 32 bit,
1126 * - 32S signed 32 bit,
1127 * - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
1128 * \warning 12 bit images appear as 16 bit.
1129 * 24 bit images appear as 8 bit + photochromatic interp ="RGB "
1130 * + Planar Configuration = 0
1131 * @return 0S if nothing found. NOT USABLE file. The caller has to check
1133 std::string File::GetPixelType()
1135 std::string bitsAlloc = GetEntryString(0x0028, 0x0100); // Bits Allocated
1136 if ( bitsAlloc == GDCM_UNFOUND )
1138 gdcmWarningMacro( "Bits Allocated (0028,0100) supposed to be mandatory");
1139 bitsAlloc = "16"; // default and arbitrary value, not to polute the output
1142 else if ( bitsAlloc == "64" )
1146 // useless since we have to bypass a bug ( >8 && < 16)
1147 else if ( bitsAlloc == "12" )
1149 // It will be unpacked
1153 else if ( bitsAlloc == "24" )
1155 // (in order no to be messed up by old RGB images)
1159 int i= atoi(bitsAlloc.c_str()); // fix a bug in some headers
1160 if ( i > 8 && i < 16 )
1166 if( IsSignedPixelData() )
1174 return bitsAlloc + sign;
1178 * \brief Check whether the pixels are signed (1) or UNsigned (0) data.
1179 * \warning The method defaults to false (UNsigned) when tag 0028|0103
1181 * The responsability of checking this value is left to the caller
1182 * (NO transformation is performed on the pixels to make then >0)
1183 * @return True when signed, false when UNsigned
1185 bool File::IsSignedPixelData()
1187 DataEntry *entry = GetDataEntry(0x0028, 0x0103);//"Pixel Representation"
1190 gdcmWarningMacro( "Pixel Representation (0028,0103) supposed to be "
1194 return entry->GetValue(0) != 0;
1198 * \brief Check whether this a monochrome picture (gray levels) or not,
1199 * using "Photometric Interpretation" tag (0x0028,0x0004).
1200 * @return true when "MONOCHROME1" or "MONOCHROME2". False otherwise.
1202 bool File::IsMonochrome()
1204 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1205 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1")
1206 || Util::DicomStringEqual(PhotometricInterp, "MONOCHROME2") )
1210 if ( PhotometricInterp == GDCM_UNFOUND )
1212 gdcmWarningMacro( "Photometric Interpretation (0028,0004) supposed to be "
1219 * \brief Check whether this a MONOCHROME1 picture (high values = dark)
1220 * or not using "Photometric Interpretation" tag (0x0028,0x0004).
1221 * @return true when "MONOCHROME1" . False otherwise.
1223 bool File::IsMonochrome1()
1225 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1226 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1") )
1230 if ( PhotometricInterp == GDCM_UNFOUND )
1232 gdcmWarningMacro( "Photometric Interpretation (0028,0004) : supposed to"
1233 << " be mandatory! ");
1239 * \brief Check whether this a "PALETTE COLOR" picture or not by accessing
1240 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1241 * @return true when "PALETTE COLOR". False otherwise.
1243 bool File::IsPaletteColor()
1245 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1246 if ( PhotometricInterp == "PALETTE COLOR " )
1250 if ( PhotometricInterp == GDCM_UNFOUND )
1252 gdcmDebugMacro( "Not found : Palette color (0028,0004)");
1258 * \brief Check whether this a "YBR_FULL" color picture or not by accessing
1259 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1260 * @return true when "YBR_FULL". False otherwise.
1262 bool File::IsYBRFull()
1264 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1265 if ( PhotometricInterp == "YBR_FULL" )
1269 if ( PhotometricInterp == GDCM_UNFOUND )
1271 gdcmDebugMacro( "Not found : YBR Full (0028,0004)");
1277 * \brief tells us if LUT are used
1278 * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
1279 * are NOT considered as LUT, since nobody knows
1280 * how to deal with them
1281 * Please warn me if you know sbdy that *does* know ... jprx
1282 * @return true if LUT Descriptors and LUT Tables were found
1286 // Check the presence of the LUT Descriptors, and LUT Tables
1288 if ( !GetDocEntry(0x0028,0x1101) )
1292 // LutDescriptorGreen
1293 if ( !GetDocEntry(0x0028,0x1102) )
1297 // LutDescriptorBlue
1298 if ( !GetDocEntry(0x0028,0x1103) )
1302 // Red Palette Color Lookup Table Data
1303 if ( !GetDocEntry(0x0028,0x1201) )
1307 // Green Palette Color Lookup Table Data
1308 if ( !GetDocEntry(0x0028,0x1202) )
1312 // Blue Palette Color Lookup Table Data
1313 if ( !GetDocEntry(0x0028,0x1203) )
1318 // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
1319 // NOT taken into account, but we don't know how to use it ...
1324 * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
1326 * @return Lookup Table number of Bits , 0 by default
1327 * when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
1328 * @ return bit number of each LUT item
1330 int File::GetLUTNbits()
1332 std::vector<std::string> tokens;
1335 //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red
1336 // = Lookup Table Desc-Blue
1337 // Consistency already checked in GetLUTLength
1338 std::string lutDescription = GetEntryString(0x0028,0x1101);
1339 if ( lutDescription == GDCM_UNFOUND )
1344 tokens.clear(); // clean any previous value
1345 Util::Tokenize ( lutDescription, tokens, "\\" );
1346 //LutLength=atoi(tokens[0].c_str());
1347 //LutDepth=atoi(tokens[1].c_str());
1349 lutNbits = atoi( tokens[2].c_str() );
1356 *\brief gets the info from 0028,1052 : Rescale Intercept
1357 * @return Rescale Intercept. defaulted to 0.0 is not found or empty
1359 float File::GetRescaleIntercept()
1361 // 0028 1052 DS IMG Rescale Intercept
1362 DataEntry *entry = GetDataEntry(0x0028, 0x1052);
1365 gdcmWarningMacro( "Missing Rescale Intercept (0028,1052)");
1368 return (float)entry->GetValue(0);
1373 *\brief gets the info from 0028,1053 : Rescale Slope
1374 * @return Rescale Slope. defaulted to 1.0 is not found or empty
1376 float File::GetRescaleSlope()
1378 // 0028 1053 DS IMG Rescale Slope
1379 DataEntry *entry = GetDataEntry(0x0028, 0x1053);
1382 gdcmDebugMacro( "Missing Rescale Slope (0028,1053)");
1385 return (float)entry->GetValue(0);
1389 * \brief This function is intended to user who doesn't want
1390 * to have to manage a LUT and expects to get an RBG Pixel image
1391 * (or a monochrome one, if no LUT found ...)
1392 * \warning to be used with GetImagePixels()
1393 * @return 1 if Gray level, 3 if Color (RGB, YBR, *or PALETTE COLOR*)
1395 int File::GetNumberOfScalarComponents()
1397 if ( GetSamplesPerPixel() == 3 )
1402 // 0028 0100 US IMG Bits Allocated
1403 // (in order no to be messed up by old RGB images)
1404 if ( GetEntryString(0x0028,0x0100) == "24" )
1409 std::string strPhotometricInterpretation = GetEntryString(0x0028,0x0004);
1411 if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
1413 if ( HasLUT() )// PALETTE COLOR is NOT enough
1423 // beware of trailing space at end of string
1424 // DICOM tags are never of odd length
1425 if ( strPhotometricInterpretation == GDCM_UNFOUND ||
1426 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
1427 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
1433 // we assume that *all* kinds of YBR are dealt with
1439 * \brief This function is intended to user that DOESN'T want
1440 * to get RGB pixels image when it's stored as a PALETTE COLOR image
1441 * - the (vtk) user is supposed to know how deal with LUTs -
1442 * \warning to be used with GetImagePixelsRaw()
1443 * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
1445 int File::GetNumberOfScalarComponentsRaw()
1447 // 0028 0100 US IMG Bits Allocated
1448 // (in order no to be messed up by old RGB images)
1449 if ( File::GetEntryString(0x0028,0x0100) == "24" )
1454 // we assume that *all* kinds of YBR are dealt with
1455 return GetSamplesPerPixel();
1459 * \brief Recover the offset (from the beginning of the file)
1460 * of *image* pixels (not *icone image* pixels, if any !)
1461 * @return Pixel Offset
1463 size_t File::GetPixelOffset()
1465 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1468 return pxlElement->GetOffset();
1472 gdcmWarningMacro( "Big trouble : Pixel Element ("
1473 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1479 * \brief Recover the pixel area length (in Bytes)
1480 * @return Pixel Element Length, as stored in the header
1481 * (NOT the memory space necessary to hold the Pixels
1482 * -in case of embeded compressed image-)
1483 * 0 : NOT USABLE file. The caller has to check.
1485 size_t File::GetPixelAreaLength()
1487 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1490 return pxlElement->GetLength();
1494 gdcmWarningMacro( "Big trouble : Pixel Element ("
1495 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1501 * \brief Adds the characteristics of a new element we want to anonymize
1502 * @param group Group number of the target tag.
1503 * @param elem Element number of the target tag.
1504 * @param value new value (string) to substitute with
1506 void File::AddAnonymizeElement (uint16_t group, uint16_t elem,
1507 std::string const &value)
1513 UserAnonymizeList.push_back(el);
1517 * \brief Overwrites in the file the values of the DicomElements
1520 void File::AnonymizeNoLoad()
1522 std::fstream *fp = new std::fstream(Filename.c_str(),
1523 std::ios::in | std::ios::out | std::ios::binary);
1527 uint32_t valLgth = 0;
1528 std::string *spaces;
1529 for (ListElements::iterator it = UserAnonymizeList.begin();
1530 it != UserAnonymizeList.end();
1534 //std::cout << "File::AnonymizeNoLoad -------" << std::hex <<(*it).Group <<"|"<<
1536 // << "[" << (*it).Value << "] "<< std::dec << std::endl;
1537 d = GetDocEntry( (*it).Group, (*it).Elem);
1542 if ( dynamic_cast<SeqEntry *>(d) )
1544 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1548 valLgth = (*it).Value.size();
1552 offset = d->GetOffset();
1553 lgth = d->GetLength();
1555 //std::cout << "lgth " << lgth << " valLgth " << valLgth << std::endl;
1558 spaces = new std::string( lgth-valLgth, ' ');
1559 (*it).Value = (*it).Value + *spaces;
1560 //std::cout << "[" << (*it).Value << "] " << lgth << std::endl;
1563 fp->seekp( offset, std::ios::beg );
1564 fp->write( (*it).Value.c_str(), lgth );
1572 * \brief anonymize a File (remove Patient's personal info passed with
1573 * AddAnonymizeElement()
1574 * \note You cannot Anonymize a DataEntry (to be fixed)
1576 bool File::AnonymizeFile()
1578 // If Anonymisation list is empty, let's perform some basic anonymization
1579 if ( UserAnonymizeList.begin() == UserAnonymizeList.end() )
1581 // If exist, replace by spaces
1582 SetEntryString(" ",0x0010, 0x2154); // Telephone
1583 SetEntryString(" ",0x0010, 0x1040); // Adress
1584 SetEntryString(" ",0x0010, 0x0020); // Patient ID
1586 DocEntry *patientNameHE = GetDocEntry (0x0010, 0x0010);
1588 if ( patientNameHE ) // we replace it by Study Instance UID (why not ?)
1590 std::string studyInstanceUID = GetEntryString (0x0020, 0x000d);
1591 if ( studyInstanceUID != GDCM_UNFOUND )
1593 SetEntryString(studyInstanceUID, 0x0010, 0x0010);
1597 SetEntryString("anonymized", 0x0010, 0x0010);
1604 for (ListElements::iterator it = UserAnonymizeList.begin();
1605 it != UserAnonymizeList.end();
1608 d = GetDocEntry( (*it).Group, (*it).Elem);
1613 if ( dynamic_cast<SeqEntry *>(d) )
1615 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1619 if ( dynamic_cast<DataEntry *>(d) )
1621 gdcmWarningMacro( "To 'Anonymize' a DataEntry, better use AnonymizeNoLoad (FIXME) ");
1625 SetEntryString ((*it).Value, (*it).Group, (*it).Elem);
1629 // In order to make definitively impossible any further identification
1630 // remove or replace all the stuff that contains a Date
1632 //0008 0012 DA ID Instance Creation Date
1633 //0008 0020 DA ID Study Date
1634 //0008 0021 DA ID Series Date
1635 //0008 0022 DA ID Acquisition Date
1636 //0008 0023 DA ID Content Date
1637 //0008 0024 DA ID Overlay Date
1638 //0008 0025 DA ID Curve Date
1639 //0008 002a DT ID Acquisition Datetime
1640 //0018 9074 DT ACQ Frame Acquisition Datetime
1641 //0018 9151 DT ACQ Frame Reference Datetime
1642 //0018 a002 DT ACQ Contribution Date Time
1643 //0020 3403 SH REL Modified Image Date (RET)
1644 //0032 0032 DA SDY Study Verified Date
1645 //0032 0034 DA SDY Study Read Date
1646 //0032 1000 DA SDY Scheduled Study Start Date
1647 //0032 1010 DA SDY Scheduled Study Stop Date
1648 //0032 1040 DA SDY Study Arrival Date
1649 //0032 1050 DA SDY Study Completion Date
1650 //0038 001a DA VIS Scheduled Admission Date
1651 //0038 001c DA VIS Scheduled Discharge Date
1652 //0038 0020 DA VIS Admitting Date
1653 //0038 0030 DA VIS Discharge Date
1654 //0040 0002 DA PRC Scheduled Procedure Step Start Date
1655 //0040 0004 DA PRC Scheduled Procedure Step End Date
1656 //0040 0244 DA PRC Performed Procedure Step Start Date
1657 //0040 0250 DA PRC Performed Procedure Step End Date
1658 //0040 2004 DA PRC Issue Date of Imaging Service Request
1659 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
1660 //0040 4011 DT PRC Expected Completion Date and Time
1661 //0040 a030 DT PRC Verification Date Time
1662 //0040 a032 DT PRC Observation Date Time
1663 //0040 a120 DT PRC DateTime
1664 //0040 a121 DA PRC Date
1665 //0040 a13a DT PRC Referenced Datetime
1666 //0070 0082 DA ??? Presentation Creation Date
1667 //0100 0420 DT ??? SOP Autorization Date and Time
1668 //0400 0105 DT ??? Digital Signature DateTime
1669 //2100 0040 DA PJ Creation Date
1670 //3006 0008 DA SSET Structure Set Date
1671 //3008 0024 DA ??? Treatment Control Point Date
1672 //3008 0054 DA ??? First Treatment Date
1673 //3008 0056 DA ??? Most Recent Treatment Date
1674 //3008 0162 DA ??? Safe Position Exit Date
1675 //3008 0166 DA ??? Safe Position Return Date
1676 //3008 0250 DA ??? Treatment Date
1677 //300a 0006 DA RT RT Plan Date
1678 //300a 022c DA RT Air Kerma Rate Reference Date
1679 //300e 0004 DA RT Review Date
1685 * \brief Performs some consistency checking on various 'File related'
1686 * (as opposed to 'DicomDir related') entries
1687 * then writes in a file all the (Dicom Elements) included the Pixels
1688 * @param fileName file name to write to
1689 * @param writetype type of the file to be written
1690 * (ACR, ExplicitVR, ImplicitVR)
1692 bool File::Write(std::string fileName, FileType writetype)
1694 std::ofstream *fp = new std::ofstream(fileName.c_str(),
1695 std::ios::out | std::ios::binary);
1698 gdcmWarningMacro("Failed to open (write) File: " << fileName.c_str());
1702 // Entry : 0002|0000 = group length -> recalculated
1703 DataEntry *e0000 = GetDataEntry(0x0002,0x0000);
1706 std::ostringstream sLen;
1707 sLen << ComputeGroup0002Length( );
1708 e0000->SetString(sLen.str());
1711 /// \todo FIXME : Derma?.dcm does not have it...let's remove it ?!? JPRx
1712 if( writetype != JPEG )
1714 int i_lgPix = GetEntryLength(GrPixel, NumPixel);
1717 // no (GrPixel, NumPixel) element
1718 std::string s_lgPix = Util::Format("%d", i_lgPix+12);
1719 s_lgPix = Util::DicomString( s_lgPix.c_str() );
1720 InsertEntryString(s_lgPix,GrPixel, 0x0000, "UL");
1723 Document::WriteContent(fp, writetype);
1731 //-----------------------------------------------------------------------------
1735 //-----------------------------------------------------------------------------
1738 * \brief Parse pixel data from disk of [multi-]fragment RLE encoding.
1739 * Compute the RLE extra information and store it in \ref RLEInfo
1740 * for later pixel retrieval usage.
1742 void File::ComputeRLEInfo()
1744 std::string ts = GetTransferSyntax();
1745 if ( !Global::GetTS()->IsRLELossless(ts) )
1750 // Encoded pixel data: for the time being we are only concerned with
1751 // Jpeg or RLE Pixel data encodings.
1752 // As stated in PS 3.5-2003, section 8.2 p44:
1753 // "If sent in Encapsulated Format (i.e. other than the Native Format) the
1754 // value representation OB is used".
1755 // Hence we expect an OB value representation. Concerning OB VR,
1756 // the section PS 3.5-2003, section A.4.c p 58-59, states:
1757 // "For the Value Representations OB and OW, the encoding shall meet the
1758 // following specifications depending on the Data element tag:"
1760 // - the first item in the sequence of items before the encoded pixel
1761 // data stream shall be basic offset table item. The basic offset table
1762 // item value, however, is not required to be present"
1763 ReadEncapsulatedBasicOffsetTable();
1765 // Encapsulated RLE Compressed Images (see PS 3.5-2003, Annex G)
1766 // Loop on the individual frame[s] and store the information
1767 // on the RLE fragments in a RLEFramesInfo.
1768 // Note: - when only a single frame is present, this is a
1770 // - when more than one frame are present, then we are in
1771 // the case of a multi-frame image.
1775 while ( (frameLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1777 // Since we have read the basic offset table, let's check the value were correct
1778 // or else produce a warning:
1779 if ( BasicOffsetTableItemValue )
1781 // If a BasicOffsetTableItemValue was read
1782 uint32_t individualLength = BasicOffsetTableItemValue[i];
1783 assert( individualLength == sum ); // REMOVE that if this is a problem
1784 if( individualLength != sum )
1786 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght" );
1788 sum += frameLength + 8;
1791 // Parse the RLE Header and store the corresponding RLE Segment
1792 // Offset Table information on fragments of this current Frame.
1793 // Note that the fragment pixels themselves are not loaded
1794 // (but just skipped).
1795 long frameOffset = Fp->tellg(); // once per fragment
1797 uint32_t nbRleSegments = ReadInt32();
1798 if ( nbRleSegments > 16 )
1800 // There should be at most 15 segments (refer to RLEFrame class)
1801 gdcmWarningMacro( "Too many segments.");
1804 uint32_t rleSegmentOffsetTable[16];
1805 for( int k = 1; k <= 15; k++ )
1807 rleSegmentOffsetTable[k] = ReadInt32();
1810 // Deduce from both RLE Header and frameLength
1811 // the fragment length, and again store this info
1812 // in a RLEFramesInfo.
1813 long rleSegmentLength[15];
1814 // skipping (not reading) RLE Segments
1815 if ( nbRleSegments > 1)
1817 for(unsigned int k = 1; k <= nbRleSegments-1; k++)
1819 rleSegmentLength[k] = rleSegmentOffsetTable[k+1]
1820 - rleSegmentOffsetTable[k];
1821 SkipBytes(rleSegmentLength[k]);
1825 rleSegmentLength[nbRleSegments] = frameLength
1826 - rleSegmentOffsetTable[nbRleSegments];
1827 SkipBytes(rleSegmentLength[nbRleSegments]);
1829 // Store the collected info
1830 RLEFrame *newFrame = new RLEFrame;
1831 newFrame->SetNumberOfFragments(nbRleSegments);
1832 for( unsigned int uk = 1; uk <= nbRleSegments; uk++ )
1834 newFrame->SetOffset(uk,frameOffset + rleSegmentOffsetTable[uk]);
1835 newFrame->SetLength(uk,rleSegmentLength[uk]);
1837 RLEInfo->AddFrame(newFrame);
1840 // Make sure that we encounter a 'Sequence Delimiter Item'
1841 // at the end of the item :
1842 if ( !ReadTag(0xfffe, 0xe0dd) ) // once per RLE File
1844 gdcmWarningMacro( "No sequence delimiter item at end of RLE item sequence");
1849 * \brief Parse pixel data from disk of [multi-]fragment Jpeg encoding.
1850 * Compute the jpeg extra information (fragment[s] offset[s] and
1851 * length) and store it[them] in \ref JPEGInfo for later pixel
1854 void File::ComputeJPEGFragmentInfo()
1856 // If you need to, look for comments of ComputeRLEInfo().
1857 std::string ts = GetTransferSyntax();
1858 if ( ! Global::GetTS()->IsJPEG(ts) )
1863 ReadEncapsulatedBasicOffsetTable();
1865 // Loop on the fragments[s] and store the parsed information in a
1867 long fragmentLength;
1870 while ( (fragmentLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
1872 // Since we have read the basic offset table, let's check the value were correct
1873 // or else produce a warning:
1874 // A.4 Transfer syntaxes for encapsulation of encoded pixel data:
1875 // When the Item Value is present, the Basic Offset Table Item Value shall contain
1876 // concatenated 32-bit unsigned integer values that are byte offsets to the first
1877 // byte of the Item Tag of the first fragment for each frame in the Sequence of
1878 // Items. These offsets are measured from the first byte of the first Item Tag
1879 // following the Basic Offset Table item (See Table A.4-2).
1881 if ( BasicOffsetTableItemValue )
1883 // If a BasicOffsetTableItemValue was read
1884 uint32_t individualLength = BasicOffsetTableItemValue[i];
1885 //assert( individualLength == sum ); // Seems like 00191113.dcm is off by one ??
1886 if( individualLength != sum )
1888 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght:" <<
1889 individualLength << " != " << sum );
1891 sum += fragmentLength + 8;
1895 long fragmentOffset = Fp->tellg(); // Once per fragment
1896 // Store the collected info
1897 JPEGFragment *newFragment = new JPEGFragment;
1898 newFragment->SetOffset(fragmentOffset);
1899 newFragment->SetLength(fragmentLength);
1900 JPEGInfo->AddFragment(newFragment);
1902 SkipBytes(fragmentLength);
1905 // Make sure that we encounter a 'Sequence Delimiter Item'
1906 // at the end of the item :
1907 if ( !ReadTag(0xfffe, 0xe0dd) )
1909 gdcmWarningMacro( "No sequence delimiter item at end of JPEG item sequence");
1914 * \brief Assuming the internal file pointer \ref Document::Fp
1915 * is placed at the beginning of a tag, check whether this
1916 * tag is (TestGroup, TestElem).
1917 * \warning On success the internal file pointer \ref Document::Fp
1918 * is modified to point after the tag.
1919 * On failure (i.e. when the tag wasn't the expected tag
1920 * (TestGroup, TestElem) the internal file pointer
1921 * \ref Document::Fp is restored to it's original position.
1922 * @param testGroup The expected group of the tag.
1923 * @param testElem The expected Element of the tag.
1924 * @return True on success, false otherwise.
1926 bool File::ReadTag(uint16_t testGroup, uint16_t testElem)
1928 long positionOnEntry = Fp->tellg(); // Only when reading fragments
1929 //long currentPosition = positionOnEntry; // On debugging purposes
1931 // Read the Item Tag group and element, and make
1932 // sure they are what we expected:
1933 uint16_t itemTagGroup;
1934 uint16_t itemTagElem;
1937 itemTagGroup = ReadInt16();
1938 itemTagElem = ReadInt16();
1940 catch ( FormatError )
1942 gdcmErrorMacro( "Can not read tag for "
1943 << " We should have found tag ("
1944 << DictEntry::TranslateToKey(testGroup,testElem) << ")"
1949 if ( itemTagGroup != testGroup || itemTagElem != testElem )
1951 // in order not to pollute output we don't warn on 'delimitors'
1952 if (itemTagGroup != 0xfffe || testGroup != 0xfffe )
1953 gdcmWarningMacro( "Wrong Item Tag found:"
1954 << " We should have found tag ("
1955 << DictEntry::TranslateToKey(testGroup,testElem) << ")" << std::endl
1956 << " but instead we encountered tag ("
1957 << DictEntry::TranslateToKey(itemTagGroup,itemTagElem) << ")"
1958 << " at address: " << " 0x(" << std::hex
1959 << (unsigned int)positionOnEntry << std::dec << ")"
1961 Fp->seekg(positionOnEntry, std::ios::beg);
1969 * \brief Assuming the internal file pointer \ref Document::Fp
1970 * is placed at the beginning of a tag (TestGroup, TestElement),
1971 * read the length associated to the Tag.
1972 * \warning On success the internal file pointer \ref Document::Fp
1973 * is modified to point after the tag and it's length.
1974 * On failure (i.e. when the tag wasn't the expected tag
1975 * (TestGroup, TestElement) the internal file pointer
1976 * \ref Document::Fp is restored to it's original position.
1977 * @param testGroup The expected Group of the tag.
1978 * @param testElem The expected Element of the tag.
1979 * @return On success returns the length associated to the tag. On failure
1982 uint32_t File::ReadTagLength(uint16_t testGroup, uint16_t testElem)
1985 if ( !ReadTag(testGroup, testElem) )
1987 // Avoid polutting output
1988 if ( testGroup != 0xfffe )
1989 gdcmErrorMacro( "ReadTag did not succeed for ("
1990 << DictEntry::TranslateToKey(testGroup,testElem)
1995 //// Then read the associated Item Length
1997 // long currentPosition = Fp->tellg(); // save time // JPRx
1998 uint32_t itemLength = ReadInt32();
1999 gdcmDebugMacro( "Basic Item Length is: " << itemLength
2000 // << " at address: " << std::hex << (unsigned int)currentPosition
2006 * \brief When parsing the Pixel Data of an encapsulated file, read
2007 * the basic offset table (when present, and BTW dump it).
2009 void File::ReadEncapsulatedBasicOffsetTable()
2011 //// Read the Basic Offset Table Item Tag length...
2012 uint32_t itemLength = ReadTagLength(0xfffe, 0xe000);
2014 // When present, read the basic offset table itself.
2015 // Notes: - since the presence of this basic offset table is optional
2016 // we can't rely on it for the implementation, and we will simply
2017 // trash it's content (when present).
2018 // - still, when present, we could add some further checks on the
2019 // lengths, but we won't bother with such fuses for the time being.
2020 if ( itemLength != 0 )
2022 char *charBasicOffsetTableItemValue = new char[itemLength];
2023 Fp->read(charBasicOffsetTableItemValue, itemLength);
2024 unsigned int nbEntries = itemLength/4;
2025 assert( nbEntries*4 == itemLength); // Make sure this is a multiple
2026 BasicOffsetTableItemValue = new uint32_t[nbEntries];
2028 for (unsigned int i=0; i < nbEntries; i++ )
2030 BasicOffsetTableItemValue[i] = *((uint32_t*)(&charBasicOffsetTableItemValue[4*i]));
2031 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
2032 uint32_t val = BasicOffsetTableItemValue[i];
2033 BasicOffsetTableItemValue[i]
2034 = ( (val<<24) | ((val<<8) & 0x00ff0000) |
2035 ( (val>>8) & 0x0000ff00) | (val>>24) );
2037 gdcmDebugMacro( "Read one length for: " <<
2038 std::hex << BasicOffsetTableItemValue[i] );
2041 delete[] charBasicOffsetTableItemValue;
2045 // These are the deprecated method that one day should be removed (after the next release)
2047 //#ifndef GDCM_LEGACY_REMOVE
2049 * \ brief Loader. (DEPRECATED : temporaryly kept not to break the API)
2050 * @ param fileName file to be open for parsing
2051 * @ return false if file cannot be open or no swap info was found,
2052 * or no tag was found.
2053 * @deprecated Use the Load() [ + SetLoadMode() ] + SetFileName() functions instead
2056 bool File::Load( std::string const &fileName )
2058 GDCM_LEGACY_REPLACED_BODY(File::Load(std::string), "1.2",
2060 SetFileName( fileName );
2061 if ( ! this->Document::Load( ) )
2064 return DoTheLoadingJob( );
2068 //-----------------------------------------------------------------------------
2071 //-----------------------------------------------------------------------------
2072 } // end namespace gdcm