1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2009/05/19 15:08:36 $
7 Version: $Revision: 1.344 $
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.
147 if ( ! this->Document::Load( ) )
150 return DoTheLoadingJob( );
154 * \brief Does the Loading Job (internal use only)
155 * @return false if file cannot be open or no swap info was found,
156 * or no tag was found.
158 bool File::DoTheLoadingJob( )
160 // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
161 // We may encounter the 'RETired' (0x0028, 0x0200) tag
162 // (Image Location") . This entry contains the number of
163 // the group that contains the pixel data (hence the "Pixel Data"
164 // is found by indirection through the "Image Location").
165 // Inside the group pointed by "Image Location" the searched element
166 // is conventionally the element 0x0010 (when the norm is respected).
167 // When the "Image Location" is missing we default to group 0x7fe0.
168 // Note: this IS the right place for the code
171 const std::string &imgLocation = GetEntryString(0x0028, 0x0200);
172 if ( imgLocation == GDCM_UNFOUND )
179 GrPixel = (uint16_t) atoi( imgLocation.c_str() );
182 // sometimes Image Location value doesn't follow
183 // the supposed processor endianness.
184 // see gdcmData/cr172241.dcm
185 if ( GrPixel == 0xe07f )
190 if ( GrPixel != 0x7fe0 )
192 // This is a kludge for old dirty Philips imager.
200 // Now, we know GrPixel and NumPixel.
201 // Let's create a VirtualDictEntry to allow a further VR modification
202 // and force VR to match with BitsAllocated.
203 DocEntry *entry = GetDocEntry(GrPixel, NumPixel);
206 // Compute the RLE or JPEG info
208 const std::string &ts = GetTransferSyntax();
209 Fp->seekg( entry->GetOffset(), std::ios::beg );
210 if ( Global::GetTS()->IsRLELossless(ts) )
212 else if ( Global::GetTS()->IsJPEG(ts) )
213 ComputeJPEGFragmentInfo();
216 // Create a new DataEntry to change the DictEntry
217 // The changed DictEntry will have
218 // - a correct PixelVR OB or OW)
219 // - the name to "Pixel Data"
222 //==> Just change the VR !
225 DataEntry *oldEntry = dynamic_cast<DataEntry *>(entry);
229 // 8 bits allocated is a 'O Bytes' , as well as 24 (old ACR-NEMA RGB)
230 // more than 8 (i.e 12, 16) is a 'O Words'
231 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
236 // Change only made if usefull
237 if ( PixelVR != oldEntry->GetVR() )
239 //DictEntry* newDict = DictEntry::New(GrPixel,NumPixel,
240 // PixelVR,"1","Pixel Data");
241 //DataEntry *newEntry = DataEntry::New(newDict);
243 //newEntry->Copy(entry);
244 //newEntry->SetBinArea(oldEntry->GetBinArea(),oldEntry->IsSelfArea());
245 //oldEntry->SetSelfArea(false);
247 //RemoveEntry(oldEntry);
248 //AddEntry(newEntry);
249 //newEntry->Delete();
254 // 8 bits allocated is a 'OB(ytes)' , as well as 24 (old ACR-NEMA RGB)
255 // more than 8 (i.e 12, 16) is a 'OW(ords)'
256 if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
260 // Change only made if usefull
261 if ( PixelVR != entry->GetVR() )
263 entry->SetVR(PixelVR);
270 * \brief This predicate, based on hopefully reasonable heuristics,
271 * decides whether or not the current File was properly parsed
272 * and contains the mandatory information for being considered as
273 * a well formed and usable Dicom/Acr File.
274 * @return true when File is the one of a reasonable Dicom/Acr file,
277 bool File::IsReadable()
279 if ( !Document::IsReadable() )
284 const std::string &res = GetEntryString(0x0028, 0x0005);
285 if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 )
287 gdcmWarningMacro("Wrong Image Dimensions" << res);
288 return false; // Image Dimensions
290 bool b0028_0100 = true;
291 if ( !GetDocEntry(0x0028, 0x0100) )
293 gdcmWarningMacro("Bits Allocated (0028|0100) not found");
294 //return false; // "Bits Allocated"
297 bool b0028_0101 = true;
298 if ( !GetDocEntry(0x0028, 0x0101) )
300 gdcmWarningMacro("Bits Stored (0028|0101) not found");
301 //return false; // "Bits Stored"
304 bool b0028_0102 = true;
305 if ( !GetDocEntry(0x0028, 0x0102) )
307 gdcmWarningMacro("Hight Bit (0028|0102) not found");
308 //return false; // "High Bit"
311 bool b0028_0103 = true;
312 if ( !GetDocEntry(0x0028, 0x0103) )
314 gdcmWarningMacro("Pixel Representation (0028|0103) not found");
315 //return false; // "Pixel Representation" i.e. 'Sign' ( 0 : unsigned, 1 : signed)
319 if ( !b0028_0100 && !b0028_0101 && !b0028_0102 && !b0028_0103)
321 gdcmWarningMacro("Too much mandatory Tags missing !");
325 if ( !GetDocEntry(GrPixel, NumPixel) )
327 gdcmWarningMacro("Pixel Dicom Element " << std::hex <<
328 GrPixel << "|" << NumPixel << "not found");
329 return false; // Pixel Dicom Element not found :-(
335 * \brief gets the info from 0020,0013 : Image Number else 0.
336 * @return image number
338 int File::GetImageNumber()
340 //0020 0013 : Image Number
341 std::string strImNumber = GetEntryString(0x0020,0x0013);
342 if ( strImNumber != GDCM_UNFOUND )
344 return atoi( strImNumber.c_str() );
350 * \brief gets the info from 0008,0060 : Modality
351 * @return Modality Type
353 ModalityType File::GetModality()
355 // 0008 0060 : Modality
356 std::string strModality = GetEntryString(0x0008,0x0060);
357 if ( strModality != GDCM_UNFOUND )
359 if ( strModality.find("AU") < strModality.length()) return AU;
360 else if ( strModality.find("AS") < strModality.length()) return AS;
361 else if ( strModality.find("BI") < strModality.length()) return BI;
362 else if ( strModality.find("CF") < strModality.length()) return CF;
363 else if ( strModality.find("CP") < strModality.length()) return CP;
364 else if ( strModality.find("CR") < strModality.length()) return CR;
365 else if ( strModality.find("CT") < strModality.length()) return CT;
366 else if ( strModality.find("CS") < strModality.length()) return CS;
367 else if ( strModality.find("DD") < strModality.length()) return DD;
368 else if ( strModality.find("DF") < strModality.length()) return DF;
369 else if ( strModality.find("DG") < strModality.length()) return DG;
370 else if ( strModality.find("DM") < strModality.length()) return DM;
371 else if ( strModality.find("DS") < strModality.length()) return DS;
372 else if ( strModality.find("DX") < strModality.length()) return DX;
373 else if ( strModality.find("ECG") < strModality.length()) return ECG;
374 else if ( strModality.find("EPS") < strModality.length()) return EPS;
375 else if ( strModality.find("FA") < strModality.length()) return FA;
376 else if ( strModality.find("FS") < strModality.length()) return FS;
377 else if ( strModality.find("HC") < strModality.length()) return HC;
378 else if ( strModality.find("HD") < strModality.length()) return HD;
379 else if ( strModality.find("LP") < strModality.length()) return LP;
380 else if ( strModality.find("LS") < strModality.length()) return LS;
381 else if ( strModality.find("MA") < strModality.length()) return MA;
382 else if ( strModality.find("MR") < strModality.length()) return MR;
383 else if ( strModality.find("NM") < strModality.length()) return NM;
384 else if ( strModality.find("OT") < strModality.length()) return OT;
385 else if ( strModality.find("PT") < strModality.length()) return PT;
386 else if ( strModality.find("RF") < strModality.length()) return RF;
387 else if ( strModality.find("RG") < strModality.length()) return RG;
388 else if ( strModality.find("RTDOSE")
389 < strModality.length()) return RTDOSE;
390 else if ( strModality.find("RTIMAGE")
391 < strModality.length()) return RTIMAGE;
392 else if ( strModality.find("RTPLAN")
393 < strModality.length()) return RTPLAN;
394 else if ( strModality.find("RTSTRUCT")
395 < strModality.length()) return RTSTRUCT;
396 else if ( strModality.find("SM") < strModality.length()) return SM;
397 else if ( strModality.find("ST") < strModality.length()) return ST;
398 else if ( strModality.find("TG") < strModality.length()) return TG;
399 else if ( strModality.find("US") < strModality.length()) return US;
400 else if ( strModality.find("VF") < strModality.length()) return VF;
401 else if ( strModality.find("XA") < strModality.length()) return XA;
402 else if ( strModality.find("XC") < strModality.length()) return XC;
406 /// \todo throw error return value ???
407 /// specified <> unknown in our database
415 * \brief Retrieve the number of columns of image.
416 * @return The encountered size when found, 0 by default.
417 * 0 means the file is NOT USABLE. The caller will have to check
421 DataEntry *entry = GetDataEntry(0x0028,0x0011);
423 return (int)entry->GetValue(0);
428 * \brief Retrieve the number of lines of image.
429 * \warning The defaulted value is 1 as opposed to File::GetXSize()
430 * @return The encountered size when found, 1 by default
431 * (The ACR-NEMA file contains a Signal, not an Image).
435 DataEntry *entry = GetDataEntry(0x0028,0x0010);
437 return (int)entry->GetValue(0);
444 // The Rows (0028,0010) entry was optional for ACR/NEMA.
445 // (at least some images didn't have it.)
446 // It might hence be a signal (1D image). So we default to 1:
451 * \brief Retrieve the number of planes of volume or the number
452 * of frames of a multiframe.
453 * \warning When present we consider the "Number of Frames" as the third
454 * dimension. When missing we consider the third dimension as
455 * being the ACR-NEMA "Planes" tag content.
456 * @return The encountered size when found, 1 by default (single image).
460 // Both DicomV3 and ACR/Nema consider the "Number of Frames"
461 // as the third dimension.
462 DataEntry *entry = GetDataEntry(0x0028,0x0008);
464 return (int)entry->GetValue(0);
466 // We then consider the "Planes" entry as the third dimension
467 entry = GetDataEntry(0x0028,0x0012);
469 return (int)entry->GetValue(0);
474 // ts["1.2.840.10008.5.1.4.1.1.4.1"] = "Enhanced MR Image Storage";
475 bool File::GetSpacing(float &xspacing, float &yspacing, float &zspacing)
477 xspacing = yspacing = zspacing = 1.0;
478 TS *ts = Global::GetTS();
479 std::string sopclassuid_used;
480 // D 0002|0002 [UI] [Media Storage SOP Class UID]
482 //const std::string &mediastoragesopclassuid_str = GetEntryValue(0x0002,0x0002);
483 const std::string &mediastoragesopclassuid_str = GetEntryString(0x0002,0x0002);
484 const std::string &mediastoragesopclassuid = ts->GetValue(mediastoragesopclassuid_str);
485 //D 0008|0016 [UI] [SOP Class UID]
486 const std::string &sopclassuid_str = GetEntryString(0x0008,0x0016);
487 const std::string &sopclassuid = ts->GetValue(sopclassuid_str);
488 if ( mediastoragesopclassuid == GDCM_UNFOUND && sopclassuid == GDCM_UNFOUND )
494 if ( mediastoragesopclassuid == sopclassuid )
496 sopclassuid_used = mediastoragesopclassuid;
500 gdcmWarningMacro( "Inconsistant SOP Class UID: "
501 << mediastoragesopclassuid << " and " << sopclassuid );
505 // ok we have now the correct SOP Class UID
506 if( sopclassuid_used == "Enhanced MR Image Storage" )
508 SeqEntry *PerframeFunctionalGroupsSequence = GetSeqEntry(0x5200,0x9230);
509 unsigned int n = PerframeFunctionalGroupsSequence->GetNumberOfSQItems();
510 if( !n ) return false;
511 SQItem *item1 = PerframeFunctionalGroupsSequence->GetFirstSQItem();
512 DocEntry *p = item1->GetDocEntry(0x0028,0x9110);
513 if( !p ) return false;
514 SeqEntry *seq = dynamic_cast<SeqEntry*>(p);
515 unsigned int n1 = seq->GetNumberOfSQItems();
516 if( !n1 ) return false;
517 SQItem *item2 = seq->GetFirstSQItem();
518 // D 0028|0030 [DS] [Pixel Spacing] [0.83333331346511\0.83333331346511 ]
519 DocEntry *p2 = item2->GetDocEntry(0x0028,0x0030);
520 if( !p2 ) return false;
521 DataEntry *entry = dynamic_cast<DataEntry *>(p2);
522 std::string spacing = entry->GetString();
523 if ( sscanf( spacing.c_str(), "%f\\%f", &yspacing, &xspacing) != 2 )
525 xspacing = yspacing = 1.;
528 // D 0018|0050 [DS] [Slice Thickness] [1 ]
529 DocEntry *p3 = item2->GetDocEntry(0x0018,0x0050);
530 if( !p3 ) return false;
531 DataEntry *entry2 = dynamic_cast<DataEntry *>(p3);
532 std::string thickness = entry2->GetString();
533 if ( sscanf( thickness.c_str(), "%f", &zspacing) != 1 )
544 * \brief Retrieve the -unnormalized- number of 'times' of '4D image'.
545 * User has to tell gdcm the location of this '4th Dimension component'
546 * using SetFourthDimensionLocation() method before.
547 * \warning The defaulted value is 1.
548 * @return The encountered size when found, 1 by default
549 * (The file doesn't contain a '4D image'.).
553 if (FourthDimensionLocation == TagKey(0,0) )// 4D location is not set : not a 4D object
556 DataEntry *entry = GetDataEntry(FourthDimensionLocation.GetGroup(),
557 FourthDimensionLocation.GetElement() );
560 gdcmWarningMacro( " FourthDimensionLocation not found at : " <<
561 std::hex << FourthDimensionLocation.GetGroup()
562 << "|" << FourthDimensionLocation.GetElement());
567 return (int)entry->GetValue(0);
572 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
573 * then 0028,0030 : Pixel Spacing
575 * @return X dimension of a pixel
577 float File::GetXSpacing()
579 float xspacing = 1.0;
580 float yspacing = 1.0;
581 float zspacing = 1.0;
586 if ( GetSpacing(xspacing,yspacing,zspacing) )
593 From:David Clunie - view profile
594 Date:Wed, May 24 2006 1:12 pm
595 Email:David Clunie <dclu...@dclunie.com>
596 Groups:comp.protocols.dicom
598 The short answer is that:
600 - (0018,1164) describes a spacing equivalent to that which
601 would be measured off a film in projection radiography
603 - (0018,7022) does not describe the image pixels themselves,
604 since detector elements may have been binned to produce
607 - (0018,7020) may be different from (0018,7022) since there
608 may be non-sensitive material separating individual
609 detectors (i.e. the size is smaller than the spacing
612 Only (0018,1164) is relevant when measuring things; the
613 detector-specific attributes are there to describe the
618 PS. For ultrasound you need to use Region Calibration.
622 It *SHOULD* first find the IOD and then deduce which tags to read
623 Eg: Cross section this is in Pixel Spacing (0028,0030)
624 CR is in Imager Pixel Spacing (0018,1164)
625 US is in Pixel Aspect Ratio (0028,0034)
627 (3002,0011) Image Plane Pixel Spacing
628 (3002,0012) RT Image Position
630 (3004,000c) for deducing Z spacing
633 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
635 /// \todo check the various SOP Class
636 /// to get the Pixel Spacing at the proper location
638 ///\todo find images to check if it *actually* works
640 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
641 // Ultrasound Image Storage (Retired)
642 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
643 // Ultrasound Image Storage
644 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
645 // Ultrasound Multi-Frame Storage (Retired)
646 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
647 // Ultrasound Multi-FrameImage Storage
649 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
650 // - guess how to deduce the spacing (FOV ?, ??)
652 entry = GetDataEntry(0x0028,0x0034);
655 nbValue = entry->GetValueCount();
657 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
658 << "has a wrong number of values :" << nbValue);
660 xspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
668 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.1") )
669 // Computed Radiography Image Storage
671 // CR is in Imager Pixel Spacing (0018,1164)//
674 // go on with old method ...
675 // ---------------------
676 // To follow David Clunie's advice, we first check ImagerPixelSpacing
678 entry = GetDataEntry(0x0018,0x1164);
681 nbValue = entry->GetValueCount();
682 // Can't use IsValueCountValid because of the complex heuristic.
684 gdcmWarningMacro("ImagerPixelSpacing (0x0018,0x1164) "
685 << "has a wrong number of values :" << nbValue);
688 xspacing = (float)entry->GetValue(2);
689 else if( nbValue >= 2 )
690 xspacing = (float)entry->GetValue(1);
692 xspacing = (float)entry->GetValue(0);
694 if ( xspacing == 0.0 )
700 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
703 entry = GetDataEntry(0x0028,0x0030);
706 nbValue = entry->GetValueCount();
708 gdcmWarningMacro("PixelSpacing (0x0018,0x0030) "
709 << "has a wrong number of values :" << nbValue);
712 xspacing = (float)entry->GetValue(2);
713 else if( nbValue >= 2 )
714 xspacing = (float)entry->GetValue(1);
716 xspacing = (float)entry->GetValue(0);
718 if ( xspacing == 0.0 )
724 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
730 * \brief gets the info from 0018,1164 : ImagerPixelSpacing
731 * then from 0028,0030 : Pixel Spacing
733 * @return Y dimension of a pixel
735 float File::GetYSpacing()
737 float xspacing = 1., yspacing = 1.0, zspacing = 1.;
741 if ( GetSpacing(xspacing,yspacing,zspacing) )
747 std::string SOPClassUID = GetEntryString(0x0008,0x0016);
749 /// \todo check the various SOP Class
750 /// to get the Pixel Spacing at the proper location
752 ///\todo find images to check if it *actually* works
754 if (Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6")
755 // Ultrasound Image Storage (Retired)
756 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.6.1")
757 // Ultrasound Image Storage
758 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3")
759 // Ultrasound Multi-Frame Storage (Retired)
760 || Util::DicomStringEqual( SOPClassUID,"1.2.840.10008.5.1.4.1.1.3.1") )
761 // Ultrasound Multi-FrameImage Storage
763 // - check if SOPClassUID contains 2 parts (e.g. "4\3")
764 // - no way to deduce the spacing/
766 entry = GetDataEntry(0x0028,0x0034);
769 nbValue = entry->GetValueCount();
771 yspacing = (float)entry->GetValue(0)/(float)entry->GetValue(1);
772 //std::cout << "ys " << yspacing << std::endl;
777 gdcmWarningMacro("PixelAspectRatio (0x0028,0x0034) "
778 << "has a wrong number of values :" << nbValue);
782 else if (nbValue == 1 ) {
783 yspacing = 1.0; // We get Pixel Aspect Ratio, not Spacing ...
793 // go on with old method ...
794 // ---------------------
795 // To follow David Clunie's advice, we first check ImagerPixelSpacing
797 // To follow David Clunie's advice, we first check ImagerPixelSpacing
799 entry = GetDataEntry(0x0018,0x1164);
802 yspacing = (float)entry->GetValue(0);
804 if ( yspacing == 0.0 )
810 gdcmWarningMacro( "Unfound Imager Pixel Spacing (0018,1164)" );
813 entry = GetDataEntry(0x0028,0x0030);
816 yspacing = (float)entry->GetValue(0);
818 if ( yspacing == 0.0 )
824 gdcmWarningMacro( "Unfound Pixel Spacing (0028,0030)" );
831 * \brief gets the info from 0018,0088 : Space Between Slices
832 * else from 0018,0050 : Slice Thickness
835 * When an element is missing, we suppose slices join together
836 * (no overlapping, no interslice gap) but we have no way to check it !
837 * For *Dicom* images, ZSpacing *should be* calculated using
838 * XOrigin, YOrigin, ZOrigin (of the top left image corner)
839 * of 2 consecutive images, and the Orientation
840 * Computing ZSpacing on a single image is not really meaningfull !
841 * @return Z dimension of a voxel-to be
843 float File::GetZSpacing()
846 float xspacing = 1.0;
847 float yspacing = 1.0;
848 float zspacing = 1.0;
849 if ( GetSpacing(xspacing,yspacing,zspacing) )
854 // Spacing Between Slices : distance between the middle of 2 slices
856 // jointives (Spacing between Slices = Slice Thickness)
857 // overlapping (Spacing between Slices < Slice Thickness)
858 // disjointes (Spacing between Slices > Slice Thickness)
859 // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
860 // It only concerns the MRI guys, not people wanting to visualize volumes
861 // If Spacing Between Slices is missing,
862 // we suppose slices joint together
863 DataEntry *entry = GetDataEntry(0x0018,0x0088);
865 { zspacing = (float)entry->GetValue(0);
867 if ( zspacing == 0.0 )
872 gdcmWarningMacro("Unfound Spacing Between Slices (0018,0088)");
874 // if no 'Spacing Between Slices' is found,
875 // we assume slices join together
876 // (no overlapping, no interslice gap)
877 entry = GetDataEntry(0x0018,0x0050);
880 zspacing = (float)entry->GetValue(0);
882 if ( zspacing == 0.0 )
887 gdcmWarningMacro("Unfound Slice Thickness (0018,0050)");
889 // if no 'Spacing Between Slices' is found,
890 // we assume slices join together
891 // (no overlapping, no interslice gap)
892 entry = GetDataEntry(0x3004,0x000c);
895 float z1 = (float)entry->GetValue(0);
896 float z2 = (float)entry->GetValue(1);
897 zspacing = z2 - z1; // can be negative...
899 if ( zspacing == 0.0 )
908 * \brief gets the info from 0020,0032 : Image Position Patient
909 * else from 0020,0030 : Image Position (RET)
911 * @return up-left image corner X position
913 float File::GetXOrigin()
915 DataEntry *entry = GetDataEntry(0x0020,0x0032);
918 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
919 entry = GetDataEntry(0x0020,0x0030);
922 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
927 if( entry->GetValueCount() == 3 )
929 if (!entry->IsValueCountValid() )
931 gdcmErrorMacro( "Invalid Value Count" );
933 return (float)entry->GetValue(0);
939 * \brief gets the info from 0020,0032 : Image Position Patient
940 * else from 0020,0030 : Image Position (RET)
942 * @return up-left image corner Y position
944 float File::GetYOrigin()
946 DataEntry *entry = GetDataEntry(0x0020,0x0032);
949 gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
950 entry = GetDataEntry(0x0020,0x0030);
953 gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
958 if( entry->GetValueCount() == 3 )
960 if (!entry->IsValueCountValid() )
962 gdcmErrorMacro( "Invalid Value Count" );
964 return (float)entry->GetValue(1);
970 * \brief gets the info from 0020,0032 : Image Position Patient
971 * else from 0020,0030 : Image Position (RET)
972 * else from 0020,1041 : Slice Location
973 * else from 0020,0050 : Location
975 * @return up-left image corner Z position
977 float File::GetZOrigin()
979 DataEntry *entry = GetDataEntry(0x0020,0x0032);
982 if( entry->GetValueCount() == 3 )
984 if (!entry->IsValueCountValid() )
986 gdcmErrorMacro( "Invalid Value Count" );
988 return (float)entry->GetValue(2);
990 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)");
994 entry = GetDataEntry(0x0020,0x0030);
997 if( entry->GetValueCount() == 3 )
999 if (!entry->IsValueCountValid() )
1001 gdcmErrorMacro( "Invalid Value Count" );
1003 return (float)entry->GetValue(2);
1005 gdcmWarningMacro( "Wrong Image Position (RET) (0020,0030)");
1009 // for *very* old ACR-NEMA images
1010 entry = GetDataEntry(0x0020,0x1041);
1013 if( entry->GetValueCount() == 1 )
1015 if (!entry->IsValueCountValid() )
1017 gdcmErrorMacro( "Invalid Value Count" );
1019 return (float)entry->GetValue(0); // VM=1 !
1021 gdcmWarningMacro( "Wrong Slice Location (0020,1041)");
1025 entry = GetDataEntry(0x0020,0x0050);
1028 if( entry->GetValueCount() == 1 )
1030 if (!entry->IsValueCountValid() )
1032 gdcmErrorMacro( "Invalid Value Count" );
1034 return (float)entry->GetValue(0);
1036 gdcmWarningMacro( "Wrong Location (0020,0050)");
1039 return 0.; // Hopeless
1043 * \brief gets the info from 0020,0037 : Image Orientation Patient
1044 * or from 0020 0035 : Image Orientation (RET)
1046 * (needed to organize DICOM files based on their x,y,z position)
1048 * @param iop adress of the (6)float array to receive values.
1049 * (defaulted as 1.,0.,0.,0.,1.,0. if nothing -or inconsistent stuff-
1051 * @return true when one of the tag -with consistent values- is found
1052 * false when nothing or inconsistent stuff - is found
1054 bool File::GetImageOrientationPatient( float iop[6] )
1056 std::string strImOriPat;
1057 //iop is supposed to be float[6]
1058 iop[0] = iop[4] = 1.;
1059 iop[1] = iop[2] = iop[3] = iop[5] = 0.;
1061 // 0020 0037 DS REL Image Orientation (Patient)
1062 if ( (strImOriPat = GetEntryString(0x0020,0x0037)) != GDCM_UNFOUND )
1064 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
1065 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
1067 gdcmWarningMacro( "Wrong Image Orientation Patient (0020,0037)."
1068 << " Less than 6 values were found." );
1074 // 0020 0035 DS REL Image Orientation (RET)
1075 else if ( (strImOriPat = GetEntryString(0x0020,0x0035)) != GDCM_UNFOUND )
1077 if ( sscanf( strImOriPat.c_str(), "%f \\ %f \\%f \\%f \\%f \\%f ",
1078 &iop[0], &iop[1], &iop[2], &iop[3], &iop[4], &iop[5]) != 6 )
1080 gdcmWarningMacro( "wrong Image Orientation Patient (0020,0035). "
1081 << "Less than 6 values were found." );
1090 * \brief gets the cosine of image X axis, against patient X axis
1091 * (Sorry, but Python needs it :-( )
1092 * @return cosine of image X axis, against patient X axis
1094 float File::GetXCosineOnX()
1097 GetImageOrientationPatient( iop );
1102 * \brief gets the cosine of image X axis, against patient Y axis
1103 * (Sorry, but Python needs it :-( )
1104 * @return cosine of image X axis, against patient Y axis
1106 float File::GetXCosineOnY()
1109 GetImageOrientationPatient( iop );
1114 * \brief gets the cosine of image X axis, against patient Z axis
1115 * (Sorry, but Python needs it :-( )
1116 * @return cosine of image X axis, against patient Z axis
1118 float File::GetXCosineOnZ()
1121 GetImageOrientationPatient( iop );
1126 * \brief gets the cosine of image Y axis, against patient X axis
1127 * (Sorry, but Python needs it :-( )
1128 * @return cosine of image Y axis, against patient X axis
1130 float File::GetYCosineOnX()
1133 GetImageOrientationPatient( iop );
1138 * \brief gets the cosine of image Y axis, against patient Y axis
1139 * (Sorry, but Python needs it :-( )
1140 * @return cosine of image Y axis, against patient Y axis
1142 float File::GetYCosineOnY()
1145 GetImageOrientationPatient( iop );
1150 * \brief gets the cosine of image Y axis, against patient Z axis
1151 * (Sorry, but Python needs it :-( )
1152 * @return cosine of image Y axis, against patient Z axis
1154 float File::GetYCosineOnZ()
1157 GetImageOrientationPatient( iop );
1161 * \brief gets the info from 0020,0032 : Image Position Patient
1162 * or from 0020 0030 : Image Position (RET)
1164 * @param ipp adress of the (3)float array to receive values.
1165 * (defaulted as 0.,0.,0. if nothing -or inconsistent stuff-
1167 * @return true when one of the tag -with consistent values- is found
1168 * false when nothing or inconsistent stuff - is found
1170 bool File::GetImagePositionPatient( float ipp[3] )
1172 std::string strImPosiPat;
1173 //ipp is supposed to be float[3]
1174 ipp[0] = ipp[1] = ipp[2] = 0.;
1176 // 0020 0032 DS REL Image Position (Patient)
1177 strImPosiPat = GetEntryString(0x0020,0x0032);
1178 if ( strImPosiPat != GDCM_UNFOUND )
1180 if ( sscanf( strImPosiPat.c_str(), "%f \\ %f \\%f ",
1181 &ipp[0], &ipp[1], &ipp[2]) != 3 )
1183 gdcmWarningMacro( "Wrong Image Position Patient (0020,0032)."
1184 << " Less than 3 values were found." );
1190 // 0020 0030 DS REL Image Position (RET)
1191 else if ( (strImPosiPat = GetEntryString(0x0020,0x0030)) != GDCM_UNFOUND )
1193 if ( sscanf( strImPosiPat.c_str(), "%f \\ %f \\%f ",
1194 &ipp[0], &ipp[1], &ipp[2]) != 3 )
1196 gdcmWarningMacro( "wrong Image Position Patient (0020,0030). "
1197 << "Less than 3 values were found." );
1206 * \brief Retrieve the number of Bits Stored (actually used)
1207 * (as opposed to number of Bits Allocated)
1208 * @return The encountered number of Bits Stored, 0 by default.
1209 * 0 means the file is NOT USABLE. The caller has to check it !
1211 int File::GetBitsStored()
1213 DataEntry *entry = GetDataEntry(0x0028,0x0101);
1216 gdcmWarningMacro("BitsStored (0028,0101) is supposed to be mandatory");
1219 return (int)entry->GetValue(0);
1223 * \brief Retrieve the number of Bits Allocated
1224 * (8, 12 -compacted ACR-NEMA files-, 16, 24 -old RGB ACR-NEMA files-,)
1225 * @return The encountered Number of Bits Allocated, 0 by default.
1226 * 0 means the file is NOT USABLE. The caller has to check it !
1228 int File::GetBitsAllocated()
1230 DataEntry *entry = GetDataEntry(0x0028,0x0100);
1233 gdcmWarningMacro("BitsAllocated (0028,0100) is supposed to be mandatory");
1236 return (int)entry->GetValue(0);
1240 * \brief Retrieve the high bit position.
1241 * \warning The method defaults to 0 when information is missing.
1242 * The responsability of checking this value is left to the caller.
1243 * @return The high bit position when present. 0 when missing.
1245 int File::GetHighBitPosition()
1247 DataEntry *entry = GetDataEntry(0x0028,0x0102);
1250 gdcmWarningMacro("HighBitPosition (0028,0102) is supposed to be mandatory");
1253 return (int)entry->GetValue(0);
1257 * \brief Retrieve the number of Samples Per Pixel
1258 * (1 : gray level, 3 : RGB/YBR -1 or 3 Planes-)
1259 * @return The encountered number of Samples Per Pixel, 1 by default.
1260 * (we assume Gray level Pixels)
1262 int File::GetSamplesPerPixel()
1264 DataEntry *entry = GetDataEntry(0x0028,0x0002);
1267 gdcmWarningMacro("SamplesPerPixel (0028,0002) is supposed to be mandatory");
1268 return 1; // Well, it's supposed to be mandatory ...
1269 // but sometimes it's missing : *we* assume Gray pixels
1271 return (int)entry->GetValue(0);
1275 * \brief Retrieve the Planar Configuration for RGB images
1276 * (0 : RGB Pixels , 1 : R Plane + G Plane + B Plane)
1277 * @return The encountered Planar Configuration, 0 by default.
1279 int File::GetPlanarConfiguration()
1281 DataEntry *entry = GetDataEntry(0x0028,0x0006);
1286 return (int)entry->GetValue(0);
1290 * \brief Return the size (in bytes) of a single pixel of data.
1291 * @return The size in bytes of a single pixel of data; 0 by default
1292 * 0 means the file is NOT USABLE; the caller will have to check
1294 int File::GetPixelSize()
1296 // 0028 0100 US IMG Bits Allocated
1297 // (in order no to be messed up by old ACR-NEMA RGB images)
1298 assert( !(GetEntryString(0x0028,0x0100) == "24") );
1300 std::string pixelType = GetPixelType();
1301 if ( pixelType == "8U" || pixelType == "8S" )
1305 if ( pixelType == "16U" || pixelType == "16S")
1309 if ( pixelType == "32U" || pixelType == "32S")
1313 if ( pixelType == "FD" )
1317 gdcmWarningMacro( "Unknown pixel type: " << pixelType);
1322 * \brief Build the Pixel Type of the image.
1323 * Possible values are:
1324 * - 8U unsigned 8 bit,
1325 * - 8S signed 8 bit,
1326 * - 16U unsigned 16 bit,
1327 * - 16S signed 16 bit,
1328 * - 32U unsigned 32 bit,
1329 * - 32S signed 32 bit,
1330 * - FD floating double 64 bits (Not kosher DICOM, but so usefull!)
1331 * \warning 12 bit images appear as 16 bit.
1332 * 24 bit images appear as 8 bit + photochromatic interp ="RGB "
1333 * + Planar Configuration = 0
1334 * @return 0S if nothing found. NOT USABLE file. The caller has to check
1336 std::string File::GetPixelType()
1338 std::string bitsAlloc = GetEntryString(0x0028, 0x0100); // Bits Allocated
1339 if ( bitsAlloc == GDCM_UNFOUND )
1341 gdcmWarningMacro( "Bits Allocated (0028,0100) supposed to be mandatory");
1342 bitsAlloc = "16"; // default and arbitrary value, not to polute the output
1345 else if ( bitsAlloc == "64" )
1349 // useless since we have to bypass a bug ( >8 && < 16)
1350 else if ( bitsAlloc == "12" )
1352 // It will be unpacked
1356 else if ( bitsAlloc == "24" )
1358 // (in order no to be messed up by old RGB images)
1362 int i= atoi(bitsAlloc.c_str()); // fix a bug in some headers
1363 if ( i > 8 && i < 16 )
1369 if( IsSignedPixelData() )
1377 return bitsAlloc + sign;
1381 * \brief Check whether the pixels are signed (1) or UNsigned (0) data.
1382 * \warning The method defaults to false (UNsigned) when tag 0028|0103
1384 * The responsability of checking this value is left to the caller
1385 * (NO transformation is performed on the pixels to make then >0)
1386 * @return True when signed, false when UNsigned
1388 bool File::IsSignedPixelData()
1390 DataEntry *entry = GetDataEntry(0x0028, 0x0103);//"Pixel Representation"
1393 gdcmWarningMacro( "Pixel Representation (0028,0103) supposed to be "
1397 return entry->GetValue(0) != 0;
1401 * \brief Check whether this a monochrome picture (gray levels) or not,
1402 * using "Photometric Interpretation" tag (0x0028,0x0004).
1403 * @return true when "MONOCHROME1" or "MONOCHROME2". False otherwise.
1405 bool File::IsMonochrome()
1407 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1408 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1")
1409 || Util::DicomStringEqual(PhotometricInterp, "MONOCHROME2") )
1413 if ( PhotometricInterp == GDCM_UNFOUND )
1415 gdcmWarningMacro( "Photometric Interpretation (0028,0004) supposed to be "
1417 // to deal with old ACR-NEMA images
1418 if (GetNumberOfScalarComponents() == 1)
1425 * \brief Check whether this a MONOCHROME1 picture (high values = dark)
1426 * or not using "Photometric Interpretation" tag (0x0028,0x0004).
1427 * @return true when "MONOCHROME1" . False otherwise.
1429 bool File::IsMonochrome1()
1431 const std::string &PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1432 if ( Util::DicomStringEqual(PhotometricInterp, "MONOCHROME1") )
1436 if ( PhotometricInterp == GDCM_UNFOUND )
1438 gdcmWarningMacro( "Photometric Interpretation (0028,0004) : supposed to"
1439 << " be mandatory! ");
1445 * \brief Check whether this a "PALETTE COLOR" picture or not by accessing
1446 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1447 * @return true when "PALETTE COLOR". False otherwise.
1449 bool File::IsPaletteColor()
1451 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1452 if ( PhotometricInterp == "PALETTE COLOR " )
1457 // MONOCHROME + [Enhanced CT Image Storage] actually have Palettes
1458 std::string sopClassUid = GetEntryString( 0x0008, 0x0016 );
1459 if (Util::DicomStringEqual( sopClassUid, "1.2.840.10008.5.1.4.1.1.2.1"))
1463 if ( PhotometricInterp == GDCM_UNFOUND )
1465 gdcmDebugMacro( "Not found : Palette color (0028,0004)");
1471 * \brief Check whether this a "YBR_FULL" color picture or not by accessing
1472 * the "Photometric Interpretation" tag ( 0x0028, 0x0004 ).
1473 * @return true when "YBR_FULL". False otherwise.
1475 bool File::IsYBRFull()
1477 std::string PhotometricInterp = GetEntryString( 0x0028, 0x0004 );
1478 if ( PhotometricInterp == "YBR_FULL" )
1482 if ( PhotometricInterp == GDCM_UNFOUND )
1484 gdcmDebugMacro( "Not found : YBR Full (0028,0004)");
1490 * \brief tells us if LUT are used
1491 * \warning Right now, 'Segmented xxx Palette Color Lookup Table Data'
1492 * are NOT considered as LUT, since nobody knows
1493 * how to deal with them
1494 * Please warn me if you know sbdy that *does* know ... jprx
1495 * @return true if LUT Descriptors and LUT Tables were found
1499 // Check the presence of the LUT Descriptors, and LUT Tables
1501 if ( !GetDocEntry(0x0028,0x1101) )
1505 // LutDescriptorGreen
1506 if ( !GetDocEntry(0x0028,0x1102) )
1510 // LutDescriptorBlue
1511 if ( !GetDocEntry(0x0028,0x1103) )
1515 // Red Palette Color Lookup Table Data
1517 segmented = GetDocEntry(0x0028,0x1221) &&
1518 GetDocEntry(0x0028,0x1222) &&
1519 GetDocEntry(0x0028,0x1223);
1520 if( segmented ) return true;
1522 if( !GetDocEntry(0x0028,0x1201) )
1526 // Green Palette Color Lookup Table Data
1527 if ( !GetDocEntry(0x0028,0x1202) )
1531 // Blue Palette Color Lookup Table Data
1532 if ( !GetDocEntry(0x0028,0x1203) )
1537 // FIXME : (0x0028,0x3006) : LUT Data (CTX dependent)
1538 // NOT taken into account, but we don't know how to use it ...
1543 * \brief gets the info from 0028,1101 : Lookup Table Desc-Red
1545 * @return Lookup Table number of Bits , 0 by default
1546 * when (0028,0004),Photometric Interpretation = [PALETTE COLOR ]
1547 * @ return bit number of each LUT item
1549 int File::GetLUTNbits()
1551 std::vector<std::string> tokens;
1554 //Just hope Lookup Table Desc-Red = Lookup Table Desc-Red
1555 // = Lookup Table Desc-Blue
1556 // Consistency already checked in GetLUTLength
1557 std::string lutDescription = GetEntryString(0x0028,0x1101);
1558 if ( lutDescription == GDCM_UNFOUND )
1563 tokens.clear(); // clean any previous value
1564 Util::Tokenize ( lutDescription, tokens, "\\" );
1565 //LutLength=atoi(tokens[0].c_str());
1566 //LutDepth=atoi(tokens[1].c_str());
1568 lutNbits = atoi( tokens[2].c_str() );
1575 // ts["1.2.840.10008.5.1.4.1.1.4.1"] = "Enhanced MR Image Storage";
1576 bool File::GetRescaleSlopeIntercept(double &slope, double &intercept)
1580 TS *ts = Global::GetTS();
1581 std::string sopclassuid_used;
1582 // D 0002|0002 [UI] [Media Storage SOP Class UID]
1583 const std::string &mediastoragesopclassuid_str = GetEntryString(0x0002,0x0002);
1584 const std::string &mediastoragesopclassuid = ts->GetValue(mediastoragesopclassuid_str);
1585 //D 0008|0016 [UI] [SOP Class UID]
1586 const std::string &sopclassuid_str = GetEntryString(0x0008,0x0016);
1587 const std::string &sopclassuid = ts->GetValue(sopclassuid_str);
1588 if ( mediastoragesopclassuid == GDCM_UNFOUND && sopclassuid == GDCM_UNFOUND )
1594 if( mediastoragesopclassuid == sopclassuid )
1596 sopclassuid_used = mediastoragesopclassuid;
1600 gdcmWarningMacro( "Inconsistant SOP Class UID: "
1601 << mediastoragesopclassuid << " and " << sopclassuid );
1605 // ok we have now the correc SOP Class UID
1606 if( sopclassuid_used == "Enhanced MR Image Storage" )
1608 SeqEntry *PerframeFunctionalGroupsSequence = GetSeqEntry(0x5200,0x9230);
1609 unsigned int n = PerframeFunctionalGroupsSequence->GetNumberOfSQItems();
1610 if( !n ) return false;
1611 SQItem *item1 = PerframeFunctionalGroupsSequence->GetFirstSQItem();
1612 DocEntry *p = item1->GetDocEntry(0x0028,0x9145);
1613 if( !p ) return false;
1614 SeqEntry *seq = dynamic_cast<SeqEntry*>(p);
1615 unsigned int n1 = seq->GetNumberOfSQItems();
1616 if( !n1 ) return false;
1617 SQItem *item2 = seq->GetFirstSQItem();
1618 // D 0028|1052 [DS] [Rescale Intercept] [0 ]
1619 DocEntry *p2 = item2->GetDocEntry(0x0028,0x1052);
1620 if( !p2 ) return false;
1621 DataEntry *entry = dynamic_cast<DataEntry *>(p2);
1622 std::string intercept_str = entry->GetString();
1623 if ( sscanf( intercept_str.c_str(), "%lf", &intercept) != 1 )
1628 // D 0028|1053 [DS] [Rescale Slope] [5.65470085470085]
1629 DocEntry *p3 = item2->GetDocEntry(0x0028,0x1053);
1630 if( !p3 ) return false;
1631 DataEntry *entry2 = dynamic_cast<DataEntry *>(p3);
1632 std::string slope_str = entry2->GetString();
1633 if ( sscanf( slope_str.c_str(), "%lf", &slope) != 1 )
1644 *\brief gets the info from 0028,1052 : Rescale Intercept
1645 * @return Rescale Intercept. defaulted to 0.0 if not found or empty
1647 double File::GetRescaleIntercept()
1649 // 0028 1052 DS IMG Rescale Intercept
1650 DataEntry *entry = GetDataEntry(0x0028, 0x1052);
1653 gdcmWarningMacro( "Missing Rescale Intercept (0028,1052)");
1656 return (float)entry->GetValue(0);
1661 *\brief gets the info from 0028,1053 : Rescale Slope
1662 * @return Rescale Slope. defaulted to 1.0 if not found or empty
1664 double File::GetRescaleSlope()
1666 double resInter = 0.;
1667 double resSlope = 1.;
1668 if ( GetRescaleSlopeIntercept(resSlope, resInter) )
1672 //0028 1053 DS IMG Rescale Slope
1673 std::string strRescSlope = GetEntryString(0x0028,0x1053);
1674 if ( strRescSlope != GDCM_UNFOUND )
1676 if ( sscanf( strRescSlope.c_str(), "%lf ", &resSlope) != 1 )
1678 // bug in the element 0x0028,0x1053
1679 gdcmWarningMacro( "Rescale Slope (0028,1053) is empty.");
1687 * \brief This function is intended to user who doesn't want
1688 * to have to manage a LUT and expects to get an RBG Pixel image
1689 * (or a monochrome one, if no LUT found ...)
1690 * \warning to be used with GetImagePixels()
1691 * @return 1 if Gray level, 3 if Color (RGB, YBR, *or PALETTE COLOR*)
1693 int File::GetNumberOfScalarComponents()
1695 if ( GetSamplesPerPixel() == 3 )
1700 // 0028 0100 US IMG Bits Allocated
1701 // (in order no to be messed up by old RGB images)
1702 if ( GetEntryString(0x0028,0x0100) == "24" )
1707 std::string strPhotometricInterpretation = GetEntryString(0x0028,0x0004);
1709 if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
1711 if ( HasLUT() )// PALETTE COLOR is NOT enough
1721 // beware of trailing space at end of string
1722 // DICOM tags are never of odd length
1723 if ( strPhotometricInterpretation == GDCM_UNFOUND ||
1724 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
1725 Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
1731 // we assume that *all* kinds of YBR are dealt with
1737 * \brief This function is intended to user that DOESN'T want
1738 * to get RGB pixels image when it's stored as a PALETTE COLOR image
1739 * - the (vtk) user is supposed to know how deal with LUTs -
1740 * \warning to be used with GetImagePixelsRaw()
1741 * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
1743 int File::GetNumberOfScalarComponentsRaw()
1745 // 0028 0100 US IMG Bits Allocated
1746 // (in order no to be messed up by old RGB images)
1747 if ( File::GetEntryString(0x0028,0x0100) == "24" )
1752 // we assume that *all* kinds of YBR are dealt with
1753 return GetSamplesPerPixel();
1757 * \brief Recover the offset (from the beginning of the file)
1758 * of *image* pixels (not *icone image* pixels, if any !)
1759 * @return Pixel Offset
1761 size_t File::GetPixelOffset()
1763 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1766 return pxlElement->GetOffset();
1770 gdcmWarningMacro( "Big trouble : Pixel Element ("
1771 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1777 * \brief Recover the pixel area length (in Bytes)
1778 * @return Pixel Element Length, as stored in the header
1779 * (NOT the memory space necessary to hold the Pixels
1780 * -in case of embeded compressed image-)
1781 * 0 : NOT USABLE file. The caller has to check.
1783 size_t File::GetPixelAreaLength()
1785 DocEntry *pxlElement = GetDocEntry(GrPixel, NumPixel);
1788 return pxlElement->GetLength();
1792 gdcmWarningMacro( "Big trouble : Pixel Element ("
1793 << std::hex << GrPixel<<","<< NumPixel<< ") NOT found" );
1799 * \brief Adds the characteristics of a new element we want to anonymize
1800 * @param group Group number of the target tag.
1801 * @param elem Element number of the target tag.
1802 * @param value new value (string) to substitute with
1804 void File::AddAnonymizeElement (uint16_t group, uint16_t elem,
1805 std::string const &value)
1811 UserAnonymizeList.push_back(el);
1815 * \brief Overwrites in the file the values of the DicomElements
1818 void File::AnonymizeNoLoad()
1820 std::fstream *fp = new std::fstream(Filename.c_str(),
1821 std::ios::in | std::ios::out | std::ios::binary);
1822 GDCM_NAME_SPACE::DocEntry *d;
1825 uint32_t valLgth = 0;
1826 std::string *spaces;
1827 for (ListElements::iterator it = UserAnonymizeList.begin();
1828 it != UserAnonymizeList.end();
1832 //std::cout << "File::AnonymizeNoLoad -------" << std::hex <<(*it).Group <<"|"<<
1834 // << "[" << (*it).Value << "] "<< std::dec << std::endl;
1835 d = GetDocEntry( (*it).Group, (*it).Elem);
1840 if ( dynamic_cast<SeqEntry *>(d) )
1842 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1846 valLgth = (*it).Value.size();
1850 offset = d->GetOffset();
1851 lgth = d->GetLength();
1853 //std::cout << "lgth " << lgth << " valLgth " << valLgth << std::endl;
1856 spaces = new std::string( lgth-valLgth, ' ');
1857 (*it).Value = (*it).Value + *spaces;
1858 //std::cout << "[" << (*it).Value << "] " << lgth << std::endl;
1861 fp->seekp( offset, std::ios::beg );
1862 fp->write( (*it).Value.c_str(), lgth );
1870 * \brief anonymize a File (remove Patient's personal info passed with
1871 * AddAnonymizeElement()
1872 * \note You cannot Anonymize a DataEntry (to be fixed)
1874 bool File::AnonymizeFile()
1876 // If Anonymisation list is empty, let's perform some basic anonymization
1877 if ( UserAnonymizeList.begin() == UserAnonymizeList.end() )
1879 // If exist, replace by spaces
1880 SetEntryString(" ",0x0010, 0x2154); // Telephone
1881 SetEntryString(" ",0x0010, 0x1040); // Adress
1882 SetEntryString(" ",0x0010, 0x0020); // Patient ID
1884 DocEntry *patientNameHE = GetDocEntry (0x0010, 0x0010);
1886 if ( patientNameHE ) // we replace it by Study Instance UID (why not ?)
1888 std::string studyInstanceUID = GetEntryString (0x0020, 0x000d);
1889 if ( studyInstanceUID != GDCM_UNFOUND )
1891 SetEntryString(studyInstanceUID, 0x0010, 0x0010);
1895 SetEntryString("anonymized", 0x0010, 0x0010);
1901 GDCM_NAME_SPACE::DocEntry *d;
1902 for (ListElements::iterator it = UserAnonymizeList.begin();
1903 it != UserAnonymizeList.end();
1906 d = GetDocEntry( (*it).Group, (*it).Elem);
1911 if ( dynamic_cast<SeqEntry *>(d) )
1913 gdcmWarningMacro( "You cannot 'Anonymize' a SeqEntry ");
1917 if ( dynamic_cast<DataEntry *>(d) )
1919 gdcmWarningMacro( "To 'Anonymize' a DataEntry, better use AnonymizeNoLoad (FIXME) ");
1924 SetEntryString ((*it).Value, (*it).Group, (*it).Elem);
1928 // In order to make definitively impossible any further identification
1929 // remove or replace all the stuff that contains a Date
1931 //0008 0012 DA ID Instance Creation Date
1932 //0008 0020 DA ID Study Date
1933 //0008 0021 DA ID Series Date
1934 //0008 0022 DA ID Acquisition Date
1935 //0008 0023 DA ID Content Date
1936 //0008 0024 DA ID Overlay Date
1937 //0008 0025 DA ID Curve Date
1938 //0008 002a DT ID Acquisition Datetime
1939 //0018 9074 DT ACQ Frame Acquisition Datetime
1940 //0018 9151 DT ACQ Frame Reference Datetime
1941 //0018 a002 DT ACQ Contribution Date Time
1942 //0020 3403 SH REL Modified Image Date (RET)
1943 //0032 0032 DA SDY Study Verified Date
1944 //0032 0034 DA SDY Study Read Date
1945 //0032 1000 DA SDY Scheduled Study Start Date
1946 //0032 1010 DA SDY Scheduled Study Stop Date
1947 //0032 1040 DA SDY Study Arrival Date
1948 //0032 1050 DA SDY Study Completion Date
1949 //0038 001a DA VIS Scheduled Admission Date
1950 //0038 001c DA VIS Scheduled Discharge Date
1951 //0038 0020 DA VIS Admitting Date
1952 //0038 0030 DA VIS Discharge Date
1953 //0040 0002 DA PRC Scheduled Procedure Step Start Date
1954 //0040 0004 DA PRC Scheduled Procedure Step End Date
1955 //0040 0244 DA PRC Performed Procedure Step Start Date
1956 //0040 0250 DA PRC Performed Procedure Step End Date
1957 //0040 2004 DA PRC Issue Date of Imaging Service Request
1958 //0040 4005 DT PRC Scheduled Procedure Step Start Date and Time
1959 //0040 4011 DT PRC Expected Completion Date and Time
1960 //0040 a030 DT PRC Verification Date Time
1961 //0040 a032 DT PRC Observation Date Time
1962 //0040 a120 DT PRC DateTime
1963 //0040 a121 DA PRC Date
1964 //0040 a13a DT PRC Referenced Datetime
1965 //0070 0082 DA ??? Presentation Creation Date
1966 //0100 0420 DT ??? SOP Autorization Date and Time
1967 //0400 0105 DT ??? Digital Signature DateTime
1968 //2100 0040 DA PJ Creation Date
1969 //3006 0008 DA SSET Structure Set Date
1970 //3008 0024 DA ??? Treatment Control Point Date
1971 //3008 0054 DA ??? First Treatment Date
1972 //3008 0056 DA ??? Most Recent Treatment Date
1973 //3008 0162 DA ??? Safe Position Exit Date
1974 //3008 0166 DA ??? Safe Position Return Date
1975 //3008 0250 DA ??? Treatment Date
1976 //300a 0006 DA RT RT Plan Date
1977 //300a 022c DA RT Air Kerma Rate Reference Date
1978 //300e 0004 DA RT Review Date
1984 * \brief Performs some consistency checking on various 'File related'
1985 * (as opposed to 'DicomDir related') entries
1986 * then writes in a file all the (Dicom Elements) included the Pixels
1987 * @param fileName file name to write to
1988 * @param writetype type of the file to be written
1989 * (ACR, ExplicitVR, ImplicitVR)
1991 bool File::Write(std::string fileName, FileType writetype)
1993 gdcmDebugMacro(" File::Write ");
1994 std::ofstream *fp = new std::ofstream(fileName.c_str(),
1995 std::ios::out | std::ios::binary);
1998 gdcmWarningMacro("Failed to open (write) File: " << fileName.c_str());
2002 // Entry : 0002|0000 = group length -> recalculated
2003 DataEntry *e0000 = GetDataEntry(0x0002,0x0000);
2006 std::ostringstream sLen;
2007 sLen << ComputeGroup0002Length( );
2008 e0000->SetString(sLen.str());
2011 /// \todo FIXME : Derma?.dcm does not have it...let's remove it ?!? JPRx
2012 if( writetype != JPEG && writetype != JPEG2000)
2014 int i_lgPix = GetEntryLength(GrPixel, NumPixel);
2015 if (i_lgPix != -2) /// \todo wtf "-2" ?!?
2017 // no (GrPixel, NumPixel) element
2018 std::string s_lgPix = Util::Format("%d", i_lgPix+12);
2019 s_lgPix = Util::DicomString( s_lgPix.c_str() );
2020 InsertEntryString(s_lgPix,GrPixel, 0x0000, "UL");
2023 Document::WriteContent(fp, writetype,false,false);
2031 //-----------------------------------------------------------------------------
2035 //-----------------------------------------------------------------------------
2038 * \brief Parse pixel data from disk of [multi-]fragment RLE encoding.
2039 * Compute the RLE extra information and store it in RLEInfo
2040 * for later pixel retrieval usage.
2042 void File::ComputeRLEInfo()
2044 std::string ts = GetTransferSyntax();
2045 if ( !Global::GetTS()->IsRLELossless(ts) )
2050 // Encoded pixel data: for the time being we are only concerned with
2051 // Jpeg or RLE Pixel data encodings.
2052 // As stated in PS 3.5-2003, section 8.2 p44:
2053 // "If sent in Encapsulated Format (i.e. other than the Native Format) the
2054 // value representation OB is used".
2055 // Hence we expect an OB value representation. Concerning OB VR,
2056 // the section PS 3.5-2003, section A.4.c p 58-59, states:
2057 // "For the Value Representations OB and OW, the encoding shall meet the
2058 // following specifications depending on the Data element tag:"
2060 // - the first item in the sequence of items before the encoded pixel
2061 // data stream shall be basic offset table item. The basic offset table
2062 // item value, however, is not required to be present"
2063 ReadEncapsulatedBasicOffsetTable();
2065 // Encapsulated RLE Compressed Images (see PS 3.5-2003, Annex G)
2066 // Loop on the individual frame[s] and store the information
2067 // on the RLE fragments in a RLEFramesInfo.
2068 // Note: - when only a single frame is present, this is a
2070 // - when more than one frame are present, then we are in
2071 // the case of a multi-frame image.
2075 while ( (frameLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
2077 // Since we have read the basic offset table, let's check the value were correct
2078 // or else produce a warning:
2079 if ( BasicOffsetTableItemValue )
2081 // If a BasicOffsetTableItemValue was read
2082 uint32_t individualLength = BasicOffsetTableItemValue[i];
2083 assert( individualLength == sum ); // REMOVE that if this is a problem
2084 if( individualLength != sum )
2086 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght" );
2088 sum += frameLength + 8;
2091 // Parse the RLE Header and store the corresponding RLE Segment
2092 // Offset Table information on fragments of this current Frame.
2093 // Note that the fragment pixels themselves are not loaded
2094 // (but just skipped).
2095 long frameOffset = Fp->tellg(); // once per fragment
2097 uint32_t nbRleSegments = ReadInt32();
2098 if ( nbRleSegments > 16 )
2100 // There should be at most 15 segments (refer to RLEFrame class)
2101 gdcmWarningMacro( "Too many segments.");
2104 uint32_t rleSegmentOffsetTable[16];
2105 for( int k = 1; k <= 15; k++ )
2107 rleSegmentOffsetTable[k] = ReadInt32();
2110 // Deduce from both RLE Header and frameLength
2111 // the fragment length, and again store this info
2112 // in a RLEFramesInfo.
2113 long rleSegmentLength[15];
2114 // skipping (not reading) RLE Segments
2115 if ( nbRleSegments > 1)
2117 for(unsigned int k = 1; k <= nbRleSegments-1; k++)
2119 rleSegmentLength[k] = rleSegmentOffsetTable[k+1]
2120 - rleSegmentOffsetTable[k];
2121 SkipBytes(rleSegmentLength[k]);
2125 rleSegmentLength[nbRleSegments] = frameLength
2126 - rleSegmentOffsetTable[nbRleSegments];
2127 SkipBytes(rleSegmentLength[nbRleSegments]);
2129 // Store the collected info
2130 RLEFrame *newFrame = new RLEFrame;
2131 newFrame->SetNumberOfFragments(nbRleSegments);
2132 for( unsigned int uk = 1; uk <= nbRleSegments; uk++ )
2134 newFrame->SetOffset(uk,frameOffset + rleSegmentOffsetTable[uk]);
2135 newFrame->SetLength(uk,rleSegmentLength[uk]);
2137 RLEInfo->AddFrame(newFrame);
2140 // Make sure that we encounter a 'Sequence Delimiter Item'
2141 // at the end of the item :
2142 if ( !ReadTag(0xfffe, 0xe0dd) ) // once per RLE File
2144 gdcmWarningMacro( "No sequence delimiter item at end of RLE item sequence");
2149 * \brief Parse pixel data from disk of [multi-]fragment Jpeg encoding.
2150 * Compute the jpeg extra information (fragment[s] offset[s] and
2151 * length) and store it[them] in JPEGInfo for later pixel
2154 void File::ComputeJPEGFragmentInfo()
2156 // If you need to, look for comments of ComputeRLEInfo().
2157 std::string ts = GetTransferSyntax();
2158 if ( ! Global::GetTS()->IsJPEG(ts) )
2163 ReadEncapsulatedBasicOffsetTable();
2165 // Loop on the fragments[s] and store the parsed information in a
2167 long fragmentLength;
2170 while ( (fragmentLength = ReadTagLength(0xfffe, 0xe000)) != 0 )
2172 // Since we have read the basic offset table, let's check the value were correct
2173 // or else produce a warning:
2174 // A.4 Transfer syntaxes for encapsulation of encoded pixel data:
2175 // When the Item Value is present, the Basic Offset Table Item Value shall contain
2176 // concatenated 32-bit unsigned integer values that are byte offsets to the first
2177 // byte of the Item Tag of the first fragment for each frame in the Sequence of
2178 // Items. These offsets are measured from the first byte of the first Item Tag
2179 // following the Basic Offset Table item (See Table A.4-2).
2181 if ( BasicOffsetTableItemValue )
2183 // If a BasicOffsetTableItemValue was read
2184 uint32_t individualLength = BasicOffsetTableItemValue[i];
2185 //assert( individualLength == sum ); // Seems like 00191113.dcm is off by one ??
2186 if( individualLength != sum )
2188 gdcmWarningMacro( "BasicOffsetTableItemValue differs from the fragment lenght:" <<
2189 individualLength << " != " << sum );
2191 sum += fragmentLength + 8;
2195 long fragmentOffset = Fp->tellg(); // Once per fragment
2196 // Store the collected info
2197 JPEGFragment *newFragment = new JPEGFragment;
2198 newFragment->SetOffset(fragmentOffset);
2199 newFragment->SetLength(fragmentLength);
2200 JPEGInfo->AddFragment(newFragment);
2202 SkipBytes(fragmentLength);
2205 // Make sure that we encounter a 'Sequence Delimiter Item'
2206 // at the end of the item :
2207 if ( !ReadTag(0xfffe, 0xe0dd) )
2209 gdcmWarningMacro( "No sequence delimiter item at end of JPEG item sequence");
2214 * \brief Assuming the internal file pointer Document::Fp
2215 * is placed at the beginning of a tag, check whether this
2216 * tag is (TestGroup, TestElem).
2217 * \warning On success the internal file pointer Document::Fp
2218 * is modified to point after the tag.
2219 * On failure (i.e. when the tag wasn't the expected tag
2220 * (TestGroup, TestElem) the internal file pointer
2221 * Document::Fp is restored to its original position.
2222 * @param testGroup The expected group of the tag.
2223 * @param testElem The expected Element of the tag.
2224 * @return True on success, false otherwise.
2226 bool File::ReadTag(uint16_t testGroup, uint16_t testElem)
2228 long positionOnEntry = Fp->tellg(); // Only when reading fragments
2229 //long currentPosition = positionOnEntry; // On debugging purposes
2231 // Read the Item Tag group and element, and make
2232 // sure they are what we expected:
2233 uint16_t itemTagGroup;
2234 uint16_t itemTagElem;
2237 itemTagGroup = ReadInt16();
2238 itemTagElem = ReadInt16();
2240 catch ( FormatError )
2242 gdcmErrorMacro( "Can not read tag at 0x("
2243 << std::hex << positionOnEntry
2244 << "). We should have found tag ("
2245 << DictEntry::TranslateToKey(testGroup,testElem) << ")"
2250 if ( itemTagGroup != testGroup || itemTagElem != testElem )
2252 // in order not to pollute output we don't warn on 'delimitors'
2253 if (itemTagGroup != 0xfffe || testGroup != 0xfffe )
2254 gdcmWarningMacro( "Wrong Item Tag found:"
2255 << " We should have found tag ("
2256 << DictEntry::TranslateToKey(testGroup,testElem) << ")" << std::endl
2257 << " but instead we encountered tag ("
2258 << DictEntry::TranslateToKey(itemTagGroup,itemTagElem) << ")"
2259 << " at address: " << " 0x(" << std::hex
2260 << (unsigned int)positionOnEntry << std::dec << ")"
2262 Fp->seekg(positionOnEntry, std::ios::beg);
2270 * \brief Assuming the internal file pointer Document::Fp
2271 * is placed at the beginning of a tag (TestGroup, TestElement),
2272 * read the length associated to the Tag.
2273 * \warning On success the internal file pointer Document::Fp
2274 * is modified to point after the tag and its length.
2275 * On failure (i.e. when the tag wasn't the expected tag
2276 * (TestGroup, TestElement) the internal file pointer
2277 * Document::Fp is restored to its original position.
2278 * @param testGroup The expected Group of the tag.
2279 * @param testElem The expected Element of the tag.
2280 * @return On success returns the length associated to the tag. On failure
2283 uint32_t File::ReadTagLength(uint16_t testGroup, uint16_t testElem)
2286 if ( !ReadTag(testGroup, testElem) )
2288 // Avoid polutting output
2289 if ( testGroup != 0xfffe )
2290 gdcmErrorMacro( "ReadTag did not succeed for ("
2291 << DictEntry::TranslateToKey(testGroup,testElem)
2296 //// Then read the associated Item Length
2298 // long currentPosition = Fp->tellg(); // save time // JPRx
2299 uint32_t itemLength = ReadInt32();
2300 gdcmDebugMacro( "Basic Item Length is: " << itemLength
2301 // << " at address: " << std::hex << (unsigned int)currentPosition
2307 * \brief When parsing the Pixel Data of an encapsulated file, read
2308 * the basic offset table (when present, and BTW dump it).
2310 void File::ReadEncapsulatedBasicOffsetTable()
2312 //// Read the Basic Offset Table Item Tag length...
2313 uint32_t itemLength = ReadTagLength(0xfffe, 0xe000);
2315 // When present, read the basic offset table itself.
2316 // Notes: - since the presence of this basic offset table is optional
2317 // we can't rely on it for the implementation, and we will simply
2318 // trash its content (when present).
2319 // - still, when present, we could add some further checks on the
2320 // lengths, but we won't bother with such fuses for the time being.
2321 if ( itemLength != 0 )
2323 char *charBasicOffsetTableItemValue = new char[itemLength];
2324 Fp->read(charBasicOffsetTableItemValue, itemLength);
2325 unsigned int nbEntries = itemLength/4;
2326 assert( nbEntries*4 == itemLength); // Make sure this is a multiple
2327 BasicOffsetTableItemValue = new uint32_t[nbEntries];
2329 for (unsigned int i=0; i < nbEntries; i++ )
2331 BasicOffsetTableItemValue[i] = *((uint32_t*)(&charBasicOffsetTableItemValue[4*i]));
2332 #if defined(GDCM_WORDS_BIGENDIAN) || defined(GDCM_FORCE_BIGENDIAN_EMULATION)
2333 uint32_t val = BasicOffsetTableItemValue[i];
2334 BasicOffsetTableItemValue[i]
2335 = ( (val<<24) | ((val<<8) & 0x00ff0000) |
2336 ( (val>>8) & 0x0000ff00) | (val>>24) );
2338 gdcmDebugMacro( "Read one length for: " <<
2339 std::hex << BasicOffsetTableItemValue[i] );
2342 delete[] charBasicOffsetTableItemValue;
2346 // These are the deprecated method that one day should be removed (after the next release)
2348 //#ifndef GDCM_LEGACY_REMOVE
2350 * \ brief Loader. (DEPRECATED : temporaryly kept not to break the API)
2351 * @ param fileName file to be open for parsing
2352 * @ return false if file cannot be open or no swap info was found,
2353 * or no tag was found.
2354 * @deprecated Use the Load() [ + SetLoadMode() ] + SetFileName() functions instead
2357 bool File::Load( std::string const &fileName )
2359 GDCM_LEGACY_REPLACED_BODY(File::Load(std::string), "1.2",
2361 SetFileName( fileName );
2362 if ( ! this->Document::Load( ) )
2365 return DoTheLoadingJob( );
2369 //-----------------------------------------------------------------------------
2372 //-----------------------------------------------------------------------------
2373 } // end namespace gdcm