X-Git-Url: https://git.creatis.insa-lyon.fr/pubgit/?a=blobdiff_plain;f=src%2FgdcmFileHelper.cxx;h=c1e1c31147701558d0549b335447a63606a52924;hb=fa5cf7cf67a3ea643dae69394ac8df512a4d4d2e;hp=43f6b89be5063d15293831715ba23f98d0256b1e;hpb=c094e185dd6404df031524ccae8e1b51e3b84871;p=gdcm.git diff --git a/src/gdcmFileHelper.cxx b/src/gdcmFileHelper.cxx index 43f6b89b..c1e1c311 100644 --- a/src/gdcmFileHelper.cxx +++ b/src/gdcmFileHelper.cxx @@ -3,8 +3,9 @@ Program: gdcm Module: $RCSfile: gdcmFileHelper.cxx,v $ Language: C++ - Date: $Date: 2005/01/21 11:40:55 $ - Version: $Revision: 1.2 $ + + Date: $Date: 2005/03/04 09:45:04 $ + Version: $Revision: 1.25 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -23,17 +24,86 @@ #include "gdcmDebug.h" #include "gdcmUtil.h" #include "gdcmBinEntry.h" +#include "gdcmValEntry.h" +#include "gdcmSeqEntry.h" +#include "gdcmSQItem.h" +#include "gdcmContentEntry.h" #include "gdcmFile.h" #include "gdcmPixelReadConvert.h" #include "gdcmPixelWriteConvert.h" #include "gdcmDocEntryArchive.h" +#include "gdcmDictSet.h" #include +/* +// ----------------------------- WARNING ------------------------- + +These lines will be moved to the document-to-be 'User's Guide' + +// To read an image, user needs a gdcm::File +gdcm::File *f1 = new gdcm::File(fileName); +// user can now check some values +std::string v = f1->GetEntryValue(groupNb,ElementNb); +// to get the pixels, user needs a gdcm::FileHelper +gdcm::FileHelper *fh1 = new gdcm::FileHelper(f1); +// user may ask not to convert Palette to RGB +uint8_t *pixels = fh1->GetImageDataRaw(); +int imageLength = fh1->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 + +fh1->SetImageData( userPixels, userPixelsLength); +fh1->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB + // (WriteMode is set) + +fh1->SetWriteTypeToDcmExpl(); // he wants Explicit Value Representation + // Little Endian is the default + // no other value is allowed + (-->SetWriteType(ExplicitVR);) + -->WriteType = ExplicitVR; +fh1->Write(newFileName); // overwrites the file, if any + +// or : +fh1->WriteDcmExplVR(newFileName); + + +// ----------------------------- WARNING ------------------------- + +These lines will be moved to the document-to-be 'Developer's Guide' + +WriteMode : WMODE_RAW / WMODE_RGB +WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO + +fh1->Write(newFileName); + SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR(); + (modifies TransferSyntax) + SetWriteToRaw(); / SetWriteToRGB(); + (modifies, when necessary : photochromatic interpretation, + samples per pixel, Planar configuration, + bits allocated, bits stored, high bit -ACR 24 bits- + Pixels element VR, pushes out the LUT ) + CheckWriteIntegrity(); + (checks user given pixels length) + FileInternal->Write(fileName,WriteType) + fp = opens file(fileName); + ComputeGroup0002Length(writetype); + BitsAllocated 12->16 + RemoveEntryNoDestroy(palettes, etc) + Document::WriteContent(fp, writetype); + RestoreWrite(); + (moves back to the File all the archived elements) + RestoreWriteFileType(); + (pushes back group 0002, with TransferSyntax) +*/ + + + + namespace gdcm { -typedef std::pair IterHT; - //------------------------------------------------------------------------- // Constructor / Destructor /** @@ -88,7 +158,7 @@ FileHelper::FileHelper(File *header) * seen as a side effect). * @param filename file to be opened for parsing */ -FileHelper::FileHelper(std::string const & filename ) +FileHelper::FileHelper(std::string const &filename ) { FileInternal = new File( filename ); SelfHeader = true; @@ -123,18 +193,84 @@ FileHelper::~FileHelper() } //----------------------------------------------------------------------------- -// Print -void FileHelper::Print(std::ostream &os, std::string const &) +// Public +/** + * \brief Accesses an existing DocEntry (i.e. a Dicom Element) + * through it's (group, element) and modifies it's content with + * the given value. + * @param content new value (string) to substitute with + * @param group group number of the Dicom Element to modify + * @param elem element number of the Dicom Element to modify + * \return false if DocEntry not found + */ +bool FileHelper::SetValEntry(std::string const &content, + uint16_t group, uint16_t elem) +{ + return FileInternal->SetValEntry(content, group, elem); +} + + +/** + * \brief Accesses an existing DocEntry (i.e. a Dicom Element) + * through it's (group, element) and modifies it's content with + * the given value. + * @param content new value (void* -> uint8_t*) to substitute with + * @param lgth new value length + * @param group group number of the Dicom Element to modify + * @param elem element number of the Dicom Element to modify + * \return false if DocEntry not found + */ +bool FileHelper::SetBinEntry(uint8_t *content, int lgth, + uint16_t group, uint16_t elem) { - FileInternal->SetPrintLevel(PrintLevel); - FileInternal->Print(os); + return FileInternal->SetBinEntry(content, lgth, group, elem); +} - PixelReadConverter->SetPrintLevel(PrintLevel); - PixelReadConverter->Print(os); +/** + * \brief Modifies the value of a given DocEntry (Dicom entry) + * when it exists. Creates it with the given value when unexistant. + * @param content (string)value to be set + * @param group Group number of the Entry + * @param elem Element number of the Entry + * \return pointer to the modified/created Dicom entry (NULL when creation + * failed). + */ +ValEntry *FileHelper::InsertValEntry(std::string const &content, + uint16_t group, uint16_t elem) +{ + return FileInternal->InsertValEntry(content,group,elem); +} + +/** + * \brief Modifies the value of a given DocEntry (Dicom entry) + * when it exists. Creates it with the given value when unexistant. + * A copy of the binArea is made to be kept in the Document. + * @param binArea (binary)value to be set + * @param lgth new value length + * @param group Group number of the Entry + * @param elem Element number of the Entry + * \return pointer to the modified/created Dicom entry (NULL when creation + * failed). + */ +BinEntry *FileHelper::InsertBinEntry(uint8_t *binArea, int lgth, + uint16_t group, uint16_t elem) +{ + return FileInternal->InsertBinEntry(binArea, lgth, group, elem); +} + +/** + * \brief Modifies the value of a given DocEntry (Dicom entry) + * when it exists. Creates it, empty (?!) when unexistant. + * @param group Group number of the Entry + * @param elem Element number of the Entry + * \return pointer to the modified/created Dicom entry (NULL when creation + * failed). + */ +SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem) +{ + return FileInternal->InsertSeqEntry(group, elem); } -//----------------------------------------------------------------------------- -// Public /** * \brief Get the size of the image data * If the image can be RGB (with a lut or by default), the size @@ -171,9 +307,9 @@ size_t FileHelper::GetImageDataRawSize() } /** - * \brief - Allocates necessary memory, + * \brief - Allocates necessary memory, * - Reads the pixels from disk (uncompress if necessary), - * - Transforms YBR pixels, if any, into RGB pixels + * - Transforms YBR pixels, if any, into RGB pixels, * - Transforms 3 planes R, G, B, if any, into a single RGB Plane * - Transforms single Grey plane + 3 Palettes into a RGB Plane * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone. @@ -211,7 +347,7 @@ uint8_t *FileHelper::GetImageData() * Copies the pixel data (image[s]/volume[s]) to newly allocated zone. * DOES NOT transform Grey plane + 3 Palettes into a RGB Plane * @return Pointer to newly allocated pixel data. - * \ NULL if alloc fails + * NULL if alloc fails */ uint8_t *FileHelper::GetImageDataRaw () { @@ -255,7 +391,7 @@ size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize) { if ( PixelReadConverter->GetRGBSize() > maxSize ) { - gdcmVerboseMacro( "Pixel data bigger than caller's expected MaxSize"); + gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize"); return 0; } memcpy( destination, @@ -267,7 +403,7 @@ size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize) // Either no LUT conversion necessary or LUT conversion failed if ( PixelReadConverter->GetRawSize() > maxSize ) { - gdcmVerboseMacro( "Pixel data bigger than caller's expected MaxSize"); + gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize"); return 0; } memcpy( destination, @@ -282,30 +418,34 @@ size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize) * 'image' Pixels are presented as C-like 2D arrays : line per line. * 'volume'Pixels are presented as C-like 3D arrays : plane per plane * \warning Since the pixels are not copied, it is the caller's responsability - * not to deallocate it's data before gdcm uses them (e.g. with - * the Write() method. - * @param inData user supplied pixel area - * @param expectedSize total image size, in Bytes - * - * @return boolean + * 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 + * given in bytes) + * @param expectedSize total image size, *in Bytes* */ void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize) { - SetUserData(inData,expectedSize); + SetUserData(inData, expectedSize); } /** - * \brief Set the image datas defined by the user - * \warning When writting the file, this datas are get as default datas to write + * \brief Set the image data defined by the user + * \warning When writting the file, this data are get as default data to write + * @param inData user supplied pixel area (uint8_t* is just for the compiler. + * 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::SetUserData(uint8_t *data, size_t expectedSize) +void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize) { - PixelWriteConverter->SetUserData(data,expectedSize); + PixelWriteConverter->SetUserData(inData, expectedSize); } /** - * \brief Get the image datas defined by the user - * \warning When writting the file, this datas are get as default data to write + * \brief Get the image data defined by the user + * \warning When writting the file, this data are get as default data to write */ uint8_t *FileHelper::GetUserData() { @@ -314,7 +454,7 @@ uint8_t *FileHelper::GetUserData() /** * \brief Get the image data size defined by the user - * \warning When writting the file, this datas are get as default data to write + * \warning When writting the file, this data are get as default data to write */ size_t FileHelper::GetUserDataSize() { @@ -322,7 +462,7 @@ size_t FileHelper::GetUserDataSize() } /** - * \brief Get the image datas from the file. + * \brief Get the image data from the file. * If a LUT is found, the data are expanded to be RGB */ uint8_t *FileHelper::GetRGBData() @@ -340,8 +480,8 @@ size_t FileHelper::GetRGBDataSize() } /** - * \brief Get the image datas from the file. - * If a LUT is found, the datas are not expanded ! + * \brief Get the image data from the file. + * Even when a LUT is found, the data are not expanded to RGB! */ uint8_t *FileHelper::GetRawData() { @@ -350,13 +490,21 @@ uint8_t *FileHelper::GetRawData() /** * \brief Get the image data size from the file. - * If a LUT is found, the data are not expanded ! + * Even when a LUT is found, the data are not expanded to RGB! */ size_t FileHelper::GetRawDataSize() { return PixelReadConverter->GetRawSize(); } +/** + * \brief Access to the underlying \ref PixelReadConverter RGBA LUT + */ +uint8_t* FileHelper::GetLutRGBA() +{ + return PixelReadConverter->GetLutRGBA(); +} + /** * \brief Writes on disk A SINGLE Dicom file * NO test is performed on processor "Endiannity". @@ -365,13 +513,12 @@ size_t FileHelper::GetRawDataSize() * (any already existing file is over written) * @return false if write fails */ - bool FileHelper::WriteRawData(std::string const &fileName) { - std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary ); + std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary ); if (!fp1) { - gdcmVerboseMacro( "Fail to open (write) file:" << fileName.c_str()); + gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str()); return false; } @@ -462,9 +609,11 @@ bool FileHelper::Write(std::string const &fileName) { case ImplicitVR: SetWriteFileTypeToImplicitVR(); + CheckMandatoryElements(); break; case ExplicitVR: SetWriteFileTypeToExplicitVR(); + CheckMandatoryElements(); break; case ACR: case ACR_LIBIDO: @@ -472,6 +621,7 @@ bool FileHelper::Write(std::string const &fileName) break; default: SetWriteFileTypeToExplicitVR(); + CheckMandatoryElements(); } // -------------------------------------------------------------- @@ -495,14 +645,14 @@ bool FileHelper::Write(std::string const &fileName) switch(WriteMode) { case WMODE_RAW : - SetWriteToRaw(); + SetWriteToRaw(); // modifies and pushes to the archive, when necessary break; case WMODE_RGB : - SetWriteToRGB(); + SetWriteToRGB(); // modifies and pushes to the archive, when necessary break; } - bool check = CheckWriteIntegrity(); + bool check = CheckWriteIntegrity(); // verifies length if(check) { check = FileInternal->Write(fileName,WriteType); @@ -522,85 +672,15 @@ bool FileHelper::Write(std::string const &fileName) return check; } -/** - * \brief Accesses an existing DocEntry (i.e. a Dicom Element) - * through it's (group, element) and modifies it's content with - * the given value. - * @param content new value (string) to substitute with - * @param group group number of the Dicom Element to modify - * @param elem element number of the Dicom Element to modify - */ -bool FileHelper::SetEntry(std::string const &content, - uint16_t group, uint16_t elem) -{ - return FileInternal->SetEntry(content,group,elem); -} - - -/** - * \brief Accesses an existing DocEntry (i.e. a Dicom Element) - * through it's (group, element) and modifies it's content with - * the given value. - * @param content new value (void* -> uint8_t*) to substitute with - * @param lgth new value length - * @param group group number of the Dicom Element to modify - * @param elem element number of the Dicom Element to modify - */ -bool FileHelper::SetEntry(uint8_t *content, int lgth, - uint16_t group, uint16_t elem) -{ - return FileInternal->SetEntry(content,lgth,group,elem); -} - -/** - * \brief Modifies the value of a given DocEntry (Dicom entry) - * when it exists. Create it with the given value when unexistant. - * @param content (string) Value to be set - * @param group Group number of the Entry - * @param elem Element number of the Entry - * \return pointer to the modified/created Dicom entry (NULL when creation - * failed). - */ -bool FileHelper::ReplaceOrCreate(std::string const &content, - uint16_t group, uint16_t elem) -{ - return FileInternal->ReplaceOrCreate(content,group,elem) != NULL; -} - -/* - * \brief Modifies the value of a given DocEntry (Dicom entry) - * when it exists. Create it with the given value when unexistant. - * A copy of the binArea is made to be kept in the Document. - * @param binArea (binary) value to be set - * @param group Group number of the Entry - * @param elem Element number of the Entry - * \return pointer to the modified/created Dicom entry (NULL when creation - * failed). - */ -bool FileHelper::ReplaceOrCreate(uint8_t *binArea, int lgth, - uint16_t group, uint16_t elem) -{ - return FileInternal->ReplaceOrCreate(binArea,lgth,group,elem) != NULL; -} - -/** - * \brief Access to the underlying \ref PixelReadConverter RGBA LUT - */ -uint8_t* FileHelper::GetLutRGBA() -{ - return PixelReadConverter->GetLutRGBA(); -} - //----------------------------------------------------------------------------- // Protected - /** - * \brief Check the write integrity + * \brief Checks the write integrity * * The tests made are : * - verify the size of the image to write with the possible write * when the user set an image data - * @return true if the check successfulls + * @return true if check is successfull */ bool FileHelper::CheckWriteIntegrity() { @@ -626,16 +706,18 @@ bool FileHelper::CheckWriteIntegrity() case WMODE_RAW : if( decSize!=PixelWriteConverter->GetUserDataSize() ) { - gdcmVerboseMacro( "Data size is incorrect (Raw)" << decSize - << " / " << PixelWriteConverter->GetUserDataSize() ); + gdcmWarningMacro( "Data size (Raw) is incorrect. Should be " + << decSize << " / Found :" + << PixelWriteConverter->GetUserDataSize() ); return false; } break; case WMODE_RGB : if( rgbSize!=PixelWriteConverter->GetUserDataSize() ) { - gdcmVerboseMacro( "Data size is incorrect (RGB)" << decSize - << " / " << PixelWriteConverter->GetUserDataSize() ); + gdcmWarningMacro( "Data size (RGB) is incorrect. Should be " + << decSize << " / Found " + << PixelWriteConverter->GetUserDataSize() ); return false; } break; @@ -646,7 +728,9 @@ bool FileHelper::CheckWriteIntegrity() } /** - * \brief + * \brief Updates the File to write RAW data (as opposed to RGB data) + * (modifies, when necessary, photochromatic interpretation, + * bits allocated, Pixels element VR) */ void FileHelper::SetWriteToRaw() { @@ -664,14 +748,19 @@ void FileHelper::SetWriteToRaw() } else { - photInt->SetValue("MONOCHROME1 "); + photInt->SetValue("MONOCHROME2 "); } PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(), PixelReadConverter->GetRawSize()); + std::string vr = "OB"; + if( FileInternal->GetBitsAllocated()>8 ) + vr = "OW"; + if( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files + vr = "OB"; BinEntry *pixel = - CopyBinEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel()); + CopyBinEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr); pixel->SetValue(GDCM_BINLOADED); pixel->SetBinArea(PixelWriteConverter->GetData(),false); pixel->SetLength(PixelWriteConverter->GetDataSize()); @@ -682,7 +771,11 @@ void FileHelper::SetWriteToRaw() } /** - * \brief + * \brief Updates the File to write RGB data (as opposed to RAW data) + * (modifies, when necessary, photochromatic interpretation, + * samples per pixel, Planar configuration, + * bits allocated, bits stored, high bit -ACR 24 bits- + * Pixels element VR, pushes out the LUT, ) */ void FileHelper::SetWriteToRGB() { @@ -710,8 +803,13 @@ void FileHelper::SetWriteToRGB() PixelReadConverter->GetRawSize()); } + std::string vr = "OB"; + if( FileInternal->GetBitsAllocated()>8 ) + vr = "OW"; + if( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files + vr = "OB"; BinEntry *pixel = - CopyBinEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel()); + CopyBinEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr); pixel->SetValue(GDCM_BINLOADED); pixel->SetBinArea(PixelWriteConverter->GetData(),false); pixel->SetLength(PixelWriteConverter->GetDataSize()); @@ -729,7 +827,7 @@ void FileHelper::SetWriteToRGB() Archive->Push(0x0028,0x1202); Archive->Push(0x0028,0x1203); - // For old ACR-NEMA + // For old '24 Bits' ACR-NEMA // Thus, we have a RGB image and the bits allocated = 24 and // samples per pixels = 1 (in the read file) if(FileInternal->GetBitsAllocated()==24) @@ -755,7 +853,7 @@ void FileHelper::SetWriteToRGB() } /** - * \brief + * \brief Restore the File write mode */ void FileHelper::RestoreWrite() { @@ -776,18 +874,43 @@ void FileHelper::RestoreWrite() Archive->Restore(0x0028,0x1201); Archive->Restore(0x0028,0x1202); Archive->Restore(0x0028,0x1203); + + // group 0002 may be pushed out for ACR-NEMA writting purposes + Archive->Restore(0x0002,0x0000); + Archive->Restore(0x0002,0x0001); + Archive->Restore(0x0002,0x0002); + Archive->Restore(0x0002,0x0003); + Archive->Restore(0x0002,0x0010); + Archive->Restore(0x0002,0x0012); + Archive->Restore(0x0002,0x0013); + Archive->Restore(0x0002,0x0016); + Archive->Restore(0x0002,0x0100); + Archive->Restore(0x0002,0x0102); } /** - * \brief + * \brief Pushes out the whole group 0002 + * FIXME : better, set a flag to tell the writer not to write it ... + * FIXME : method should probably have an other name ! + * SetWriteFileTypeToACR is NOT opposed to + * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR */ void FileHelper::SetWriteFileTypeToACR() { + Archive->Push(0x0002,0x0000); + Archive->Push(0x0002,0x0001); + Archive->Push(0x0002,0x0002); + Archive->Push(0x0002,0x0003); Archive->Push(0x0002,0x0010); + Archive->Push(0x0002,0x0012); + Archive->Push(0x0002,0x0013); + Archive->Push(0x0002,0x0016); + Archive->Push(0x0002,0x0100); + Archive->Push(0x0002,0x0102); } /** - * \brief + * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian" */ void FileHelper::SetWriteFileTypeToExplicitVR() { @@ -801,7 +924,7 @@ void FileHelper::SetWriteFileTypeToExplicitVR() } /** - * \brief + * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian" */ void FileHelper::SetWriteFileTypeToImplicitVR() { @@ -816,13 +939,26 @@ void FileHelper::SetWriteFileTypeToImplicitVR() /** - * \brief + * \brief Restore in the File the initial group 0002 */ void FileHelper::RestoreWriteFileType() { + // group 0002 may be pushed out for ACR-NEMA writting purposes + Archive->Restore(0x0002,0x0000); + Archive->Restore(0x0002,0x0001); + Archive->Restore(0x0002,0x0002); + Archive->Restore(0x0002,0x0003); Archive->Restore(0x0002,0x0010); + Archive->Restore(0x0002,0x0012); + Archive->Restore(0x0002,0x0013); + Archive->Restore(0x0002,0x0016); + Archive->Restore(0x0002,0x0100); + Archive->Restore(0x0002,0x0102); } +/** + * \brief Set the Write not to Libido format + */ void FileHelper::SetWriteToLibido() { ValEntry *oldRow = dynamic_cast @@ -853,7 +989,7 @@ void FileHelper::SetWriteToLibido() } /** - * \brief + * \brief Set the Write not to No Libido format */ void FileHelper::SetWriteToNoLibido() { @@ -871,7 +1007,7 @@ void FileHelper::SetWriteToNoLibido() } /** - * \brief + * \brief Restore the Write format */ void FileHelper::RestoreWriteOfLibido() { @@ -880,53 +1016,352 @@ void FileHelper::RestoreWriteOfLibido() Archive->Restore(0x0008,0x0010); } -ValEntry *FileHelper::CopyValEntry(uint16_t group,uint16_t elem) +/** + * \brief Duplicates a ValEntry or creates it. + * @param group Group number of the Entry + * @param elem Element number of the Entry + * \return pointer to the new Val Entry (NULL when creation failed). + */ +ValEntry *FileHelper::CopyValEntry(uint16_t group, uint16_t elem) { DocEntry *oldE = FileInternal->GetDocEntry(group, elem); ValEntry *newE; - if(oldE) + if( oldE ) { newE = new ValEntry(oldE->GetDictEntry()); newE->Copy(oldE); } else { - newE = GetFile()->NewValEntry(group,elem); + newE = GetFile()->NewValEntry(group, elem); } return newE; } /** - * \brief Modifies the value of a given Bin Entry (Dicom Element) - * when it exists. Create it with the given value when unexistant. - * @param content (string) Value to be set + * \brief Duplicates a BinEntry or creates it. * @param group Group number of the Entry * @param elem Element number of the Entry - * \return pointer to the modified/created Bin Entry (NULL when creation - * failed). + * @param vr Value Representation of the Entry + * FIXME : what is it used for? + * \return pointer to the new Bin Entry (NULL when creation failed). */ -BinEntry *FileHelper::CopyBinEntry(uint16_t group,uint16_t elem) +BinEntry *FileHelper::CopyBinEntry(uint16_t group, uint16_t elem, + const std::string &vr) { DocEntry *oldE = FileInternal->GetDocEntry(group, elem); BinEntry *newE; - if(oldE) + if( oldE ) + if( oldE->GetVR()!=vr ) + oldE = NULL; + + if( oldE ) { newE = new BinEntry(oldE->GetDictEntry()); newE->Copy(oldE); } else { - newE = GetFile()->NewBinEntry(group,elem); + newE = GetFile()->NewBinEntry(group, elem, vr); } return newE; } +/** + * \brief This method is called automatically, just before writting + * in order to produce a 'True Dicom V3' image + * We cannot know *how* the user made the File (reading an old ACR-NEMA + * file or a not very clean DICOM file ...) + * + * Just before writting : + * - we check the Entries + * - we create the mandatory entries if they are missing + * - we modify the values if necessary + * - we push the sensitive entries to the Archive + * The writing process will restore the entries as they where before + * entering FileHelper::CheckMandatoryElements, so the user will always + * see the entries just as he left them. + * + * \todo : - warn the user if we had to add some entries : + * even if a mandatory entry is missing, we add it, with a default value + * (we don't want to give up the writting process if user forgot to + * specify Lena's Patient ID, for instance ...) + * - read the whole PS 3.3 Part of DICOM (890 pages) + * and write a *full* checker (probably one method per Modality ...) + * Any contribution is welcome. + * - write a user callable full checker, to allow post reading + * and/or pre writting image consistency check. + */ + +void FileHelper::CheckMandatoryElements() +{ + // just to remember : 'official' 0002 group + + //0002 0000 UL 1 Meta Group Length + //0002 0001 OB 1 File Meta Information Version + //0002 0002 UI 1 Media Stored SOP Class UID + //0002 0003 UI 1 Media Stored SOP Instance UID + //0002 0010 UI 1 Transfer Syntax UID + //0002 0012 UI 1 Implementation Class UID + //0002 0013 SH 1 Implementation Version Name + //0002 0016 AE 1 Source Application Entity Title + //0002 0100 UI 1 Private Information Creator + //0002 0102 OB 1 Private Information + + // Create them if not found + // Always modify the value + // Push the entries to the archive. + + ValEntry *e_0002_0000 = CopyValEntry(0x0002,0x0000); + e_0002_0000->SetValue("0"); // for the moment + Archive->Push(e_0002_0000); + + BinEntry *e_0002_0001 = CopyBinEntry(0x0002,0x0001, "OB"); + e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(), + false); + e_0002_0001->SetLength(2); + + ValEntry *e_0002_0002 = CopyValEntry(0x0002,0x0002); + // [Secondary Capture Image Storage] + e_0002_0002->SetValue("1.2.840.10008.5.1.4.1.1.7"); + Archive->Push(e_0002_0002); + + // 'Media Stored SOP Instance UID' + ValEntry *e_0002_0003 = CopyValEntry(0x0002,0x0003); + e_0002_0003->SetValue(Util::CreateUniqueUID()); + Archive->Push(e_0002_0003); + + ValEntry *e_0002_0010 = CopyValEntry(0x0002,0x0010); + //[Explicit VR - Little Endian] + e_0002_0010->SetValue("1.2.840.10008.1.2.1"); + Archive->Push(e_0002_0010); + + // 'Implementation Class UID' + ValEntry *e_0002_0012 = CopyValEntry(0x0002,0x0012); + e_0002_0012->SetValue(Util::CreateUniqueUID()); + Archive->Push(e_0002_0012); + + // 'Implementation Version Name' + ValEntry *e_0002_0013 = CopyValEntry(0x0002,0x0013); + e_0002_0013->SetValue("GDCM 1.0"); + Archive->Push(e_0002_0013); + + //'Source Application Entity Title' Not Mandatory + //ValEntry *e_0002_0016 = CopyValEntry(0x0002,0x0016); + // e_0002_0016->SetValue("1.2.840.10008.5.1.4.1.1.7"); + // Archive->Push(e_0002_0016); + + // Push out 'LibIDO-special' entries, if any + Archive->Push(0x0028,0x0015); + Archive->Push(0x0028,0x0016); + Archive->Push(0x0028,0x0017); + Archive->Push(0x0028,0x00199); + + // --- Check UID-related Entries --- + + // If 'SOP Class UID' exists ('true DICOM' image) + // we create the 'Source Image Sequence' SeqEntry + // to hold informations about the Source Image + + ValEntry *e_0008_0016 = FileInternal->GetValEntry(0x0008, 0x0016); + if ( e_0008_0016 != 0 ) + { + // Create 'Source Image Sequence' SeqEntry + SeqEntry *s = new SeqEntry ( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) ); + SQItem *sqi = new SQItem(1); + // (we assume 'SOP Instance UID' exists too) + // create 'Referenced SOP Class UID' + ValEntry *e_0008_1150 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) ); + e_0008_1150->SetValue( e_0008_0016->GetValue()); + sqi->AddEntry(e_0008_1150); + + // create 'Referenced SOP Instance UID' + ValEntry *e_0008_0018 = FileInternal->GetValEntry(0x0008, 0x0018); + ValEntry *e_0008_1155 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) ); + e_0008_1155->SetValue( e_0008_0018->GetValue()); + sqi->AddEntry(e_0008_1155); + + s->AddSQItem(sqi,1); + // temporarily replaces any previous 'Source Image Sequence' + Archive->Push(s); + + // 'Image Type' (The written image is no longer an 'ORIGINAL' one) + ValEntry *e_0008_0008 = CopyValEntry(0x0008,0x0008); + e_0008_0008->SetValue("DERIVED\\PRIMARY"); + Archive->Push(e_0008_0008); + } + else + { + // There was no 'SOP Class UID'. + // the source image was NOT a true Dicom one. + // We consider the image is a 'Secondary Capture' one + // SOP Class UID + e_0008_0016 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x0016) ); + // [Secondary Capture Image Storage] + e_0008_0016 ->SetValue("1.2.840.10008.5.1.4.1.1.7"); + Archive->Push(e_0008_0016); + } + +// ---- The user will never have to take any action on the following ----. + + // new value for 'SOP Instance UID' + ValEntry *e_0008_0018 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x0018) ); + e_0008_0018->SetValue( Util::CreateUniqueUID() ); + Archive->Push(e_0008_0018); + + // Instance Creation Date + ValEntry *e_0008_0012 = CopyValEntry(0x0008,0x0012); + std::string date = Util::GetCurrentDate(); + e_0008_0012->SetValue(date.c_str()); + Archive->Push(e_0008_0012); + + // Instance Creation Time + ValEntry *e_0008_0013 = CopyValEntry(0x0008,0x0013); + std::string time = Util::GetCurrentTime(); + e_0008_0013->SetValue(time.c_str()); + Archive->Push(e_0008_0013); + +// ----- Add Mandatory Entries if missing --- + +// Entries whose type is 1 are mandatory, with a mandatory value +// Entries whose type is 1c are mandatory-inside-a-Sequence +// Entries whose type is 2 are mandatory, with a optional value +// Entries whose type is 2c are mandatory-inside-a-Sequence +// Entries whose type is 3 are optional + + // 'Serie Instance UID' + // Keep the value if exists + // The user is allowed to create his own Series, + // keeping the same 'Serie Instance UID' for various images + // The user shouldn't add any image to a 'Manufacturer Serie' + // but there is no way no to allowed him to do that + ValEntry *e_0020_000e = FileInternal->GetValEntry(0x0020, 0x000e); + if ( !e_0020_000e ) + { + e_0020_000e = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0020, 0x000e) ); + e_0020_000e->SetValue(Util::CreateUniqueUID() ); + Archive->Push(e_0020_000e); + } + + // 'Study Instance UID' + // Keep the value if exists + // The user is allowed to create his own Study, + // keeping the same 'Study Instance UID' for various images + // The user may add images to a 'Manufacturer Study', + // adding new series to an already existing Study + ValEntry *e_0020_000d = FileInternal->GetValEntry(0x0020, 0x000d); + if ( !e_0020_000d ) + { + e_0020_000d = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0020, 0x000d) ); + e_0020_000d->SetValue(Util::CreateUniqueUID() ); + Archive->Push(e_0020_000d); + } + + // Modality : if missing we set it to 'OTher' + ValEntry *e_0008_0060 = FileInternal->GetValEntry(0x0008, 0x0060); + if ( !e_0008_0060 ) + { + e_0008_0060 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x0060) ); + e_0008_0060->SetValue("OT"); + Archive->Push(e_0008_0060); + } + + // Manufacturer : if missing we set it to 'GDCM Factory' + ValEntry *e_0008_0070 = FileInternal->GetValEntry(0x0008, 0x0070); + if ( !e_0008_0070 ) + { + e_0008_0070 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x0070) ); + e_0008_0070->SetValue("GDCM Factory"); + Archive->Push(e_0008_0070); + } + + // Institution Name : if missing we set it to 'GDCM Hospital' + ValEntry *e_0008_0080 = FileInternal->GetValEntry(0x0008, 0x0080); + if ( !e_0008_0080 ) + { + e_0008_0080 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x0080) ); + e_0008_0080->SetValue("GDCM Hospital"); + Archive->Push(e_0008_0080); + } + + // Patient's Name : if missing, we set it to 'GDCM^Patient' + ValEntry *e_0010_0010 = FileInternal->GetValEntry(0x0010, 0x0010); + if ( !e_0010_0010 ) + { + e_0010_0010 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0010, 0x0010) ); + e_0010_0010->SetValue("GDCM^Patient"); + Archive->Push(e_0010_0010); + } + + // Patient's ID : if missing, we set it to 'GDCM_Patient_ID' + ValEntry *e_0010_0020 = FileInternal->GetValEntry(0x0010, 0x0020); + if ( !e_0010_0020 ) + { + e_0010_0020 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0010, 0x0020) ); + e_0010_0020->SetValue("GDCM_Patient_ID"); + Archive->Push(e_0010_0020); + } + + // Patient's Birth Date :'type 2' entry -> must exist, value not mandatory + ValEntry *e_0010_0030 = FileInternal->GetValEntry(0x0010, 0x0030); + if ( !e_0010_0030 ) + { + e_0010_0030 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0010, 0x0030) ); + e_0010_0030->SetValue(""); + Archive->Push(e_0010_0030); + } + + // Patient's Sex :'type 2' entry -> must exist, value not mandatory + ValEntry *e_0010_0040 = FileInternal->GetValEntry(0x0010, 0x0040); + if ( !e_0010_0040 ) + { + e_0010_0040 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0010, 0x0040) ); + e_0010_0040->SetValue(""); + Archive->Push(e_0010_0040); + } + + // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory + ValEntry *e_0008_0090 = FileInternal->GetValEntry(0x0008, 0x0090); + if ( !e_0008_0090 ) + { + e_0008_0090 = new ValEntry( + Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x0090) ); + e_0008_0090->SetValue(""); + Archive->Push(e_0008_0090); + } + + // Remove some inconstencies (probably some more will be added) + + // if (0028 0008)Number of Frames exists + // Push out (0020 0052),Frame of Reference UID + // (only meaningfull within a Serie) + ValEntry *e_0028_0008 = FileInternal->GetValEntry(0x0028, 0x0008); + if ( !e_0028_0008 ) + { + Archive->Push(0x0020, 0X0052); + } +} + //----------------------------------------------------------------------------- -// Protected +// Private /** * \brief Factorization for various forms of constructors. */ @@ -935,13 +1370,13 @@ void FileHelper::Initialize() WriteMode = WMODE_RAW; WriteType = ExplicitVR; - PixelReadConverter = new PixelReadConvert; + PixelReadConverter = new PixelReadConvert; PixelWriteConverter = new PixelWriteConvert; Archive = new DocEntryArchive( FileInternal ); if ( FileInternal->IsReadable() ) { - PixelReadConverter->GrabInformationsFromHeader( FileInternal ); + PixelReadConverter->GrabInformationsFromFile( FileInternal ); } } @@ -962,7 +1397,7 @@ uint8_t *FileHelper::GetRaw() raw = PixelReadConverter->GetRaw(); if ( ! raw ) { - gdcmVerboseMacro( "Read/decompress of pixel data apparently went wrong."); + gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong."); return 0; } } @@ -971,8 +1406,15 @@ uint8_t *FileHelper::GetRaw() } //----------------------------------------------------------------------------- -// Private +// Print +void FileHelper::Print(std::ostream &os, std::string const &) +{ + FileInternal->SetPrintLevel(PrintLevel); + FileInternal->Print(os); + + PixelReadConverter->SetPrintLevel(PrintLevel); + PixelReadConverter->Print(os); +} //----------------------------------------------------------------------------- } // end namespace gdcm -