+//-----------------------------------------------------------------------------
+// Constructor / Destructor
+/**
+ * \brief Constructor
+ * @param filename name of the file whose header we want to analyze
+ */
+File::File( std::string const &filename ):
+ Document( filename )
+{
+ RLEInfo = new RLEFramesInfo;
+ JPEGInfo = new JPEGFragmentsInfo;
+
+ // for some ACR-NEMA images GrPixel, NumPixel is *not* 7fe0,0010
+ // We may encounter the 'RETired' (0x0028, 0x0200) tag
+ // (Image Location") . This entry contains the number of
+ // the group that contains the pixel data (hence the "Pixel Data"
+ // is found by indirection through the "Image Location").
+ // Inside the group pointed by "Image Location" the searched element
+ // is conventionally the element 0x0010 (when the norm is respected).
+ // When the "Image Location" is missing we default to group 0x7fe0.
+ // Note: this IS the right place for the code
+
+ // Image Location
+ const std::string &imgLocation = GetEntryValue(0x0028, 0x0200);
+ if ( imgLocation == GDCM_UNFOUND )
+ {
+ // default value
+ GrPixel = 0x7fe0;
+ }
+ else
+ {
+ GrPixel = (uint16_t) atoi( imgLocation.c_str() );
+ }
+
+ // sometimes Image Location value doesn't follow
+ // the supposed processor endianness.
+ // see gdcmData/cr172241.dcm
+ if ( GrPixel == 0xe07f )
+ {
+ GrPixel = 0x7fe0;
+ }
+
+ if ( GrPixel != 0x7fe0 )
+ {
+ // This is a kludge for old dirty Philips imager.
+ NumPixel = 0x1010;
+ }
+ else
+ {
+ NumPixel = 0x0010;
+ }
+
+ // Now, we know GrPixel and NumPixel.
+ // Let's create a VirtualDictEntry to allow a further VR modification
+ // and force VR to match with BitsAllocated.
+ DocEntry *entry = GetDocEntry(GrPixel, NumPixel);
+ if ( entry != 0 )
+ {
+ // Compute the RLE or JPEG info
+ OpenFile();
+ std::string ts = GetTransferSyntax();
+ Fp->seekg( entry->GetOffset(), std::ios::beg );
+ if ( Global::GetTS()->IsRLELossless(ts) )
+ ComputeRLEInfo();
+ else if ( Global::GetTS()->IsJPEG(ts) )
+ ComputeJPEGFragmentInfo();
+ CloseFile();
+
+ // Change the created dict entry
+ std::string PixelVR;
+ // 8 bits allocated is a 'O Bytes' , as well as 24 (old ACR-NEMA RGB)
+ // more than 8 (i.e 12, 16) is a 'O Words'
+ if ( GetBitsAllocated() == 8 || GetBitsAllocated() == 24 )
+ PixelVR = "OB";
+ else
+ PixelVR = "OW";
+
+ DictEntry* newEntry = NewVirtualDictEntry(GrPixel, NumPixel,
+ PixelVR, "PXL", "Pixel Data");
+
+ // friend class hunting : should we *create* a new entry,
+ // instead of modifying its DictEntry,in order not to use 'friend' ?
+ entry->SetDictEntry( newEntry );
+ }
+}
+
+/**
+ * \brief Constructor used when we want to generate dicom files from scratch
+ */
+File::File():
+ Document()
+{
+ RLEInfo = new RLEFramesInfo;
+ JPEGInfo = new JPEGFragmentsInfo;
+ InitializeDefaultFile();
+}
+
+/**
+ * \brief Canonical destructor.
+ */
+File::~File ()
+{
+ if( RLEInfo )
+ delete RLEInfo;
+ if( JPEGInfo )
+ delete JPEGInfo;
+}
+
+/**
+ * \brief Performs some consistency checking on various 'File related'
+ * (as opposed to 'DicomDir related') entries
+ * then writes in a file all the (Dicom Elements) included the Pixels
+ * @param fileName file name to write to
+ * @param filetype Type of the File to be written
+ * (ACR, ExplicitVR, ImplicitVR)
+ */
+bool File::Write(std::string fileName, FileType filetype)
+{
+ std::ofstream *fp = new std::ofstream(fileName.c_str(),
+ std::ios::out | std::ios::binary);
+ if (*fp == NULL)
+ {
+ gdcmVerboseMacro("Failed to open (write) File: " << fileName.c_str());
+ return false;
+ }
+
+ // Bits Allocated
+ if ( GetEntryValue(0x0028,0x0100) == "12")
+ {
+ SetValEntry("16", 0x0028,0x0100);
+ }
+
+ /// \todo correct 'Pixel group' Length if necessary
+
+ int i_lgPix = GetEntryLength(GrPixel, NumPixel);
+ if (i_lgPix != -2)
+ {
+ // no (GrPixel, NumPixel) element
+ std::string s_lgPix = Util::Format("%d", i_lgPix+12);
+ s_lgPix = Util::DicomString( s_lgPix.c_str() );
+ InsertValEntry(s_lgPix,GrPixel, 0x0000);
+ }
+
+ // FIXME : should be nice if we could move it to File
+ // (or in future gdcmPixelData class)
+
+ // Drop Palette Color, if necessary
+
+ if ( GetEntryValue(0x0028,0x0002).c_str()[0] == '3' )
+ {
+ // if SamplesPerPixel = 3, sure we don't need any LUT !
+ // Drop 0028|1101, 0028|1102, 0028|1103
+ // Drop 0028|1201, 0028|1202, 0028|1203
+
+ DocEntry *e = GetDocEntry(0x0028,0x01101);
+ if (e)
+ {
+ RemoveEntryNoDestroy(e);
+ }
+ e = GetDocEntry(0x0028,0x1102);
+ if (e)
+ {
+ RemoveEntryNoDestroy(e);
+ }
+ e = GetDocEntry(0x0028,0x1103);
+ if (e)
+ {
+ RemoveEntryNoDestroy(e);
+ }
+ e = GetDocEntry(0x0028,0x01201);
+ if (e)
+ {
+ RemoveEntryNoDestroy(e);
+ }
+ e = GetDocEntry(0x0028,0x1202);
+ if (e)
+ {
+ RemoveEntryNoDestroy(e);
+ }
+ e = GetDocEntry(0x0028,0x1203);
+ if (e)
+ {
+ RemoveEntryNoDestroy(e);
+ }
+ }
+
+/*
+#ifdef GDCM_WORDS_BIGENDIAN
+ // Super Super hack that will make gdcm a BOMB ! but should
+ // Fix temporarily the dashboard
+ BinEntry *b = GetBinEntry(GrPixel,NumPixel);
+ if ( GetPixelSize() == 16 )
+ {
+ uint16_t *im16 = (uint16_t*)b->GetBinArea();
+ int lgr = b->GetLength();
+ for( int i = 0; i < lgr / 2; i++ )
+ {
+ im16[i]= (im16[i] >> 8) | (im16[i] << 8 );
+ }
+ }
+#endif //GDCM_WORDS_BIGENDIAN
+*/
+
+ Document::WriteContent(fp,filetype);
+
+/*
+#ifdef GDCM_WORDS_BIGENDIAN
+ // Flip back the pixel ... I told you this is a hack
+ if ( GetPixelSize() == 16 )
+ {
+ uint16_t *im16 = (uint16_t*)b->GetBinArea();
+ int lgr = b->GetLength();
+ for( int i = 0; i < lgr / 2; i++ )
+ {
+ im16[i]= (im16[i] >> 8) | (im16[i] << 8 );
+ }
+ }
+#endif //GDCM_WORDS_BIGENDIAN
+*/
+
+ fp->close();
+ delete fp;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Print
+
+
+//-----------------------------------------------------------------------------
+// Public
+
+/**
+ * \brief This predicate, based on hopefully reasonable heuristics,
+ * decides whether or not the current File was properly parsed
+ * and contains the mandatory information for being considered as
+ * a well formed and usable Dicom/Acr File.
+ * @return true when File is the one of a reasonable Dicom/Acr file,
+ * false otherwise.
+ */
+bool File::IsReadable()
+{
+ if( !Document::IsReadable() )
+ {
+ return false;
+ }
+
+ const std::string &res = GetEntryValue(0x0028, 0x0005);
+ if ( res != GDCM_UNFOUND && atoi(res.c_str()) > 4 )
+ {
+ return false; // Image Dimensions
+ }
+ if ( !GetDocEntry(0x0028, 0x0100) )
+ {
+ return false; // "Bits Allocated"
+ }
+ if ( !GetDocEntry(0x0028, 0x0101) )
+ {
+ return false; // "Bits Stored"
+ }
+ if ( !GetDocEntry(0x0028, 0x0102) )
+ {
+ return false; // "High Bit"
+ }
+ if ( !GetDocEntry(0x0028, 0x0103) )
+ {
+ return false; // "Pixel Representation" i.e. 'Sign'
+ }
+
+ return true;
+}
+
+/**
+ * \brief Retrieve the number of columns of image.
+ * @return The encountered size when found, 0 by default.
+ * 0 means the file is NOT USABLE. The caller will have to check
+ */
+int File::GetXSize()
+{
+ const std::string &strSize = GetEntryValue(0x0028,0x0011);
+ if ( strSize == GDCM_UNFOUND )
+ {
+ return 0;
+ }
+
+ return atoi( strSize.c_str() );
+}
+
+/**
+ * \brief Retrieve the number of lines of image.
+ * \warning The defaulted value is 1 as opposed to File::GetXSize()
+ * @return The encountered size when found, 1 by default
+ * (The ACR-NEMA file contains a Signal, not an Image).
+ */
+int File::GetYSize()
+{
+ const std::string &strSize = GetEntryValue(0x0028,0x0010);
+ if ( strSize != GDCM_UNFOUND )
+ {
+ return atoi( strSize.c_str() );
+ }
+ if ( IsDicomV3() )
+ {
+ return 0;
+ }
+
+ // The Rows (0028,0010) entry was optional for ACR/NEMA. It might
+ // hence be a signal (1D image). So we default to 1:
+ return 1;
+}
+
+/**
+ * \brief Retrieve the number of planes of volume or the number
+ * of frames of a multiframe.
+ * \warning When present we consider the "Number of Frames" as the third
+ * dimension. When Missing we consider the third dimension as
+ * being the ACR-NEMA "Planes" tag content.
+ * @return The encountered size when found, 1 by default (single image).
+ */
+int File::GetZSize()
+{
+ // Both DicomV3 and ACR/Nema consider the "Number of Frames"
+ // as the third dimension.
+ const std::string &strSize = GetEntryValue(0x0028,0x0008);
+ if ( strSize != GDCM_UNFOUND )
+ {
+ return atoi( strSize.c_str() );
+ }
+
+ // We then consider the "Planes" entry as the third dimension
+ const std::string &strSize2 = GetEntryValue(0x0028,0x0012);
+ if ( strSize2 != GDCM_UNFOUND )
+ {
+ return atoi( strSize2.c_str() );
+ }
+
+ return 1;
+}
+
+/**
+ * \brief gets the info from 0028,0030 : Pixel Spacing
+ * else 1.0
+ * @return X dimension of a pixel
+ */
+float File::GetXSpacing()
+{
+ float xspacing, yspacing;
+ const std::string &strSpacing = GetEntryValue(0x0028,0x0030);
+
+ if ( strSpacing == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro( "Unfound Pixel Spacing (0028,0030)" );
+ return 1.;
+ }
+
+ int nbValues;
+ if( ( nbValues = sscanf( strSpacing.c_str(),
+ "%f\\%f", &yspacing, &xspacing)) != 2 )
+ {
+ // if single value is found, xspacing is defaulted to yspacing
+ if ( nbValues == 1 )
+ {
+ xspacing = yspacing;
+ }
+
+ if ( xspacing == 0.0 )
+ xspacing = 1.0;
+
+ return xspacing;
+
+ }
+
+ // to avoid troubles with David Clunie's-like images
+ if ( xspacing == 0. && yspacing == 0.)
+ return 1.;
+
+ if ( xspacing == 0.)
+ {
+ gdcmVerboseMacro("gdcmData/CT-MONO2-8-abdo.dcm problem");
+ // seems to be a bug in the header ...
+ nbValues = sscanf( strSpacing.c_str(), "%f\\0\\%f", &yspacing, &xspacing);
+ gdcmAssertMacro( nbValues == 2 );
+ }
+
+ return xspacing;
+}
+
+/**
+ * \brief gets the info from 0028,0030 : Pixel Spacing
+ * else 1.0
+ * @return Y dimension of a pixel
+ */
+float File::GetYSpacing()
+{
+ float yspacing = 1.;
+ std::string strSpacing = GetEntryValue(0x0028,0x0030);
+
+ if ( strSpacing == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro("Unfound Pixel Spacing (0028,0030)");
+ return 1.;
+ }
+
+ // if sscanf cannot read any float value, it won't affect yspacing
+ sscanf( strSpacing.c_str(), "%f", &yspacing);
+
+ if ( yspacing == 0.0 )
+ yspacing = 1.0;
+
+ return yspacing;
+}
+
+/**
+ * \brief gets the info from 0018,0088 : Space Between Slices
+ * else from 0018,0050 : Slice Thickness
+ * else 1.0
+ * @return Z dimension of a voxel-to be
+ */
+float File::GetZSpacing()
+{
+ // Spacing Between Slices : distance entre le milieu de chaque coupe
+ // Les coupes peuvent etre :
+ // jointives (Spacing between Slices = Slice Thickness)
+ // chevauchantes (Spacing between Slices < Slice Thickness)
+ // disjointes (Spacing between Slices > Slice Thickness)
+ // Slice Thickness : epaisseur de tissus sur laquelle est acquis le signal
+ // ca interesse le physicien de l'IRM, pas le visualisateur de volumes ...
+ // Si le Spacing Between Slices est Missing,
+ // on suppose que les coupes sont jointives
+
+ const std::string &strSpacingBSlices = GetEntryValue(0x0018,0x0088);
+
+ if ( strSpacingBSlices == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro("Unfound Spacing Between Slices (0018,0088)");
+ const std::string &strSliceThickness = GetEntryValue(0x0018,0x0050);
+ if ( strSliceThickness == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro("Unfound Slice Thickness (0018,0050)");
+ return 1.;
+ }
+ else
+ {
+ // if no 'Spacing Between Slices' is found,
+ // we assume slices join together
+ // (no overlapping, no interslice gap)
+ // if they don't, we're fucked up
+ return (float)atof( strSliceThickness.c_str() );
+ }
+ }
+ //else
+ return (float)atof( strSpacingBSlices.c_str() );
+}
+
+/**
+ *\brief gets the info from 0028,1052 : Rescale Intercept
+ * @return Rescale Intercept
+ */
+float File::GetRescaleIntercept()
+{
+ float resInter = 0.;
+ /// 0028 1052 DS IMG Rescale Intercept
+ const std::string &strRescInter = GetEntryValue(0x0028,0x1052);
+ if ( strRescInter != GDCM_UNFOUND )
+ {
+ if( sscanf( strRescInter.c_str(), "%f", &resInter) != 1 )
+ {
+ // bug in the element 0x0028,0x1052
+ gdcmVerboseMacro( "Rescale Intercept (0028,1052) is empty." );
+ }
+ }
+
+ return resInter;
+}
+
+/**
+ *\brief gets the info from 0028,1053 : Rescale Slope
+ * @return Rescale Slope
+ */
+float File::GetRescaleSlope()
+{
+ float resSlope = 1.;
+ //0028 1053 DS IMG Rescale Slope
+ std::string strRescSlope = GetEntryValue(0x0028,0x1053);
+ if ( strRescSlope != GDCM_UNFOUND )
+ {
+ if( sscanf( strRescSlope.c_str(), "%f", &resSlope) != 1)
+ {
+ // bug in the element 0x0028,0x1053
+ gdcmVerboseMacro( "Rescale Slope (0028,1053) is empty.");
+ }
+ }
+
+ return resSlope;
+}
+
+/**
+ * \brief This function is intended to user who doesn't want
+ * to have to manage a LUT and expects to get an RBG Pixel image
+ * (or a monochrome one ...)
+ * \warning to be used with GetImagePixels()
+ * @return 1 if Gray level, 3 if Color (RGB, YBR or PALETTE COLOR)
+ */
+int File::GetNumberOfScalarComponents()
+{
+ if ( GetSamplesPerPixel() == 3 )
+ {
+ return 3;
+ }
+
+ // 0028 0100 US IMG Bits Allocated
+ // (in order no to be messed up by old RGB images)
+ if ( GetEntryValue(0x0028,0x0100) == "24" )
+ {
+ return 3;
+ }
+
+ std::string strPhotometricInterpretation = GetEntryValue(0x0028,0x0004);
+
+ if ( ( strPhotometricInterpretation == "PALETTE COLOR ") )
+ {
+ if ( HasLUT() )// PALETTE COLOR is NOT enough
+ {
+ return 3;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ // beware of trailing space at end of string
+ // DICOM tags are never of odd length
+ if ( strPhotometricInterpretation == GDCM_UNFOUND ||
+ Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME1") ||
+ Util::DicomStringEqual(strPhotometricInterpretation, "MONOCHROME2") )
+ {
+ return 1;
+ }
+ else
+ {
+ // we assume that *all* kinds of YBR are dealt with
+ return 3;
+ }
+}
+
+/**
+ * \brief This function is intended to user that DOESN'T want
+ * to get RGB pixels image when it's stored as a PALETTE COLOR image
+ * - the (vtk) user is supposed to know how deal with LUTs -
+ * \warning to be used with GetImagePixelsRaw()
+ * @return 1 if Gray level, 3 if Color (RGB or YBR - NOT 'PALETTE COLOR' -)
+ */
+int File::GetNumberOfScalarComponentsRaw()
+{
+ // 0028 0100 US IMG Bits Allocated
+ // (in order no to be messed up by old RGB images)
+ if ( File::GetEntryValue(0x0028,0x0100) == "24" )
+ {
+ return 3;
+ }
+
+ // we assume that *all* kinds of YBR are dealt with
+ return GetSamplesPerPixel();
+}
+
+//
+// -------------- Remember ! ----------------------------------
+//
+// Image Position Patient (0020,0032):
+// If not found (ACR_NEMA) we try Image Position (0020,0030)
+// If not found (ACR-NEMA), we consider Slice Location (0020,1041)
+// or Location (0020,0050)
+// as the Z coordinate,
+// 0. for all the coordinates if nothing is found
+//
+// ---------------------------------------------------------------
+//
+
+/**
+ * \brief gets the info from 0020,0032 : Image Position Patient
+ * else from 0020,0030 : Image Position (RET)
+ * else 0.
+ * @return up-left image corner X position
+ */
+float File::GetXOrigin()
+{
+ float xImPos, yImPos, zImPos;
+ std::string strImPos = GetEntryValue(0x0020,0x0032);
+
+ if ( strImPos == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro( "Unfound Image Position Patient (0020,0032)");
+ strImPos = GetEntryValue(0x0020,0x0030); // For ACR-NEMA images
+ if ( strImPos == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro( "Unfound Image Position (RET) (0020,0030)");
+ return 0.;
+ }
+ }
+
+ if( sscanf( strImPos.c_str(), "%f\\%f\\%f", &xImPos, &yImPos, &zImPos) != 3 )
+ {
+ return 0.;
+ }
+
+ return xImPos;
+}
+
+/**
+ * \brief gets the info from 0020,0032 : Image Position Patient
+ * else from 0020,0030 : Image Position (RET)
+ * else 0.
+ * @return up-left image corner Y position
+ */
+float File::GetYOrigin()
+{
+ float xImPos, yImPos, zImPos;
+ std::string strImPos = GetEntryValue(0x0020,0x0032);
+
+ if ( strImPos == GDCM_UNFOUND)
+ {
+ gdcmVerboseMacro( "Unfound Image Position Patient (0020,0032)");
+ strImPos = GetEntryValue(0x0020,0x0030); // For ACR-NEMA images
+ if ( strImPos == GDCM_UNFOUND )
+ {
+ gdcmVerboseMacro( "Unfound Image Position (RET) (0020,0030)");
+ return 0.;
+ }
+ }
+
+ if( sscanf( strImPos.c_str(), "%f\\%f\\%f", &xImPos, &yImPos, &zImPos) != 3 )
+ {
+ return 0.;
+ }
+
+ return yImPos;
+}
+
+/**
+ * \brief gets the info from 0020,0032 : Image Position Patient
+ * else from 0020,0030 : Image Position (RET)
+ * else from 0020,1041 : Slice Location
+ * else from 0020,0050 : Location
+ * else 0.
+ * @return up-left image corner Z position
+ */
+float File::GetZOrigin()
+{
+ float xImPos, yImPos, zImPos;
+ std::string strImPos = GetEntryValue(0x0020,0x0032);
+
+ if ( strImPos != GDCM_UNFOUND )
+ {
+ if( sscanf( strImPos.c_str(), "%f\\%f\\%f", &xImPos, &yImPos, &zImPos) != 3)
+ {
+ gdcmVerboseMacro( "Wrong Image Position Patient (0020,0032)");
+ return 0.; // bug in the element 0x0020,0x0032
+ }
+ else
+ {
+ return zImPos;
+ }
+ }
+
+ strImPos = GetEntryValue(0x0020,0x0030); // For ACR-NEMA images
+ if ( strImPos != GDCM_UNFOUND )
+ {
+ if( sscanf( strImPos.c_str(),
+ "%f\\%f\\%f", &xImPos, &yImPos, &zImPos ) != 3 )
+ {
+ gdcmVerboseMacro( "Wrong Image Position (RET) (0020,0030)");
+ return 0.; // bug in the element 0x0020,0x0032
+ }
+ else
+ {
+ return zImPos;
+ }
+ }