Module: $RCSfile: gdcmFileHelper.cxx,v $
Language: C++
- Date: $Date: 2007/07/26 08:36:49 $
- Version: $Revision: 1.119 $
+ Date: $Date: 2007/10/25 07:52:59 $
+ Version: $Revision: 1.136 $
Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de
l'Image). All rights reserved. See Doc/License.txt or
#include "gdcmDocEntryArchive.h"
#include "gdcmDictSet.h"
#include "gdcmOrientation.h"
-
+
+
+
+#include <algorithm> // for transform?
+
#if defined(__BORLANDC__)
- #include <mem.h> // for memset
+ #include <mem.h> // for memset
+ #include <ctype.h> //for toupper
+ #include <math.h>
#endif
#include <fstream>
// to get the pixels, user needs a gdcm::FileHelper
gdcm::FileHelper *fh = new gdcm::FileHelper(f);
+
// user may ask not to convert Palette (if any) to RGB
uint8_t *pixels = fh->GetImageDataRaw();
int imageLength = fh->GetImageDataRawSize();
+
// He can now use the pixels, create a new image, ...
uint8_t *userPixels = ...
-To re-write the image, user re-uses the gdcm::FileHelper
+//To re-write the image, user re-uses the gdcm::FileHelper
+gdcm::File *fh = new gdcm::FileHelper();
-fh->SetImageData( userPixels, userPixelsLength);
fh->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB
// (WriteMode is set)
fh->SetPhotometricInterpretationToMonochrome1();
fh->SetWriteTypeToDcmExpl(); // he wants Explicit Value Representation
- // Little Endian is the default
- // no other value is allowed
+ // Little Endian is the default,
+ // bigendian not supported for writting
(-->SetWriteType(ExplicitVR);)
-->WriteType = ExplicitVR;
+fh->SetWriteTypeToJPEG(); // lossless compression
+fh->SetWriteTypeToJPEG2000(); // lossless compression
+
+fh->SetImageData( userPixels, userPixelsLength);
+or
+fh->SetUserData( userPixels, userPixelsLength); // this one performs compression, when required
+
fh->Write(newFileName); // overwrites the file, if any
-// or :
-fh->WriteDcmExplVR(newFileName);
-// ----------------------------- WARNING -------------------------
These lines will be moved to the document-to-be 'Developer's Guide'
WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO
PhotometricInterpretation : MONOCHROME2 (0=black), MONOCHROME2 (0=white)
-
+fh->SetImageData( userPixels, userPixelsLength);
+or
+fh->SetUserData( userPixels, userPixelsLength);
+ PixelWriteConverter->SetUserData(inData, expectedSize);
+
+
fh->SetWriteMode(WMODE_RAW / WMODE_RGB)
fh->SetWriteType( ImplicitVR/ExplicitVR/ACR/ACR_LIBIDO/JPEG/JPEG2000)
* not to deallocate its data before gdcm uses them (e.g. with
* the Write() method )
* @param inData user supplied pixel area (uint8_t* is just for the compiler.
- * user is allowed to pass any kind of pixelsn since the size is
+ * user is allowed to pass any kind of pixels since the size is
* given in bytes)
* @param expectedSize total image size, *in Bytes*
*/
void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
{
PixelWriteConverter->SetUserData(inData, expectedSize);
+ /// \todo : shouldn't we call SetCompressJPEGUserData/SetCompressJPEG2000UserData
+ /// here, too?
}
/**
*/
void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
{
+ // Shouldn't we move theese lines to FileHelper::Write()?
+/*
if( WriteType == JPEG2000 )
{
PixelWriteConverter->SetCompressJPEG2000UserData(inData, expectedSize, FileInternal);
{
PixelWriteConverter->SetUserData(inData, expectedSize);
}
+ */
+ // Just try!
+ PixelWriteConverter->SetUserData(inData, expectedSize);
}
/**
}
/**
- * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
+ * \brief Access to the underlying PixelReadConverter RGBA LUT
*/
uint8_t* FileHelper::GetLutRGBA()
{
}
/**
- * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
+ * \brief Access to the underlying PixelReadConverter RGBA LUT Item Number
*/
int FileHelper::GetLutItemNumber()
{
}
/**
- * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
+ * \brief Access to the underlying PixelReadConverter RGBA LUT Item Size
*/
int FileHelper::GetLutItemSize()
{
* @return false if write fails
*/
bool FileHelper::Write(std::string const &fileName)
-{
+{
CheckMandatoryElements(); //called once, here !
- bool flag = false;
- DocEntry *e;
switch(WriteType)
{
case ImplicitVR:
case Unknown: // should never happen; ExplicitVR is the default value
case ExplicitVR:
-
- // User should ask gdcm to write an image in Explicit VR mode
- // only when he is sure *all* the VR of *all* the DataElements is known.
- // i.e : when there are *only* Public Groups
- // or *all* the Shadow Groups are fully described in the relevant Shadow
- // Dictionnary
- // Let's just *dream* about it; *never* trust a user !
- // We turn to Implicit VR if at least the VR of one element is unknown.
- // Better we let DocEntry::WriteContent to put vr=UN for undocumented Shadow Groups !
-
-/*
- e = FileInternal->GetFirstEntry();
- while (e != 0)
- {
- if (e->GetVR() == " ")
- {
-
- SetWriteTypeToDcmImplVR();
- SetWriteFileTypeToImplicitVR();
- flag = true;
- break;
- }
- e = FileInternal->GetNextEntry();
- }
-
- if (!flag)
- {
- SetWriteFileTypeToExplicitVR();
- }
- break;
-*/
-
+ // We let DocEntry::WriteContent to put vr=UN for undocumented Shadow Groups !
SetWriteFileTypeToExplicitVR();
break;
// SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
break;
- /// \todo FIXME : JPEG may be either ExplicitVR or ImplicitVR
+ /// \todo FIXME : JPEG/JPEG2000 may be either ExplicitVR or ImplicitVR
case JPEG:
SetWriteFileTypeToJPEG();
+ // was :
+ //PixelWriteConverter->SetCompressJPEGUserData(
+ // inData, expectedSize, FileInternal);
+ PixelWriteConverter->SetCompressJPEGUserData(
+ PixelWriteConverter->GetUserData(),
+ PixelWriteConverter->GetUserDataSize(),FileInternal);
break;
case JPEG2000:
+ /// \todo Maybe we should consider doing the compression here !
+ // PixelWriteConverter->SetCompressJPEG2000UserData(inData, expectedSize, FileInternal);
+
SetWriteFileTypeToJPEG2000();
+ PixelWriteConverter->SetCompressJPEG2000UserData(
+ PixelWriteConverter->GetUserData(),
+ PixelWriteConverter->GetUserDataSize(),
+ FileInternal);
break;
}
break;
}
- bool check = CheckWriteIntegrity(); // verifies length
- if (WriteType == JPEG || WriteType == JPEG2000)
+ bool check;
+ if (WriteType == JPEG || WriteType == JPEG2000)
check = true;
+ else
+ check = CheckWriteIntegrity(); // verifies length
if (check)
{
int numberBitsAllocated = FileInternal->GetBitsAllocated();
if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
{
- gdcmWarningMacro( "numberBitsAllocated changed from "
- << numberBitsAllocated << " to 16 "
+ gdcmWarningMacro( "numberBitsAllocated changed from "
+ << numberBitsAllocated << " to 16 "
<< " for consistency purpose" );
numberBitsAllocated = 16;
}
size_t decSize = FileInternal->GetXSize()
- * FileInternal->GetYSize()
+ * FileInternal->GetYSize()
* FileInternal->GetZSize()
* FileInternal->GetTSize()
* FileInternal->GetSamplesPerPixel()
if ( FileInternal->HasLUT() )
rgbSize = decSize * 3;
+ size_t userDataSize = PixelWriteConverter->GetUserDataSize();
switch(WriteMode)
{
case WMODE_RAW :
- if ( decSize!=PixelWriteConverter->GetUserDataSize() )
+ if ( abs((long)(decSize-userDataSize))>1) // ignore padding zero
{
gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
<< decSize << " / Found :"
- << PixelWriteConverter->GetUserDataSize() );
+ << userDataSize );
return false;
}
break;
case WMODE_RGB :
- if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
+ if ( abs((long)(rgbSize-userDataSize))>1) // ignore padding zero
{
gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
- << decSize << " / Found "
- << PixelWriteConverter->GetUserDataSize() );
+ << rgbSize << " / Found "
+ << userDataSize );
return false;
}
break;
* (modifies, when necessary, photochromatic interpretation,
* bits allocated, Pixels element VR)
* WARNING : if SetPhotometricInterpretationToMonochrome1() was called
- * before Pixel Elements if modified :-(
+ * before Pixel Elements is modified :-(
*/
void FileHelper::SetWriteToRaw()
{
photInt->SetString("PALETTE COLOR ");
}
else
- {
+ {
if (GetPhotometricInterpretation() == 2)
photInt->SetString("MONOCHROME2 "); // 0 = Black
else
photInt->SetString("MONOCHROME1 "); // 0 = White !
}
-
+
PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
PixelReadConverter->GetRawSize());
vr = "OW";
if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
vr = "OB";
- // For non RAW data. Mainly JPEG
+ // For non RAW data. Mainly JPEG/JPEG2000
if( WriteType == JPEG || WriteType == JPEG2000)
{
vr = "OW";
}
-
+
DataEntry *pixel =
CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
pixel->SetBinArea(PixelWriteConverter->GetData(),false);
- pixel->SetLength(PixelWriteConverter->GetDataSize());
-
+ pixel->SetLength(
+ static_cast< uint32_t >(PixelWriteConverter->GetDataSize()) );
+
if (!FileInternal->HasLUT() && GetPhotometricInterpretation() == 1)
{
ConvertFixGreyLevels( pixel->GetBinArea(), pixel->GetLength() );
- }
+ }
Archive->Push(photInt);
Archive->Push(pixel);
PixelReadConverter->BuildRGBImage();
DataEntry *spp = CopyDataEntry(0x0028,0x0002,"US");
- spp->SetString("3 ");
+ spp->SetString("3 "); // Don't drop trailing space
DataEntry *planConfig = CopyDataEntry(0x0028,0x0006,"US");
- planConfig->SetString("0 ");
+ planConfig->SetString("0 "); // Don't drop trailing space
DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
- photInt->SetString("RGB ");
+ photInt->SetString("RGB "); // Don't drop trailing space
if ( PixelReadConverter->GetRGB() )
{
if ( oldRow && oldCol )
{
std::string rows, columns;
-
+
DataEntry *newRow=DataEntry::New(0x0028, 0x0010, "US");
DataEntry *newCol=DataEntry::New(0x0028, 0x0011, "US");
-
+
newRow->Copy(oldCol);
newCol->Copy(oldRow);
CREATED_IMAGE
-4) user modified/added some tags *without processing* the pixels (anonymization...)
UNMODIFIED_PIXELS_IMAGE
--Probabely some more to be added.
+-Probabely some more to be added.
+ --> Set it with FileHelper::SetContentType(int);
gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
--> 'Referenced SOP Instance UID' (0x0008, 0x1155)
whose value is the original 'SOP Class UID'
-3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
+3) TO DO : find a trick to allow user to pass to the writter the list of the Dicom images
or the Series, (or the Study ?) he used to created his image
(MIP, MPR, cartography image, ...)
These info should be stored (?)
// 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
}
-
- // 'Media Storage SOP Instance UID'
+
+ // 'Media Storage SOP Instance UID'
CopyMandatoryEntry(0x0002,0x0003,sop,"UI");
-
+
// 'Implementation Class UID'
// FIXME : in all examples we have, 0x0002,0x0012 is not so long :
// seems to be Root UID + 4 digits (?)
// --------------------- For DataSet ---------------------
+ // check whether 0018|0015 [CS] [Body Part Examined] value is UPPER CASE
+ // (avoid dciodvfy to complain!)
+ DataEntry *e_0018_0015 = FileInternal->GetDataEntry(0x0018, 0x0015);
+ if ( e_0018_0015)
+ {
+ std::string bodyPartExamined = e_0018_0015->GetString();
+ std::transform(bodyPartExamined.begin(), bodyPartExamined.end(), bodyPartExamined.begin(),
+ (int(*)(int)) toupper);
+ CopyMandatoryEntry(0x0018,0x0015,bodyPartExamined,"CS");
+ }
+
if ( ContentType != USER_OWN_IMAGE) // when it's not a user made image
{
-
- gdcmDebugMacro( "USER_OWN_IMAGE (1)");
- // If 'SOP Class UID' exists ('true DICOM' image)
+ // If 'SOP Class UID' and 'SOP Instance UID' exist ('true DICOM' image)
// we create the 'Source Image Sequence' SeqEntry
// to hold informations about the Source Image
-
+
+ // 'SOP Instance UID'
DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
- if ( e_0008_0016 )
+ //
+ DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
+ if ( e_0008_0016 && e_0008_0018)
{
- // Create 'Source Image Sequence' SeqEntry
-// SeqEntry *sis = SeqEntry::New (
-// Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
- SeqEntry *sis = SeqEntry::New (0x0008, 0x2112);
- SQItem *sqi = SQItem::New(1);
- // (we assume 'SOP Instance UID' exists too)
- // create 'Referenced SOP Class UID'
-// DataEntry *e_0008_1150 = DataEntry::New(
-// Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
- DataEntry *e_0008_1150 = DataEntry::New(0x0008, 0x1150, "UI");
- e_0008_1150->SetString( e_0008_0016->GetString());
- sqi->AddEntry(e_0008_1150);
- e_0008_1150->Delete();
+ // Create 'Source Image Sequence' SeqEntry
+ SeqEntry *sis = SeqEntry::New (0x0008, 0x2112);
+ SQItem *sqi = SQItem::New(1);
- // create 'Referenced SOP Instance UID'
- DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
-// DataEntry *e_0008_1155 = DataEntry::New(
-// Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
- DataEntry *e_0008_1155 = DataEntry::New(0x0008, 0x1155, "UI");
- e_0008_1155->SetString( e_0008_0018->GetString());
- sqi->AddEntry(e_0008_1155);
- e_0008_1155->Delete();
-
- sis->AddSQItem(sqi,1);
- sqi->Delete();
-
- // temporarily replaces any previous 'Source Image Sequence'
- Archive->Push(sis);
- sis->Delete();
-
- // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
- if ( ContentType == FILTERED_IMAGE)
- // the user *knows* he just modified the pixels
- // the image is no longer an 'Original' one
- CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
+ // create 'Referenced SOP Class UID' from 'SOP Class UID'
+
+ DataEntry *e_0008_1150 = DataEntry::New(0x0008, 0x1150, "UI");
+ e_0008_1150->SetString( e_0008_0016->GetString());
+ sqi->AddEntry(e_0008_1150);
+ e_0008_1150->Delete();
+
+ // create 'Referenced SOP Instance UID' from 'SOP Instance UID'
+ // DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
+
+ DataEntry *e_0008_1155 = DataEntry::New(0x0008, 0x1155, "UI");
+ e_0008_1155->SetString( e_0008_0018->GetString());
+ sqi->AddEntry(e_0008_1155);
+ e_0008_1155->Delete();
+
+ sis->AddSQItem(sqi,1);
+ sqi->Delete();
+
+ // temporarily replaces any previous 'Source Image Sequence'
+ Archive->Push(sis);
+ sis->Delete();
+ // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
+
+ if ( ContentType == FILTERED_IMAGE) // the user *knows* he just modified the pixels
+ {
+ DataEntry *e_0008_0008 = FileInternal->GetDataEntry(0x0008, 0x0008);
+ if ( e_0008_0008)
+ {
+ std::string imageType = e_0008_0008->GetString();
+ std::string::size_type p = imageType.find("ORIGINAL");
+ if (p == 0) // image is ORIGINAL one
+ {
+ // the image is no longer an 'Original' one
+ CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
+ }
+ // if Image Type was not ORIGINAL\..., we keep it.
+ }
+ else // 0008_0008 was missing, wee add it.
+ {
+ CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
+ }
+ }
}
}
Archive->Push(0x0028,0x0017);
Archive->Push(0x0028,0x0198); // very old versions
Archive->Push(0x0028,0x0199);
-
+
// Replace deprecated 0028 0012 US Planes
// by new 0028 0008 IS Number of Frames
-
+
///\todo : find if there is a rule!
DataEntry *e_0028_0012 = FileInternal->GetDataEntry(0x0028, 0x0012);
if ( e_0028_0012 )
std::ostringstream s;
// check 'Bits Allocated' vs decent values
int nbBitsAllocated = FileInternal->GetBitsAllocated();
- if ( nbBitsAllocated == 0 || nbBitsAllocated > 32
- || ( nbBitsAllocated > 8 && nbBitsAllocated <16) )
+ if ( (nbBitsAllocated == 0 || nbBitsAllocated > 32)
+ || ( nbBitsAllocated > 8 && nbBitsAllocated <16) )
{
CopyMandatoryEntry(0x0028,0x0100,"16","US");
gdcmWarningMacro("(0028,0100) changed from "
}
// check Pixel Representation (default it as 0 -unsigned-)
-
+
DataEntry *e_0028_0103 = FileInternal->GetDataEntry(0x0028, 0x0103);
if ( !e_0028_0103 )
{
}
}
- std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
- if ( pixelSpacing == GDCM_UNFOUND )
+ std::string pixelAspectRatio = FileInternal->GetEntryString(0x0028,0x0034);
+ if ( pixelAspectRatio == GDCM_UNFOUND ) // avoid conflict with pixelSpacing !
{
- pixelSpacing = "1.0\\1.0";
- // if missing, Pixel Spacing forced to "1.0\1.0"
- CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS");
- }
-
- // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
- // --> This one is the *legal* one !
- if ( ContentType != USER_OWN_IMAGE)
- // we write it only when we are *sure* the image comes from
- // an imager (see also 0008,0x0064)
- CheckMandatoryEntry(0x0018,0x1164,pixelSpacing,"DS");
-
+ std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
+ if ( pixelSpacing == GDCM_UNFOUND )
+ {
+ pixelSpacing = "1.0\\1.0";
+ // if missing, Pixel Spacing forced to "1.0\1.0"
+ CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS");
+ }
+
+ // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
+ // --> This one is the *legal* one !
+ if ( ContentType != USER_OWN_IMAGE)
+ // we write it only when we are *sure* the image comes from
+ // an imager (see also 0008,0x0064)
+ CheckMandatoryEntry(0x0018,0x1164,pixelSpacing,"DS");
+ }
/*
///Exact meaning of RETired fields
// See page 73 of ACR-NEMA_300-1988.pdf !
-// 0020,0020 : Patient Orientation :
+// 0020,0020 : Patient Orientation :
Patient direction of the first row and
column of the images. The first entry id the direction of the raws, given by the
direction of the last pixel in the first row from the first pixel in tha row.
the image system with respect to the same axes
//0020,0050 Location
-An image location reference, standard for the modality (such as CT bed
-position), used to indicate position. Calculation of position for other purposes
+An image location reference, standard for the modality (such as CT bed position),
+used to indicate position. Calculation of position for other purposes
is only from (0020,0030) and (0020,0035)
*/
-
+
/*
// if imagePositionPatient not found, default it with imagePositionRet, if any
// if imageOrientationPatient not found, default it with imageOrientationRet, if any
std::string imageOrientationRet = FileInternal->GetEntryString(0x0020,0x0035);
std::string imagePositionPatient = FileInternal->GetEntryString(0x0020,0x0032);
std::string imageOrientationPatient = FileInternal->GetEntryString(0x0020,0x0037);
-
+
if( imagePositionPatient == GDCM_UNFOUND && imageOrientationPatient == GDCM_UNFOUND
&& imagePositionRet != GDCM_UNFOUND && imageOrientationRet != GDCM_UNFOUND)
{
}
*/
- // Samples Per Pixel (type 1) : default to grayscale
+ // Samples Per Pixel (type 1) : default to grayscale
CheckMandatoryEntry(0x0028,0x0002,"1","US");
// --- Check UID-related Entries ---
if ( ContentType == USER_OWN_IMAGE)
{
- gdcmDebugMacro( "USER_OWN_IMAGE (2)");
+ gdcmDebugMacro( "USER_OWN_IMAGE (2)");
// Conversion Type.
// Other possible values are :
// See PS 3.3, Page 408
-
+
// DV = Digitized Video
// DI = Digital Interface
// DF = Digitized Film
// SI = Scanned Image
// DRW = Drawing
// SYN = Synthetic Image
-
+
CheckMandatoryEntry(0x0008,0x0064,"SYN","CS"); // Why not?
}
/*
}
*/
-
+
// ---- The user will never have to take any action on the following ----
// new value for 'SOP Instance UID'
/*
// Deal with element 0x0000 (group length) of each group.
// First stage : get all the different Groups
-
+
GroupHT grHT;
DocEntry *d = FileInternal->GetFirstEntry();
while(d)
}
}
-/// \todo : what is it used for ? (FileHelper::SetMandatoryEntry)
+/// \todo : what is it used for ? (FileHelper::SetMandatoryEntry)
void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
{
//DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
PixelReadConverter = new PixelReadConvert;
PixelWriteConverter = new PixelWriteConvert;
Archive = new DocEntryArchive( FileInternal );
+
+ KeepOverlays = false;
}
/**
/* Probabely something to be added to use Rescale Slope/Intercept
-Have a look ,at ITK code !
+Have a look at ITK code !
// Internal function to rescale pixel according to Rescale Slope/Intercept
template<class TBuffer, class TSource>