1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2007/07/03 08:17:24 $
7 Version: $Revision: 1.333 $
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"
99 #include "gdcmSQItem.h"
102 #include <stdio.h> //sscanf
103 #include <stdlib.h> // for atoi
105 namespace GDCM_NAME_SPACE
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 // ts["1.2.840.10008.5.1.4.1.1.4.1"] = "Enhanced MR Image Storage";
478 bool File::GetSpacing(float &xspacing, float &yspacing, float &zspacing)
480 xspacing = yspacing = zspacing = 1.0;
481 TS *ts = Global::GetTS();
482 std::string sopclassuid_used;
483 // D 0002|0002 [UI] [Media Storage SOP Class UID]
485 //const std::string &mediastoragesopclassuid_str = GetEntryValue(0x0002,0x0002);
486 const std::string &mediastoragesopclassuid_str = GetEntryString(0x0002,0x0002);
487 const std::string &mediastoragesopclassuid = ts->GetValue(mediastoragesopclassuid_str);
488 //D 0008|0016 [UI] [SOP Class UID]
489 const std::string &sopclassuid_str = GetEntryString(0x0008,0x0016);
490 const std::string &sopclassuid = ts->GetValue(sopclassuid_str);
491 if ( mediastoragesopclassuid == GDCM_UNFOUND && sopclassuid == GDCM_UNFOUND )
497 if( mediastoragesopclassuid == sopclassuid )
499 sopclassuid_used = mediastoragesopclassuid;
503 gdcmWarningMacro( "Inconsistant SOP Class UID: "
504 << mediastoragesopclassuid << " and " << sopclassuid );
508 // ok we have now the correc SOP Class UID
509 if( sopclassuid_used == "Enhanced MR Image Storage" )
511 SeqEntry *PerframeFunctionalGroupsSequence = GetSeqEntry(0x5200,0x9230);
512 unsigned int n = PerframeFunctionalGroupsSequence->GetNumberOfSQItems();
513 if( !n ) return false;
514 SQItem *item1 = PerframeFunctionalGroupsSequence->GetFirstSQItem();
515 DocEntry *p = item1->GetDocEntry(0x0028,0x9110);
516 if( !p ) return false;
517 SeqEntry *seq = dynamic_cast<SeqEntry*>(p);
518 unsigned int n1 = seq->GetNumberOfSQItems();
519 if( !n1 ) return false;
520 SQItem *item2 = seq->GetFirstSQItem();
521 // D 0028|0030 [DS] [Pixel Spacing] [0.83333331346511\0.83333331346511 ]
522 DocEntry *p2 = item2->GetDocEntry(0x0028,0x0030);
523 if( !p2 ) return false;
524 DataEntry *entry = dynamic_cast<DataEntry *>(p2);
525 std::string spacing = entry->GetString();
526 if ( sscanf( spacing.c_str(), "%f\\%f", &yspacing, &xspacing) != 2 )
528 xspacing = yspacing = 1.;
531 // D 0018|0050 [DS] [Slice Thickness] [1 ]
532 DocEntry *p3 = item2->GetDocEntry(0x0018,0x0050);
533 if( !p3 ) return false;
534 DataEntry *entry2 = dynamic_cast<DataEntry *>(p3);
535 std::string thickness = entry2->GetString();
536 if ( sscanf( thickness.c_str(), "%f", &zspacing) != 1 )
548 * \brief Retrieve the -unnormalized- number of 'times' of '4D image'.
549 * User has to tell gdcm the location of this '4th Dimension component'
550 * using SetFourthDimensionLocation() method before.
551 * \warning The defaulted value is 1.
552 * @return The encountered size when found, 1 by default
553 * (The file doesn't contain a '4D image'.).
557 if (FourthDimensionLocation == TagKey(0,0) )// 4D location is not set : not a 4D object
560 DataEntry *entry = GetDataEntry(FourthDimensionLocation.GetGroup(),
561 FourthDimensionLocation.GetElement() );
564 gdcmWarningMacro( " FourthDimensionLocation not found at : " <<
565 std::hex << FourthDimensionLocation.GetGroup()
566 << "|" << FourthDimensionLocation.GetElement());
571 return (int)entry->GetValue(0);
578 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
579 * then 0028,0030 : Pixel Spacing
581 * @return X dimension of a pixel
583 float File::GetXSpacing()
585 float xspacing = 1.0;
586 float yspacing = 1.0;
587 float zspacing = 1.0;
593 if ( GetSpacing(xspacing,yspacing,zspacing) )
600 From:David Clunie - view profile
601 Date:Wed, May 24 2006 1:12 pm
602 Email:David Clunie <dclu...@dclunie.com>
603 Groups:comp.protocols.dicom
605 The short answer is that:
607 - (0018,1164) describes a spacing equivalent to that which
608 would be measured off a film in projection radiography
610 - (0018,7022) does not describe the image pixels themselves,
611 since detector elements may have been binned to produce
614 - (0018,7020) may be different from (0018,7022) since there
615 may be non-sensitive material separating individual
616 detectors (i.e. the size is smaller than the spacing
619 Only (0018,1164) is relevant when measuring things; the
620 detector-specific attributes are there to describe the
625 PS. For ultrasound you need to use Region Calibration.
629 It *SHOULD* first find the IOD and then deduce which tags to read
630 Eg: Cross section this is in Pixel Spacing (0028,0030)
631 CR is in Imager Pixel Spacing (0018,1164)
632 US is in Pixel Aspect Ratio (0028,0034)
634 (3002,0011) Image Plane Pixel Spacing
635 (3002,0012) RT Image Position
637 (3004,000c) for deducing Z spacing
640 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
642 /// \todo check the various SOP Class
643 /// to get the Pixel Spacing at the proper location
645 ///\todo find images to check if it *actually* works
647 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
648 // Ultrasound Image Storage (Retired)
649 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
650 // Ultrasound Image Storage
651 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
652 // Ultrasound Multi-Frame Storage (Retired)
653 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
654 // Ultrasound Multi-FrameImage Storage
656 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
657 // - guess how to deduce the spacing (FOV ?, ??)
659 entry = GetDataEntry(0x0028,0x0034);
662 nbValue = entry->GetValueCount();
664 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
665 << "has a wrong number of values :" << nbValue);
667 xspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
675 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.1") )
676 // Computed Radiography Image Storage
678 // CR is in Imager Pixel Spacing (0018,1164)//
681 // go on with old method ...
682 // ---------------------
683 // To follow David Clunie's advice, we first check ImagerPixelSpacing
685 entry = GetDataEntry(0x0018,0x1164);
688 nbValue = entry->GetValueCount();
689 // Can't use IsValueCountValid because of the complex heuristic.
691 gdcmWarningMacro("ImagerPixelSpacing (0x0018,0x1164) "
692 << "has a wrong number of values :" << nbValue);
695 xspacing = (float)entry->GetValue(2);
696 else if( nbValue >= 2 )
697 xspacing = (float)entry->GetValue(1);
699 xspacing = (float)entry->GetValue(0);
701 if ( xspacing == 0.0 )
707 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
710 entry = GetDataEntry(0x0028,0x0030);
713 nbValue = entry->GetValueCount();
715 gdcmWarningMacro("PixelSpacing (0x0018,0x0030) "
716 << "has a wrong number of values :" << nbValue);
719 xspacing = (float)entry->GetValue(2);
720 else if( nbValue >= 2 )
721 xspacing = (float)entry->GetValue(1);
723 xspacing = (float)entry->GetValue(0);
725 if ( xspacing == 0.0 )
731 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
737 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
738 * then from 0028,0030 : Pixel Spacing
740 * @return Y dimension of a pixel
742 float File::GetYSpacing()
744 float xspacing = 1., yspacing = 1.0, zspacing = 1.;
749 if ( GetSpacing(xspacing,yspacing,zspacing) )
756 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
758 /// \todo check the various SOP Class
759 /// to get the Pixel Spacing at the proper location
761 ///\todo find images to check if it *actually* works
763 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
764 // Ultrasound Image Storage (Retired)
765 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
766 // Ultrasound Image Storage
767 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
768 // Ultrasound Multi-Frame Storage (Retired)
769 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
770 // Ultrasound Multi-FrameImage Storage
772 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
773 // - no way to deduce the spacing/
775 entry = GetDataEntry(0x0028,0x0034);
778 nbValue = entry->GetValueCount();
780 yspacing = (float)entry->GetValue(0)/(float)entry->GetValue(1);
781 //std::cout << "ys " << yspacing << std::endl;
786 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
787 << "has a wrong number of values :" << nbValue);
791 else if (nbValue == 1 ) {
792 yspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
802 // go on with old method ...
803 // ---------------------
804 // To follow David Clunie's advice, we first check ImagerPixelSpacing
806 // To follow David Clunie's advice, we first check ImagerPixelSpacing
808 entry = GetDataEntry(0x0018,0x1164);
811 yspacing = (float)entry->GetValue(0);
813 if ( yspacing == 0.0 )
819 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
822 entry = GetDataEntry(0x0028,0x0030);
825 yspacing = (float)entry->GetValue(0);
827 if ( yspacing == 0.0 )
833 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
840 * \brief gets the info from 0018,0088 : Space Between Slices
841 * else from 0018,0050 : Slice Thickness
844 * When an element is missing, we suppose slices join together
845 * (no overlapping, no interslice gap) but we have no way to check it !
846 * For *Dicom* images, ZSpacing *should be* calculated using
847 * XOrigin, YOrigin, ZOrigin (of the top left image corner)
848 * of 2 consecutive images, and the Orientation
849 * Computing ZSpacing on a single image is not really meaningfull !
850 * @return Z dimension of a voxel-to be
852 float File::GetZSpacing()
855 float xspacing = 1.0;
856 float yspacing = 1.0;
857 float zspacing = 1.0;
858 if ( GetSpacing(xspacing,yspacing,zspacing) )
863 // Spacing Between Slices : distance between the middle of 2 slices
865 // jointives (Spacing between Slices = Slice Thickness)
866 // overlapping (Spacing between Slices < Slice Thickness)
867 // disjointes (Spacing between Slices > Slice Thickness)
868 // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
869 // It only concerns the MRI guys, not people wanting to visualize volumes
870 // If Spacing Between Slices is missing,
871 // we suppose slices joint together
872 DataEntry *entry = GetDataEntry(0x0018,0x0088);
874 { zspacing = (float)entry->GetValue(0);
876 if ( zspacing == 0.0 )
881 gdcmWarningMacro("Unfound Spacing Between Slices (0018,0088)");
883 // if no 'Spacing Between Slices' is found,
884 // we assume slices join together
885 // (no overlapping, no interslice gap)
886 entry = GetDataEntry(0x0018,0x0050);
889 zspacing = (float)entry->GetValue(0);
891 if ( zspacing == 0.0 )
896 gdcmWarningMacro("Unfound Slice Thickness (0018,0050)");
898 // if no 'Spacing Between Slices' is found,
899 // we assume slices join together
900 // (no overlapping, no interslice gap)
901 entry = GetDataEntry(0x3004,0x000c);
904 float z1 = (float)entry->GetValue(0);
905 float z2 = (float)entry->GetValue(1);
906 zspacing = z2 - z1; // can be negative...
908 if ( zspacing == 0.0 )
917 * \brief gets the info from 0020,0032 : Image Position Patient
918 * else from 0020,0030 : Image Position (RET)
920 * @return up-left image corner X position
922 float File::GetXOrigin()
924 DataEntry *entry = GetDataEntry(0x0020,0x0032);
927 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
928 entry = GetDataEntry(0x0020,0x0030);
931 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
936 if( entry->GetValueCount() == 3 )
938 if (!entry->IsValueCountValid() )
940 gdcmErrorMacro( "Invalid Value Count" );
942 return (float)entry->GetValue(0);
948 * \brief gets the info from 0020,0032 : Image Position Patient
949 * else from 0020,0030 : Image Position (RET)
951 * @return up-left image corner Y position
953 float File::GetYOrigin()
955 DataEntry *entry = GetDataEntry(0x0020,0x0032);
958 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
959 entry = GetDataEntry(0x0020,0x0030);
962 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
967 if( entry->GetValueCount() == 3 )
969 if (!entry->IsValueCountValid() )
971 gdcmErrorMacro( "Invalid Value Count" );
973 return (float)entry->GetValue(1);
979 * \brief gets the info from 0020,0032 : Image Position Patient
980 * else from 0020,0030 : Image Position (RET)
981 * else from 0020,1041 : Slice Location
982 * else from 0020,0050 : Location
984 * @return up-left image corner Z position
986 float File::GetZOrigin()
988 DataEntry *entry = GetDataEntry(0x0020,0x0032);
991 if( entry->GetValueCount() == 3 )
993 if (!entry->IsValueCountValid() )
995 gdcmErrorMacro( "Invalid Value Count" );
997 return (float)entry->GetValue(2);
999 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)");
1003 entry = GetDataEntry(0x0020,0x0030);
1006 if( entry->GetValueCount() == 3 )
1008 if (!entry->IsValueCountValid() )
1010 gdcmErrorMacro( "Invalid Value Count" );
1012 return (float)entry->GetValue(2);
1014 gdcmWarningMacro( "Wrong Image Position (RET) (0020,0030)");
1018 // for *very* old ACR-NEMA images
1019 entry = GetDataEntry(0x0020,0x1041);
1022 if( entry->GetValueCount() == 1 )
1024 if (!entry->IsValueCountValid() )
1026 gdcmErrorMacro( "Invalid Value Count" );
1028 return (float)entry->GetValue(0); // VM=1 !
1030 gdcmWarningMacro( "Wrong Slice Location (0020,1041)");
1034 entry = GetDataEntry(0x0020,0x0050);
1037 if( entry->GetValueCount() == 1 )
1039 if (!entry->IsValueCountValid() )
1041 gdcmErrorMacro( "Invalid Value Count" );
1043 return (float)entry->GetValue(0);
1045 gdcmWarningMacro( "Wrong Location (0020,0050)");
1048 return 0.; // Hopeless
1052 * \brief gets the info from 0020,0037 : Image Orientation Patient
1053 * or from 0020 0035 : Image Orientation (RET)
1055 * (needed to organize DICOM files based on their x,y,z position)
1057 * @param iop adress of the (6)float array to receive values.
1058 * (defaulted as 1.,0.,0.,0.,1.,0. if nothing -or inconsistent stuff-
1060 * @return true when one of the tag -with consistent values- is found
1061 * false when nothing or inconsistent stuff - is found
1063 bool File::GetImageOrientationPatient( float iop[6] )
1065 std::string strImOriPat;
1066 //iop is supposed to be float[6]
1067 iop[0] = iop[4] = 1.;
1068 iop[1] = iop[2] = iop[3] = iop[5] = 0.;
1070 // 0020 0037 DS REL Image Orientation (Patient)
1071 if ( (strImOriPat = GetEntryString(0x0020,0x0037)) != GDCM_UNFOUND )
1073 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
1074 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
1076 gdcmWarningMacro( "Wrong Image Orientation Patient (0020,0037)."
1077 << " Less than 6 values were found." );
1083 // 0020 0035 DS REL Image Orientation (RET)
1084 else if ( (strImOriPat = GetEntryString(0x0020,0x0035)) != GDCM_UNFOUND )
1086 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
1087 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
1089 gdcmWarningMacro( "wrong Image Orientation Patient (0020,0035). "
1090 << "Less than 6 values were found." );
1099 * \brief gets the cosine of image X axis, against patient X axis
1100 * (Sorry, but Python needs it :-( )
1101 * @return cosine of image X axis, against patient X axis
1103 float File::GetXCosineOnX()
1106 GetImageOrientationPatient( iop );
1111 * \brief gets the cosine of image X axis, against patient Y axis
1112 * (Sorry, but Python needs it :-( )
1113 * @return cosine of image X axis, against patient Y axis
1115 float File::GetXCosineOnY()
1118 GetImageOrientationPatient( iop );
1123 * \brief gets the cosine of image X axis, against patient Z axis
1124 * (Sorry, but Python needs it :-( )
1125 * @return cosine of image X axis, against patient Z axis
1127 float File::GetXCosineOnZ()
1130 GetImageOrientationPatient( iop );
1135 * \brief gets the cosine of image Y axis, against patient X axis
1136 * (Sorry, but Python needs it :-( )
1137 * @return cosine of image Y axis, against patient X axis
1139 float File::GetYCosineOnX()
1142 GetImageOrientationPatient( iop );
1147 * \brief gets the cosine of image Y axis, against patient Y axis
1148 * (Sorry, but Python needs it :-( )
1149 * @return cosine of image Y axis, against patient Y axis
1151 float File::GetYCosineOnY()
1154 GetImageOrientationPatient( iop );
1159 * \brief gets the cosine of image Y axis, against patient Z axis
1160 * (Sorry, but Python needs it :-( )
1161 * @return cosine of image Y axis, against patient Z axis
1163 float File::GetYCosineOnZ()
1166 GetImageOrientationPatient( iop );
1170 * \brief gets the info from 0020,0032 : Image Position Patient
1171 * or from 0020 0030 : Image Position (RET)
1173 * @param ipp adress of the (3)float array to receive values.
1174 * (defaulted as 0.,0.,0. if nothing -or inconsistent stuff-
1176 * @return true when one of the tag -with consistent values- is found
1177 * false when nothing or inconsistent stuff - is found
1179 bool File::GetImagePositionPatient( float ipp[3] )
1181 std::string strImPosiPat;
1182 //ipp is supposed to be float[3]
1183 ipp[0] = ipp[1] = ipp[2] = 0.;
1185 // 0020 0032 DS REL Image Position (Patient)
1186 strImPosiPat = GetEntryString(0x0020,0x0032);
1187 if ( strImPosiPat != GDCM_UNFOUND )
1189 if ( sscanf( strImPosiPat.c_str(), "%f \\ %f \\%f ",
1190 &ipp[0], &ipp[1], &ipp[2]) != 3 )
1192 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)."
1193 << " Less than 3 values were found." );
1199 // 0020 0030 DS REL Image Position (RET)
1200 else if ( (strImPosiPat = GetEntryString(0x0020,0x0030)) != GDCM_UNFOUND )
1202 if ( sscanf( strImPosiPat.c_str(), "%f \\ %f \\%f ",
1203 &ipp[0], &ipp[1], &ipp[2]) != 3 )
1205 gdcmWarningMacro( "wrong Image Position Patient (0020,0030). "
1206 << "Less than 3 values were found." );
1215 * \brief Retrieve the number of Bits Stored (actually used)
1216 * (as opposed to number of Bits Allocated)
1217 * @return The encountered number of Bits Stored, 0 by default.
1218 * 0 means the file is NOT USABLE. The caller has to check it !
1220 int File::GetBitsStored()
1222 DataEntry *entry = GetDataEntry(0x0028,0x0101);
1225 gdcmWarningMacro("BitsStored (0028,0101) is supposed to be mandatory");
1228 return (int)entry->GetValue(0);
1232 * \brief Retrieve the number of Bits Allocated
1233 * (8, 12 -compacted ACR-NEMA files-, 16, 24 -old RGB ACR-NEMA files-,)
1234 * @return The encountered Number of Bits Allocated, 0 by default.
1235 * 0 means the file is NOT USABLE. The caller has to check it !
1237 int File::GetBitsAllocated()
1239 DataEntry *entry = GetDataEntry(0x0028,0x0100);
1242 gdcmWarningMacro("BitsAllocated (0028,0100) is supposed to be mandatory");
1245 return (int)entry->GetValue(0);
1249 * \brief Retrieve the high bit position.
1250 * \warning The method defaults to 0 when information is missing.
1251 * The responsability of checking this value is left to the caller.
1252 * @return The high bit position when present. 0 when missing.
1254 int File::GetHighBitPosition()
1256 DataEntry *entry = GetDataEntry(0x0028,0x0102);
1259 gdcmWarningMacro("HighBitPosition (0028,0102) is supposed to be mandatory");
1262 return (int)entry->GetValue(0);
1266 * \brief Retrieve the number of Samples Per Pixel
1267 * (1 : gray level, 3 : RGB/YBR -1 or 3 Planes-)
1268 * @return The encountered number of Samples Per Pixel, 1 by default.
1269 * (we assume Gray level Pixels)
1271 int File::GetSamplesPerPixel()
1273 DataEntry *entry = GetDataEntry(0x0028,0x0002);
1276 gdcmWarningMacro("SamplesPerPixel (0028,0002) is supposed to be mandatory");
1277 return 1; // Well, it's supposed to be mandatory ...
1278 // but sometimes it's missing : *we* assume Gray pixels
1280 return (int)entry->GetValue(0);
1284 * \brief Retrieve the Planar Configuration for RGB images
1285 * (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
1286 * @return The encountered Planar Configuration, 0 by default.
1288 int File::GetPlanarConfiguration()
1290 DataEntry *entry = GetDataEntry(0x0028,0x0006);
1295 return (int)entry->GetValue(0);
1299 * \brief Return the size (in bytes) of a single pixel of data.
1300 * @return The size in bytes of a single pixel of data; 0 by default
1301 * 0 means the file is NOT USABLE; the caller will have to check
1303 int File::GetPixelSize()
1305 // 0028 0100 US IMG Bits Allocated
1306 // (in order no to be messed up by old ACR-NEMA RGB images)
1307 assert( !(GetEntryString(0x0028,0x0100) == "24") );
1309 std::string pixelType = GetPixelType();
1310 if ( pixelType == "8U" || pixelType == "8S" )
1314 if ( pixelType == "16U" || pixelType == "16S")
1318 if ( pixelType == "32U" || pixelType == "32S")
1322 if ( pixelType == "FD" )
1326 gdcmWarningMacro( "Unknown pixel type: " << pixelType);
1331 * \brief Build the Pixel Type of the image.
1332 * Possible values are:
1333 * - 8U unsigned 8 bit,
1334 * - 8S signed 8 bit,
1335 * - 16U unsigned 16 bit,
1336 * - 16S signed 16 bit,
1337 * - 32U unsigned 32 bit,
1338 * - 32S signed 32 bit,
1339 * - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
1340 * \warning 12 bit images appear as 16 bit.
1341 * 24 bit images appear as 8 bit + photochromatic interp ="RGB "
1342 * + Planar Configuration = 0
1343 * @return 0S if nothing found. NOT USABLE file. The caller has to check
1345 std::string File::GetPixelType()
1347 std::string bitsAlloc = GetEntryString(0x0028, 0x0100); // Bits Allocated
1348 if ( bitsAlloc == GDCM_UNFOUND )
1350 gdcmWarningMacro( "Bits Allocated (0028,0100) supposed to be mandatory");
1351 bitsAlloc = "16"; // default and arbitrary value, not to polute the output
1354 else if ( bitsAlloc == "64" )
1358 // useless since we have to bypass a bug ( >8 && < 16)
1359 else if ( bitsAlloc == "12" )
1361 // It will be unpacked
1365 else if ( bitsAlloc == "24" )
1367 // (in order no to be messed up by old RGB images)
1371 int i= atoi(bitsAlloc.c_str()); // fix a bug in some headers
1372 if ( i > 8 && i < 16 )
1378 if( IsSignedPixelData() )
1386 return bitsAlloc + sign;
1390 * \brief Check whether the pixels are signed (1) or UNsigned (0) data.
1391 * \warning The method defaults to false (UNsigned) when tag 0028|0103
1393 * The responsability of checking this value is left to the caller
1394 * (NO transformation is performed on the pixels to make then >0)
1395 * @return True when signed, false when UNsigned
1397 bool File::IsSignedPixelData()
1399 DataEntry *entry = GetDataEntry(0x0028, 0x0103);//"Pixel Representation"
1402 gdcmWarningMacro( "Pixel Representation (0028,0103) supposed to be "
1406 return entry->GetValue(0) != 0;
1410 * \brief Check whether this a monochrome picture (gray levels) or not,
1411 * using "Photometric Interpretation" tag (0x0028,0x0004).
1412 * @return true when "MONOCHROME1" or "MONOCHROME2". False otherwise.
1414 bool File::IsMonochrome()
1416 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1417 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1")
1418 || Util::DicomStringEqual(PhotometricInterp, "MONOCHROME2") )
1422 if ( PhotometricInterp == GDCM_UNFOUND )
1424 gdcmWarningMacro( "Photometric Interpretation (0028,0004) supposed to be "
1426 // to deal with old ACR-NEMA images
1427 if (GetNumberOfScalarComponents() == 1)
1434 * \brief Check whether this a MONOCHROME1 picture (high values = dark)
1435 * or not using "Photometric Interpretation" tag (0x0028,0x0004).
1436 * @return true when "MONOCHROME1" . False otherwise.
1438 bool File::IsMonochrome1()
1440 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1441 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1") )
1445 if ( PhotometricInterp == GDCM_UNFOUND )
1447 gdcmWarningMacro( "Photometric Interpretation (0028,0004) : supposed to"
1448 << " be mandatory! ");
1454 * \brief Check whether this a "PALETTE COLOR" picture or not by accessing
1455 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1456 * @return true when "PALETTE COLOR". False otherwise.
1458 bool File::IsPaletteColor()
1460 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1461 if ( PhotometricInterp == "PALETTE COLOR " )
1465 if ( PhotometricInterp == GDCM_UNFOUND )
1467 gdcmDebugMacro( "Not found : Palette color (0028,0004)");
1473 * \brief Check whether this a "YBR_FULL" color picture or not by accessing
1474 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1475 * @return true when "YBR_FULL". False otherwise.
1477 bool File::IsYBRFull()
1479 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1480 if ( PhotometricInterp == "YBR_FULL" )
1484 if ( PhotometricInterp == GDCM_UNFOUND )
1486 gdcmDebugMacro( "Not found : YBR Full (0028,0004)");
1492 * \brief tells us if LUT are used
1493 * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
1494 * are NOT considered as LUT, since nobody knows
1495 * how to deal with them
1496 * Please warn me if you know sbdy that *does* know ... jprx
1497 * @return true if LUT Descriptors and LUT Tables were found
1501 // Check the presence of the LUT Descriptors, and LUT Tables
1503 if ( !GetDocEntry(0x0028,0x1101) )
1507 // LutDescriptorGreen
1508 if ( !GetDocEntry(0x0028,0x1102) )
1512 // LutDescriptorBlue
1513 if ( !GetDocEntry(0x0028,0x1103) )
1517 // Red Palette Color Lookup Table Data
1518 if ( !GetDocEntry(0x0028,0x1201) )
1522 // Green Palette Color Lookup Table Data
1523 if ( !GetDocEntry(0x0028,0x1202) )
1527 // Blue Palette Color Lookup Table Data
1528 if ( !GetDocEntry(0x0028,0x1203) )
1533 // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
1534 // NOT taken into account, but we don't know how to use it ...
1539 * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
1541 * @return Lookup Table number of Bits , 0 by default
1542 * when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
1543 * @ return bit number of each LUT item
1545 int File::GetLUTNbits()
1547 std::vector<std::string> tokens;
1550 //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red
1551 // = Lookup Table Desc-Blue
1552 // Consistency already checked in GetLUTLength
1553 std::string lutDescription = GetEntryString(0x0028,0x1101);
1554 if ( lutDescription == GDCM_UNFOUND )
1559 tokens.clear(); // clean any previous value
1560 Util::Tokenize ( lutDescription, tokens, "\\" );
1561 //LutLength=atoi(tokens[0].c_str());
1562 //LutDepth=atoi(tokens[1].c_str());
1564 lutNbits = atoi( tokens[2].c_str() );
1571 // ts["1.2.840.10008.5.1.4.1.1.4.1"] = "Enhanced MR Image Storage";
1572 bool File::GetRescaleSlopeIntercept(double &slope, double &intercept)
1576 TS *ts = Global::GetTS();
1577 std::string sopclassuid_used;
1578 // D 0002|0002 [UI] [Media Storage SOP Class UID]
1579 const std::string &mediastoragesopclassuid_str = GetEntryString(0x0002,0x0002);
1580 const std::string &mediastoragesopclassuid = ts->GetValue(mediastoragesopclassuid_str);
1581 //D 0008|0016 [UI] [SOP Class UID]
1582 const std::string &sopclassuid_str = GetEntryString(0x0008,0x0016);
1583 const std::string &sopclassuid = ts->GetValue(sopclassuid_str);
1584 if ( mediastoragesopclassuid == GDCM_UNFOUND && sopclassuid == GDCM_UNFOUND )
1590 if( mediastoragesopclassuid == sopclassuid )
1592 sopclassuid_used = mediastoragesopclassuid;
1596 gdcmWarningMacro( "Inconsistant SOP Class UID: "
1597 << mediastoragesopclassuid << " and " << sopclassuid );
1601 // ok we have now the correc SOP Class UID
1602 if( sopclassuid_used == "Enhanced MR Image Storage" )
1604 SeqEntry *PerframeFunctionalGroupsSequence = GetSeqEntry(0x5200,0x9230);
1605 unsigned int n = PerframeFunctionalGroupsSequence->GetNumberOfSQItems();
1606 if( !n ) return false;
1607 SQItem *item1 = PerframeFunctionalGroupsSequence->GetFirstSQItem();
1608 DocEntry *p = item1->GetDocEntry(0x0028,0x9145);
1609 if( !p ) return false;
1610 SeqEntry *seq = dynamic_cast<SeqEntry*>(p);
1611 unsigned int n1 = seq->GetNumberOfSQItems();
1612 if( !n1 ) return false;
1613 SQItem *item2 = seq->GetFirstSQItem();
1614 // D 0028|1052 [DS] [Rescale Intercept] [0 ]
1615 DocEntry *p2 = item2->GetDocEntry(0x0028,0x1052);
1616 if( !p2 ) return false;
1617 DataEntry *entry = dynamic_cast<DataEntry *>(p2);
1618 std::string intercept_str = entry->GetString();
1619 if ( sscanf( intercept_str.c_str(), "%lf", &intercept) != 1 )
1624 // D 0028|1053 [DS] [Rescale Slope] [5.65470085470085]
1625 DocEntry *p3 = item2->GetDocEntry(0x0028,0x1053);
1626 if( !p3 ) return false;
1627 DataEntry *entry2 = dynamic_cast<DataEntry *>(p3);
1628 std::string slope_str = entry2->GetString();
1629 if ( sscanf( slope_str.c_str(), "%lf", &slope) != 1 )
1641 *\brief gets the info from 0028,1052 : Rescale Intercept
1642 * @return Rescale Intercept. defaulted to 0.0 is not found or empty
1644 double File::GetRescaleIntercept()
1646 // 0028 1052 DS IMG Rescale Intercept
1647 DataEntry *entry = GetDataEntry(0x0028, 0x1052);
1650 gdcmWarningMacro( "Missing Rescale Intercept (0028,1052)");
1653 return (float)entry->GetValue(0);
1658 *\brief gets the info from 0028,1053 : Rescale Slope
1659 * @return Rescale Slope. defaulted to 1.0 is not found or empty
1661 double File::GetRescaleSlope()
1663 double resInter = 0.;
1664 double resSlope = 1.;
1665 if ( GetRescaleSlopeIntercept(resSlope, resInter) )
1669 //0028 1053 DS IMG Rescale Slope
1670 std::string strRescSlope = GetEntryString(0x0028,0x1053);
1671 if ( strRescSlope != GDCM_UNFOUND )
1673 if ( sscanf( strRescSlope.c_str(), "%lf ", &resSlope) != 1 )
1675 // bug in the element 0x0028,0x1053
1676 gdcmWarningMacro( "Rescale Slope (0028,1053) is empty.");
1684 * \brief This function is intended to user who doesn't want
1685 * to have to manage a LUT and expects to get an RBG Pixel image
1686 * (or a monochrome one, if no LUT found ...)
1687 * \warning to be used with GetImagePixels()
1688 * @return 1 if Gray level, 3 if Color (RGB, YBR, *or PALETTE COLOR*)
1690 int File::GetNumberOfScalarComponents()
1692 if ( GetSamplesPerPixel() == 3 )
1697 // 0028 0100 US IMG Bits Allocated
1698 // (in order no to be messed up by old RGB images)
1699 if ( GetEntryString(0x0028,0x0100) == "24" )
1704 std::string strPhotometricInterpretation = GetEntryString(0x0028,0x0004);
1706 if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
1708 if ( HasLUT() )// PALETTE COLOR is NOT enough
1718 // beware of trailing space at end of string
1719 // DICOM tags are never of odd length
1720 if ( strPhotometricInterpretation == GDCM_UNFOUND ||
1721 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
1722 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
1728 // we assume that *all* kinds of YBR are dealt with
1734 * \brief This function is intended to user that DOESN'T want
1735 * to get RGB pixels image when it's stored as a PALETTE COLOR image
1736 * - the (vtk) user is supposed to know how deal with LUTs -
1737 * \warning to be used with GetImagePixelsRaw()
1738 * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
1740 int File::GetNumberOfScalarComponentsRaw()
1742 // 0028 0100 US IMG Bits Allocated
1743 // (in order no to be messed up by old RGB images)
1744 if ( File::GetEntryString(0x0028,0x0100) == "24" )
1749 // we assume that *all* kinds of YBR are dealt with
1750 return GetSamplesPerPixel();
1754 * \brief Recover the offset (from the beginning of the file)
1755 * of *image* pixels (not *icone image* pixels, if any !)
1756 * @return Pixel Offset
1758 size_t File::GetPixelOffset()
1760 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1763 return pxlElement->GetOffset();
1767 gdcmWarningMacro( "Big trouble : Pixel Element ("
1768 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1774 * \brief Recover the pixel area length (in Bytes)
1775 * @return Pixel Element Length, as stored in the header
1776 * (NOT the memory space necessary to hold the Pixels
1777 * -in case of embeded compressed image-)
1778 * 0 : NOT USABLE file. The caller has to check.
1780 size_t File::GetPixelAreaLength()
1782 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1785 return pxlElement->GetLength();
1789 gdcmWarningMacro( "Big trouble : Pixel Element ("
1790 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1796 * \brief Adds the characteristics of a new element we want to anonymize
1797 * @param group Group number of the target tag.
1798 * @param elem Element number of the target tag.
1799 * @param value new value (string) to substitute with
1801 void File::AddAnonymizeElement (uint16_t group, uint16_t elem,
1802 std::string const &value)
1808 UserAnonymizeList.push_back(el);
1812 * \brief Overwrites in the file the values of the DicomElements
1815 void File::AnonymizeNoLoad()
1817 std::fstream *fp = new std::fstream(Filename.c_str(),
1818 std::ios::in | std::ios::out | std::ios::binary);
1819 GDCM_NAME_SPACE::DocEntry *d;
1822 uint32_t valLgth = 0;
1823 std::string *spaces;
1824 for (ListElements::iterator it = UserAnonymizeList.begin();
1825 it != UserAnonymizeList.end();
1829 //std::cout << "File::AnonymizeNoLoad -------" << std::hex <<(*it).Group <<"|"<<
1831 // << "[" << (*it).Value << "] "<< std::dec << std::endl;
1832 d = GetDocEntry( (*it).Group, (*it).Elem);
1837 if ( dynamic_cast<SeqEntry *>(d) )
1839 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1843 valLgth = (*it).Value.size();
1847 offset = d->GetOffset();
1848 lgth = d->GetLength();
1850 //std::cout << "lgth " << lgth << " valLgth " << valLgth << std::endl;
1853 spaces = new std::string( lgth-valLgth, ' ');
1854 (*it).Value = (*it).Value + *spaces;
1855 //std::cout << "[" << (*it).Value << "] " << lgth << std::endl;
1858 fp->seekp( offset, std::ios::beg );
1859 fp->write( (*it).Value.c_str(), lgth );
1867 * \brief anonymize a File (remove Patient's personal info passed with
1868 * AddAnonymizeElement()
1869 * \note You cannot Anonymize a DataEntry (to be fixed)
1871 bool File::AnonymizeFile()
1873 // If Anonymisation list is empty, let's perform some basic anonymization
1874 if ( UserAnonymizeList.begin() == UserAnonymizeList.end() )
1876 // If exist, replace by spaces
1877 SetEntryString(" ",0x0010, 0x2154); // Telephone
1878 SetEntryString(" ",0x0010, 0x1040); // Adress
1879 SetEntryString(" ",0x0010, 0x0020); // Patient ID
1881 DocEntry *patientNameHE = GetDocEntry (0x0010, 0x0010);
1883 if ( patientNameHE ) // we replace it by Study Instance UID (why not ?)
1885 std::string studyInstanceUID = GetEntryString (0x0020, 0x000d);
1886 if ( studyInstanceUID != GDCM_UNFOUND )
1888 SetEntryString(studyInstanceUID, 0x0010, 0x0010);
1892 SetEntryString("anonymized", 0x0010, 0x0010);
1898 GDCM_NAME_SPACE::DocEntry *d;
1899 for (ListElements::iterator it = UserAnonymizeList.begin();
1900 it != UserAnonymizeList.end();
1903 d = GetDocEntry( (*it).Group, (*it).Elem);
1908 if ( dynamic_cast<SeqEntry *>(d) )
1910 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1914 if ( dynamic_cast<DataEntry *>(d) )
1916 gdcmWarningMacro( "To 'Anonymize' a DataEntry, better use AnonymizeNoLoad (FIXME) ");
1920 SetEntryString ((*it).Value, (*it).Group, (*it).Elem);
1924 // In order to make definitively impossible any further identification
1925 // remove or replace all the stuff that contains a Date
1927 //0008 0012 DA ID Instance Creation Date
1928 //0008 0020 DA ID Study Date
1929 //0008 0021 DA ID Series Date
1930 //0008 0022 DA ID Acquisition Date
1931 //0008 0023 DA ID Content Date
1932 //0008 0024 DA ID Overlay Date
1933 //0008 0025 DA ID Curve Date
1934 //0008 002a DT ID Acquisition Datetime
1935 //0018 9074 DT ACQ Frame Acquisition Datetime
1936 //0018 9151 DT ACQ Frame Reference Datetime
1937 //0018 a002 DT ACQ Contribution Date Time
1938 //0020 3403 SH REL Modified Image Date (RET)
1939 //0032 0032 DA SDY Study Verified Date
1940 //0032 0034 DA SDY Study Read Date
1941 //0032 1000 DA SDY Scheduled Study Start Date
1942 //0032 1010 DA SDY Scheduled Study Stop Date
1943 //0032 1040 DA SDY Study Arrival Date
1944 //0032 1050 DA SDY Study Completion Date
1945 //0038 001a DA VIS Scheduled Admission Date
1946 //0038 001c DA VIS Scheduled Discharge Date
1947 //0038 0020 DA VIS Admitting Date
1948 //0038 0030 DA VIS Discharge Date
1949 //0040 0002 DA PRC Scheduled Procedure Step Start Date
1950 //0040 0004 DA PRC Scheduled Procedure Step End Date
1951 //0040 0244 DA PRC Performed Procedure Step Start Date
1952 //0040 0250 DA PRC Performed Procedure Step End Date
1953 //0040 2004 DA PRC Issue Date of Imaging Service Request
1954 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
1955 //0040 4011 DT PRC Expected Completion Date and Time
1956 //0040 a030 DT PRC Verification Date Time
1957 //0040 a032 DT PRC Observation Date Time
1958 //0040 a120 DT PRC DateTime
1959 //0040 a121 DA PRC Date
1960 //0040 a13a DT PRC Referenced Datetime
1961 //0070 0082 DA ??? Presentation Creation Date
1962 //0100 0420 DT ??? SOP Autorization Date and Time
1963 //0400 0105 DT ??? Digital Signature DateTime
1964 //2100 0040 DA PJ Creation Date
1965 //3006 0008 DA SSET Structure Set Date
1966 //3008 0024 DA ??? Treatment Control Point Date
1967 //3008 0054 DA ??? First Treatment Date
1968 //3008 0056 DA ??? Most Recent Treatment Date
1969 //3008 0162 DA ??? Safe Position Exit Date
1970 //3008 0166 DA ??? Safe Position Return Date
1971 //3008 0250 DA ??? Treatment Date
1972 //300a 0006 DA RT RT Plan Date
1973 //300a 022c DA RT Air Kerma Rate Reference Date
1974 //300e 0004 DA RT Review Date
1980 * \brief Performs some consistency checking on various 'File related'
1981 * (as opposed to 'DicomDir related') entries
1982 * then writes in a file all the (Dicom Elements) included the Pixels
1983 * @param fileName file name to write to
1984 * @param writetype type of the file to be written
1985 * (ACR, ExplicitVR, ImplicitVR)
1987 bool File::Write(std::string fileName, FileType writetype)
1989 std::ofstream *fp = new std::ofstream(fileName.c_str(),
1990 std::ios::out | std::ios::binary);
1993 gdcmWarningMacro("Failed to open (write) File: " << fileName.c_str());
1997 // Entry : 0002|0000 = group length -> recalculated
1998 DataEntry *e0000 = GetDataEntry(0x0002,0x0000);
2001 std::ostringstream sLen;
2002 sLen << ComputeGroup0002Length( );
2003 e0000->SetString(sLen.str());
2006 /// \todo FIXME : Derma?.dcm does not have it...let's remove it ?!? JPRx
2007 if( writetype != JPEG )
2009 int i_lgPix = GetEntryLength(GrPixel, NumPixel);
2012 // no (GrPixel, NumPixel) element
2013 std::string s_lgPix = Util::Format("%d", i_lgPix+12);
2014 s_lgPix = Util::DicomString( s_lgPix.c_str() );
2015 InsertEntryString(s_lgPix,GrPixel, 0x0000, "UL");
2018 Document::WriteContent(fp, writetype);
2026 //-----------------------------------------------------------------------------
2030 //-----------------------------------------------------------------------------
2033 * \brief Parse pixel data from disk of [multi-]fragment RLE encoding.
2034 * Compute the RLE extra information and store it in \ref RLEInfo
2035 * for later pixel retrieval usage.
2037 void File::ComputeRLEInfo()
2039 std::string ts = GetTransferSyntax();
2040 if ( !Global::GetTS()->IsRLELossless(ts) )
2045 // Encoded pixel data: for the time being we are only concerned with
2046 // Jpeg or RLE Pixel data encodings.
2047 // As stated in PS 3.5-2003, section 8.2 p44:
2048 // "If sent in Encapsulated Format (i.e. other than the Native Format) the
2049 // value representation OB is used".
2050 // Hence we expect an OB value representation. Concerning OB VR,
2051 // the section PS 3.5-2003, section A.4.c p 58-59, states:
2052 // "For the Value Representations OB and OW, the encoding shall meet the
2053 // following specifications depending on the Data element tag:"
2055 // - the first item in the sequence of items before the encoded pixel
2056 // data stream shall be basic offset table item. The basic offset table
2057 // item value, however, is not required to be present"
2058 ReadEncapsulatedBasicOffsetTable();
2060 // Encapsulated RLE Compressed Images (see PS 3.5-2003, Annex G)
2061 // Loop on the individual frame[s] and store the information
2062 // on the RLE fragments in a RLEFramesInfo.
2063 // Note: - when only a single frame is present, this is a
2065 // - when more than one frame are present, then we are in
2066 // the case of a multi-frame image.
2070 while ( (frameLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
2072 // Since we have read the basic offset table, let's check the value were correct
2073 // or else produce a warning:
2074 if ( BasicOffsetTableItemValue )
2076 // If a BasicOffsetTableItemValue was read
2077 uint32_t individualLength = BasicOffsetTableItemValue[i];
2078 assert( individualLength == sum ); // REMOVE that if this is a problem
2079 if( individualLength != sum )
2081 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght" );
2083 sum += frameLength + 8;
2086 // Parse the RLE Header and store the corresponding RLE Segment
2087 // Offset Table information on fragments of this current Frame.
2088 // Note that the fragment pixels themselves are not loaded
2089 // (but just skipped).
2090 long frameOffset = Fp->tellg(); // once per fragment
2092 uint32_t nbRleSegments = ReadInt32();
2093 if ( nbRleSegments > 16 )
2095 // There should be at most 15 segments (refer to RLEFrame class)
2096 gdcmWarningMacro( "Too many segments.");
2099 uint32_t rleSegmentOffsetTable[16];
2100 for( int k = 1; k <= 15; k++ )
2102 rleSegmentOffsetTable[k] = ReadInt32();
2105 // Deduce from both RLE Header and frameLength
2106 // the fragment length, and again store this info
2107 // in a RLEFramesInfo.
2108 long rleSegmentLength[15];
2109 // skipping (not reading) RLE Segments
2110 if ( nbRleSegments > 1)
2112 for(unsigned int k = 1; k <= nbRleSegments-1; k++)
2114 rleSegmentLength[k] = rleSegmentOffsetTable[k+1]
2115 - rleSegmentOffsetTable[k];
2116 SkipBytes(rleSegmentLength[k]);
2120 rleSegmentLength[nbRleSegments] = frameLength
2121 - rleSegmentOffsetTable[nbRleSegments];
2122 SkipBytes(rleSegmentLength[nbRleSegments]);
2124 // Store the collected info
2125 RLEFrame *newFrame = new RLEFrame;
2126 newFrame->SetNumberOfFragments(nbRleSegments);
2127 for( unsigned int uk = 1; uk <= nbRleSegments; uk++ )
2129 newFrame->SetOffset(uk,frameOffset + rleSegmentOffsetTable[uk]);
2130 newFrame->SetLength(uk,rleSegmentLength[uk]);
2132 RLEInfo->AddFrame(newFrame);
2135 // Make sure that we encounter a 'Sequence Delimiter Item'
2136 // at the end of the item :
2137 if ( !ReadTag(0xfffe, 0xe0dd) ) // once per RLE File
2139 gdcmWarningMacro( "No sequence delimiter item at end of RLE item sequence");
2144 * \brief Parse pixel data from disk of [multi-]fragment Jpeg encoding.
2145 * Compute the jpeg extra information (fragment[s] offset[s] and
2146 * length) and store it[them] in \ref JPEGInfo for later pixel
2149 void File::ComputeJPEGFragmentInfo()
2151 // If you need to, look for comments of ComputeRLEInfo().
2152 std::string ts = GetTransferSyntax();
2153 if ( ! Global::GetTS()->IsJPEG(ts) )
2158 ReadEncapsulatedBasicOffsetTable();
2160 // Loop on the fragments[s] and store the parsed information in a
2162 long fragmentLength;
2165 while ( (fragmentLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
2167 // Since we have read the basic offset table, let's check the value were correct
2168 // or else produce a warning:
2169 // A.4 Transfer syntaxes for encapsulation of encoded pixel data:
2170 // When the Item Value is present, the Basic Offset Table Item Value shall contain
2171 // concatenated 32-bit unsigned integer values that are byte offsets to the first
2172 // byte of the Item Tag of the first fragment for each frame in the Sequence of
2173 // Items. These offsets are measured from the first byte of the first Item Tag
2174 // following the Basic Offset Table item (See Table A.4-2).
2176 if ( BasicOffsetTableItemValue )
2178 // If a BasicOffsetTableItemValue was read
2179 uint32_t individualLength = BasicOffsetTableItemValue[i];
2180 //assert( individualLength == sum ); // Seems like 00191113.dcm is off by one ??
2181 if( individualLength != sum )
2183 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght:" <<
2184 individualLength << " != " << sum );
2186 sum += fragmentLength + 8;
2190 long fragmentOffset = Fp->tellg(); // Once per fragment
2191 // Store the collected info
2192 JPEGFragment *newFragment = new JPEGFragment;
2193 newFragment->SetOffset(fragmentOffset);
2194 newFragment->SetLength(fragmentLength);
2195 JPEGInfo->AddFragment(newFragment);
2197 SkipBytes(fragmentLength);
2200 // Make sure that we encounter a 'Sequence Delimiter Item'
2201 // at the end of the item :
2202 if ( !ReadTag(0xfffe, 0xe0dd) )
2204 gdcmWarningMacro( "No sequence delimiter item at end of JPEG item sequence");
2209 * \brief Assuming the internal file pointer \ref Document::Fp
2210 * is placed at the beginning of a tag, check whether this
2211 * tag is (TestGroup, TestElem).
2212 * \warning On success the internal file pointer \ref Document::Fp
2213 * is modified to point after the tag.
2214 * On failure (i.e. when the tag wasn't the expected tag
2215 * (TestGroup, TestElem) the internal file pointer
2216 * \ref Document::Fp is restored to its original position.
2217 * @param testGroup The expected group of the tag.
2218 * @param testElem The expected Element of the tag.
2219 * @return True on success, false otherwise.
2221 bool File::ReadTag(uint16_t testGroup, uint16_t testElem)
2223 long positionOnEntry = Fp->tellg(); // Only when reading fragments
2224 //long currentPosition = positionOnEntry; // On debugging purposes
2226 // Read the Item Tag group and element, and make
2227 // sure they are what we expected:
2228 uint16_t itemTagGroup;
2229 uint16_t itemTagElem;
2232 itemTagGroup = ReadInt16();
2233 itemTagElem = ReadInt16();
2235 catch ( FormatError )
2237 gdcmErrorMacro( "Can not read tag for "
2238 << " We should have found tag ("
2239 << DictEntry::TranslateToKey(testGroup,testElem) << ")"
2244 if ( itemTagGroup != testGroup || itemTagElem != testElem )
2246 // in order not to pollute output we don't warn on 'delimitors'
2247 if (itemTagGroup != 0xfffe || testGroup != 0xfffe )
2248 gdcmWarningMacro( "Wrong Item Tag found:"
2249 << " We should have found tag ("
2250 << DictEntry::TranslateToKey(testGroup,testElem) << ")" << std::endl
2251 << " but instead we encountered tag ("
2252 << DictEntry::TranslateToKey(itemTagGroup,itemTagElem) << ")"
2253 << " at address: " << " 0x(" << std::hex
2254 << (unsigned int)positionOnEntry << std::dec << ")"
2256 Fp->seekg(positionOnEntry, std::ios::beg);
2264 * \brief Assuming the internal file pointer \ref Document::Fp
2265 * is placed at the beginning of a tag (TestGroup, TestElement),
2266 * read the length associated to the Tag.
2267 * \warning On success the internal file pointer \ref Document::Fp
2268 * is modified to point after the tag and its length.
2269 * On failure (i.e. when the tag wasn't the expected tag
2270 * (TestGroup, TestElement) the internal file pointer
2271 * \ref Document::Fp is restored to its original position.
2272 * @param testGroup The expected Group of the tag.
2273 * @param testElem The expected Element of the tag.
2274 * @return On success returns the length associated to the tag. On failure
2277 uint32_t File::ReadTagLength(uint16_t testGroup, uint16_t testElem)
2280 if ( !ReadTag(testGroup, testElem) )
2282 // Avoid polutting output
2283 if ( testGroup != 0xfffe )
2284 gdcmErrorMacro( "ReadTag did not succeed for ("
2285 << DictEntry::TranslateToKey(testGroup,testElem)
2290 //// Then read the associated Item Length
2292 // long currentPosition = Fp->tellg(); // save time // JPRx
2293 uint32_t itemLength = ReadInt32();
2294 gdcmDebugMacro( "Basic Item Length is: " << itemLength
2295 // << " at address: " << std::hex << (unsigned int)currentPosition
2301 * \brief When parsing the Pixel Data of an encapsulated file, read
2302 * the basic offset table (when present, and BTW dump it).
2304 void File::ReadEncapsulatedBasicOffsetTable()
2306 //// Read the Basic Offset Table Item Tag length...
2307 uint32_t itemLength = ReadTagLength(0xfffe, 0xe000);
2309 // When present, read the basic offset table itself.
2310 // Notes: - since the presence of this basic offset table is optional
2311 // we can't rely on it for the implementation, and we will simply
2312 // trash its content (when present).
2313 // - still, when present, we could add some further checks on the
2314 // lengths, but we won't bother with such fuses for the time being.
2315 if ( itemLength != 0 )
2317 char *charBasicOffsetTableItemValue = new char[itemLength];
2318 Fp->read(charBasicOffsetTableItemValue, itemLength);
2319 unsigned int nbEntries = itemLength/4;
2320 assert( nbEntries*4 == itemLength); // Make sure this is a multiple
2321 BasicOffsetTableItemValue = new uint32_t[nbEntries];
2323 for (unsigned int i=0; i < nbEntries; i++ )
2325 BasicOffsetTableItemValue[i] = *((uint32_t*)(&charBasicOffsetTableItemValue[4*i]));
2326 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
2327 uint32_t val = BasicOffsetTableItemValue[i];
2328 BasicOffsetTableItemValue[i]
2329 = ( (val<<24) | ((val<<8) & 0x00ff0000) |
2330 ( (val>>8) & 0x0000ff00) | (val>>24) );
2332 gdcmDebugMacro( "Read one length for: " <<
2333 std::hex << BasicOffsetTableItemValue[i] );
2336 delete[] charBasicOffsetTableItemValue;
2340 // These are the deprecated method that one day should be removed (after the next release)
2342 //#ifndef GDCM_LEGACY_REMOVE
2344 * \ brief Loader. (DEPRECATED : temporaryly kept not to break the API)
2345 * @ param fileName file to be open for parsing
2346 * @ return false if file cannot be open or no swap info was found,
2347 * or no tag was found.
2348 * @deprecated Use the Load() [ + SetLoadMode() ] + SetFileName() functions instead
2351 bool File::Load( std::string const &fileName )
2353 GDCM_LEGACY_REPLACED_BODY(File::Load(std::string), "1.2",
2355 SetFileName( fileName );
2356 if ( ! this->Document::Load( ) )
2359 return DoTheLoadingJob( );
2363 //-----------------------------------------------------------------------------
2366 //-----------------------------------------------------------------------------
2367 } // end namespace gdcm