X-Git-Url: https://git.creatis.insa-lyon.fr/pubgit/?a=blobdiff_plain;f=src%2FgdcmFileHelper.cxx;h=87c6e895b241c2ca9adc06b227d7d13e03752969;hb=2742ff3e5f5e78f9d3c56194a0380e9bb03ca1c9;hp=687d43ca6c37989e136b74e5a92cf6f170336436;hpb=96a20c8aafd6b3e9dc558d6642d2eaf53edf0dd0;p=gdcm.git diff --git a/src/gdcmFileHelper.cxx b/src/gdcmFileHelper.cxx index 687d43ca..87c6e895 100644 --- a/src/gdcmFileHelper.cxx +++ b/src/gdcmFileHelper.cxx @@ -4,8 +4,8 @@ Module: $RCSfile: gdcmFileHelper.cxx,v $ Language: C++ - Date: $Date: 2006/05/02 13:11:57 $ - Version: $Revision: 1.103 $ + Date: $Date: 2007/08/31 14:11:00 $ + Version: $Revision: 1.126 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -51,73 +51,113 @@ gdcm::File *f = new gdcm::File(fileName); // user may also decide he doesn't want to load some parts of the header gdcm::File *f = new gdcm::File(); f->SetFileName(fileName); - f->SetLoadMode(LD_NOSEQ); // or - f->SetLoadMode(LD_NOSHADOW); // or + f->SetLoadMode(LD_NOSEQ); // or + f->SetLoadMode(LD_NOSHADOW); // or f->SetLoadMode(LD_NOSEQ | LD_NOSHADOW); // or f->SetLoadMode(LD_NOSHADOWSEQ); f->Load(); +// To decide whether it's an 'image of interest for him, or not, // user can now check some values std::string v = f->GetEntryValue(groupNb,ElementNb); // to get the pixels, user needs a gdcm::FileHelper gdcm::FileHelper *fh = new gdcm::FileHelper(f); -// user may ask not to convert Palette to RGB + +// 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) - + // (WriteMode is set) + +// If user wants to write the file as MONOCHROME1 (0=white) +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' WriteMode : WMODE_RAW / WMODE_RGB 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) -fh1->Write(newFileName); - SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR(); - (modifies TransferSyntax) +fh->SetWriteType( ImplicitVR/ExplicitVR/ACR/ACR_LIBIDO/JPEG/JPEG2000) + +fh->Write(newFileName); + CheckMandatoryElements(); // Checks existing ones / Add missing ones + Fix VR if unknown elements + SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR(); / + SetWriteFileTypeToACR() / SetWriteFileTypeToJPEG() / SetWriteFileTypeToJ2K() + (Modifies TransferSyntax if any; Pushes to the Archives old one) 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 ) + (Modifies and pushes to the Archive, when necessary : photochr. interp., + samples per pixel, Planar configuration, + bits allocated, bits stored, high bit -ACR 24 bits- + Pixels element VR, pushes out the LUT ) + SetWriteToRaw() + Sets Photometric Interpretation + DataEntry *pixel =CopyDataEntry(7fe0,0010,VR) + Sets VR, BinArea, Length for PixelData + if MONOCHROME1 + ConvertFixGreyLevels + Archive->Push(photInt); + Archive->Push(pixel); + photInt->Delete(); + pixel->Delete(); + SetWriteToRGB() + if NumberOfScalarComponents==1 + SetWriteToRaw(); return; + PixelReadConverter->BuildRGBImage() + DataEntry *pixel =CopyDataEntry(7fe0,0010,VR) + Archives spp, planConfig,photInt, pixel + Pushes out any LUT CheckWriteIntegrity(); (checks user given pixels length) FileInternal->Write(fileName,WriteType) - fp = opens file(fileName); - ComputeGroup0002Length( ); - BitsAllocated 12->16 - RemoveEntry(palettes, etc) + fp = opens file(fileName); // out|binary + ComputeGroup0002Length( ); Document::WriteContent(fp, writetype); + writes Dicom File Preamble not ACR-NEMA + ElementSet::WriteContent(fp, writetype); + writes recursively all DataElements RestoreWrite(); - (moves back to the File all the archived elements) - RestoreWriteFileType(); - (pushes back group 0002, with TransferSyntax) + (moves back to the gdcm::File all the archived elements) */ -namespace gdcm +namespace GDCM_NAME_SPACE { typedef std::map GroupHT; // Hash Table //------------------------------------------------------------------------- @@ -230,8 +270,8 @@ bool FileHelper::Load() } /** - * \brief Accesses an existing DataEntry through it's (group, element) - * and modifies it's content with the given value. + * \brief Accesses an existing DataEntry through its (group, element) + * and modifies its 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 @@ -245,8 +285,8 @@ bool FileHelper::SetEntryString(std::string const &content, /** - * \brief Accesses an existing DataEntry through it's (group, element) - * and modifies it's content with the given value. + * \brief Accesses an existing DataEntry through its (group, element) + * and modifies its 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 @@ -265,6 +305,7 @@ bool FileHelper::SetEntryBinArea(uint8_t *content, int lgth, * @param content (string) value to be set * @param group Group number of the Entry * @param elem Element number of the Entry + * @param vr Value Representation of the DataElement to be inserted * \return pointer to the modified/created DataEntry (NULL when creation * failed). */ @@ -279,10 +320,11 @@ DataEntry *FileHelper::InsertEntryString(std::string const &content, * \brief Modifies the value of a given DataEntry 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 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 + * @param vr Value Representation of the DataElement to be inserted * \return pointer to the modified/created DataEntry (NULL when creation * failed). */ @@ -391,7 +433,7 @@ uint8_t *FileHelper::GetImageDataRaw () return GetRaw(); } -#ifndef GDCM_LEGACY_REMOVE +//#ifndef GDCM_LEGACY_REMOVE /* * \brief Useless function, since PixelReadConverter forces us * copy the Pixels anyway. @@ -418,6 +460,7 @@ uint8_t *FileHelper::GetImageDataRaw () * @return On success, the number of bytes actually copied. Zero on * failure e.g. MaxSize is lower than necessary. */ + /* size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize) { if ( ! GetRaw() ) @@ -450,7 +493,8 @@ size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize) PixelReadConverter->GetRawSize() ); return PixelReadConverter->GetRawSize(); } -#endif +*/ +//#endif /** * \brief Points the internal pointer to the callers inData @@ -461,13 +505,15 @@ size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize) * 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) { - SetUserData(inData, expectedSize); + PixelWriteConverter->SetUserData(inData, expectedSize); + /// \todo : shouldn't we call SetCompressJPEGUserData/SetCompressJPEG2000UserData + /// here, too? } /** @@ -480,6 +526,22 @@ void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize) */ 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); + } + else if( WriteType == JPEG ) + { + PixelWriteConverter->SetCompressJPEGUserData(inData, expectedSize, FileInternal); + } + else + { + PixelWriteConverter->SetUserData(inData, expectedSize); + } + */ + // Just try! PixelWriteConverter->SetUserData(inData, expectedSize); } @@ -662,8 +724,7 @@ bool FileHelper::WriteAcr (std::string const &fileName) * @return false if write fails */ bool FileHelper::Write(std::string const &fileName) -{ - +{ CheckMandatoryElements(); //called once, here ! bool flag = false; @@ -676,36 +737,10 @@ bool FileHelper::Write(std::string const &fileName) 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. - - 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(); - SetWriteFileTypeToExplicitVR(); // to see JPRx break; case ACR: case ACR_LIBIDO: @@ -722,12 +757,29 @@ bool FileHelper::Write(std::string const &fileName) // 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; } - + // -------------------------------------------------------------- // Special Patch to allow gdcm to re-write ACR-LibIDO formated images // @@ -757,17 +809,20 @@ bool FileHelper::Write(std::string const &fileName) break; } - bool check = CheckWriteIntegrity(); // verifies length - if (WriteType == JPEG ) check = true; + bool check; + if (WriteType == JPEG || WriteType == JPEG2000) + check = true; + else + check = CheckWriteIntegrity(); // verifies length + if (check) { check = FileInternal->Write(fileName,WriteType); } - RestoreWrite(); + RestoreWrite(); // RestoreWriteFileType(); // RestoreWriteMandatory(); - // -------------------------------------------------------------- // Special Patch to allow gdcm to re-write ACR-LibIDO formated images @@ -783,11 +838,7 @@ bool FileHelper::Write(std::string const &fileName) //----------------------------------------------------------------------------- // Protected /** - * \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 + * \brief Verifies the size of the user given PixelData * @return true if check is successfull */ bool FileHelper::CheckWriteIntegrity() @@ -797,14 +848,14 @@ bool FileHelper::CheckWriteIntegrity() 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() @@ -813,29 +864,29 @@ bool FileHelper::CheckWriteIntegrity() if ( FileInternal->HasLUT() ) rgbSize = decSize * 3; + size_t userDataSize = PixelWriteConverter->GetUserDataSize(); switch(WriteMode) { case WMODE_RAW : - if ( decSize!=PixelWriteConverter->GetUserDataSize() ) + if ( abs(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(rgbSize-userDataSize)>1) // ignore padding zero { gdcmWarningMacro( "Data size (RGB) is incorrect. Should be " - << decSize << " / Found " - << PixelWriteConverter->GetUserDataSize() ); + << rgbSize << " / Found " + << userDataSize ); return false; } break; } } - return true; } @@ -843,6 +894,8 @@ bool FileHelper::CheckWriteIntegrity() * \brief Updates the File to write RAW data (as opposed to RGB data) * (modifies, when necessary, photochromatic interpretation, * bits allocated, Pixels element VR) + * WARNING : if SetPhotometricInterpretationToMonochrome1() was called + * before Pixel Elements is modified :-( */ void FileHelper::SetWriteToRaw() { @@ -853,6 +906,7 @@ void FileHelper::SetWriteToRaw() } else { + // 0x0028,0x0004 : Photometric Interpretation DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS"); if (FileInternal->HasLUT() ) { @@ -860,7 +914,10 @@ void FileHelper::SetWriteToRaw() } else { - photInt->SetString("MONOCHROME2 "); + if (GetPhotometricInterpretation() == 2) + photInt->SetString("MONOCHROME2 "); // 0 = Black + else + photInt->SetString("MONOCHROME1 "); // 0 = White ! } PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(), @@ -871,11 +928,23 @@ void FileHelper::SetWriteToRaw() vr = "OW"; if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files vr = "OB"; + // 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); @@ -899,13 +968,13 @@ void FileHelper::SetWriteToRGB() 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() ) { @@ -984,7 +1053,6 @@ void FileHelper::SetWriteToRGB() */ void FileHelper::RestoreWrite() { - Archive->Restore(0x0028,0x0002); Archive->Restore(0x0028,0x0004); @@ -1042,13 +1110,28 @@ void FileHelper::SetWriteFileTypeToACR() Archive->Push(0x0002,0x0102); } +/** + * \brief Sets in the File the TransferSyntax to 'JPEG2000' + */ +void FileHelper::SetWriteFileTypeToJPEG2000() +{ + std::string ts = Util::DicomString( + Global::GetTS()->GetSpecialTransferSyntax(TS::JPEG2000Lossless) ); + + DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI"); + tss->SetString(ts); + + Archive->Push(tss); + tss->Delete(); +} + /** * \brief Sets in the File the TransferSyntax to 'JPEG' - */ + */ void FileHelper::SetWriteFileTypeToJPEG() { - std::string ts = Util::DicomString( - Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) ); + std::string ts = Util::DicomString( + Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGLosslessProcess14_1) ); DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI"); tss->SetString(ts); @@ -1085,14 +1168,6 @@ void FileHelper::SetWriteFileTypeToImplicitVR() tss->Delete(); } - -/** - * \brief Restore in the File the initial group 0002 - */ -void FileHelper::RestoreWriteFileType() -{ -} - /** * \brief Set the Write not to Libido format */ @@ -1105,12 +1180,9 @@ void FileHelper::SetWriteToLibido() { std::string rows, columns; - //DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry()); - //DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry()); - DataEntry *newRow=DataEntry::New(0x0028, 0x0010, "US"); DataEntry *newCol=DataEntry::New(0x0028, 0x0011, "US"); - + newRow->Copy(oldCol); newCol->Copy(oldRow); @@ -1183,7 +1255,6 @@ DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem, if ( oldE ) { - //newE = DataEntry::New(oldE->GetDictEntry()); newE = DataEntry::New(group, elem, vr); newE->Copy(oldE); } @@ -1241,9 +1312,10 @@ We have to deal with 4 *very* different cases : FILTERED_IMAGE -3) user created a new image, using a set of existing images (eg MIP, MPR, cartography image) CREATED_IMAGE --4) user modified/added some tags *without processing* the pixels (anonymization.. +-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. @@ -1380,15 +1452,20 @@ void FileHelper::CheckMandatoryElements() //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 0002 UI 1 Media Storage SOP Class UID + //0002 0003 UI 1 Media Storage 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 - + + // Push out 'ACR-NEMA-special' entries, if any + Archive->Push(0x0008,0x0001); // Length to End + Archive->Push(0x0008,0x0010); // Recognition Code + Archive->Push(0x0028,0x0005); // Image Dimension + // Create them if not found // Always modify the value // Push the entries to the archive. @@ -1412,10 +1489,10 @@ void FileHelper::CheckMandatoryElements() // '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 (?) @@ -1431,45 +1508,39 @@ void FileHelper::CheckMandatoryElements() 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 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) ); + + // 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' + // create 'Referenced SOP Instance UID' from '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"); + + 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 @@ -1497,9 +1568,11 @@ void FileHelper::CheckMandatoryElements() 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 ) { @@ -1519,7 +1592,8 @@ void FileHelper::CheckMandatoryElements() std::ostringstream s; // check 'Bits Allocated' vs decent values int nbBitsAllocated = FileInternal->GetBitsAllocated(); - if ( nbBitsAllocated == 0 || nbBitsAllocated > 32) + if ( (nbBitsAllocated == 0 || nbBitsAllocated > 32) + || ( nbBitsAllocated > 8 && nbBitsAllocated <16) ) { CopyMandatoryEntry(0x0028,0x0100,"16","US"); gdcmWarningMacro("(0028,0100) changed from " @@ -1552,47 +1626,115 @@ void FileHelper::CheckMandatoryElements() << " for consistency purpose"); } - std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030); - if ( pixelSpacing == GDCM_UNFOUND ) + // check Pixel Representation (default it as 0 -unsigned-) + + DataEntry *e_0028_0103 = FileInternal->GetDataEntry(0x0028, 0x0103); + if ( !e_0028_0103 ) { - pixelSpacing = "1.0\\1.0"; - // if missing, Pixel Spacing forced to "1.0\1.0" - CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS"); + gdcmWarningMacro("PixelRepresentation (0028,0103) is supposed to be mandatory"); + CopyMandatoryEntry(0x0028, 0x0103,"0","US"); } - - // '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"); - - // Samples Per Pixel (type 1) : default to grayscale + else + { + int sign = (int)e_0028_0103->GetValue(0); + if (sign !=1 && sign !=0) + { + gdcmWarningMacro("PixelRepresentation (0028,0103) is supposed to be =1 or =0"); + CopyMandatoryEntry(0x0028, 0x0103,"0","US"); + } + } + + std::string pixelAspectRatio = FileInternal->GetEntryString(0x0028,0x0034); + if ( pixelAspectRatio == GDCM_UNFOUND ) // avoid conflict with pixelSpacing ! + { + 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 : +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 second entry is the direction of the columns, given by the direction of the +last pixel in the first column from the first pixel in that column. +L : Left, F : Feet, A : Anterior, P : Posterior. +Up to 3 letters can be used in combination to indicate oblique planes. + +//0020,0030 Image Position (RET) +x,y,z coordinates im mm of the first pixel in the image + +// 0020,0035 Image Orientation (RET) +Direction cosines of the R axis of the image system with respect to the +equipment coordinate axes x,y,z, followed by direction cosines of the C axis of +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 +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 imagePositionRet = FileInternal->GetEntryString(0x0020,0x0030); + 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) + { + CopyMandatoryEntry(0x0020, 0x0032,imagePositionRet,"DS"); + Archive->Push(0x0020,0x0030); + CopyMandatoryEntry(0x0020, 0x0037,imageOrientationRet,"DS"); + Archive->Push(0x0020,0x0035); + } +*/ + + // Samples Per Pixel (type 1) : default to grayscale CheckMandatoryEntry(0x0028,0x0002,"1","US"); // --- Check UID-related Entries --- // At the end, not to overwrite the original ones, - // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID' + // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID' // 'SOP Instance UID' CopyMandatoryEntry(0x0008,0x0018,sop,"UI"); 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 + // DI = Digital Interface // DF = Digitized Film // WSD = Workstation // SD = Scanned Document // SI = Scanned Image // DRW = Drawing // SYN = Synthetic Image - + CheckMandatoryEntry(0x0008,0x0064,"SYN","CS"); // Why not? } /* @@ -1602,7 +1744,7 @@ void FileHelper::CheckMandatoryElements() } */ - + // ---- The user will never have to take any action on the following ---- // new value for 'SOP Instance UID' @@ -1611,7 +1753,7 @@ void FileHelper::CheckMandatoryElements() // Instance Creation Date const std::string &date = Util::GetCurrentDate(); CopyMandatoryEntry(0x0008,0x0012,date,"DA"); - + // Instance Creation Time const std::string &time = Util::GetCurrentTime(); CopyMandatoryEntry(0x0008,0x0013,time,"TM"); @@ -1659,15 +1801,15 @@ void FileHelper::CheckMandatoryElements() // Instance Number CheckMandatoryEntry(0x0020,0x0013,"","IS"); - + // Patient Orientation // Can be computed from (0020|0037) : Image Orientation (Patient) - gdcm::Orientation *o = gdcm::Orientation::New(); + GDCM_NAME_SPACE::Orientation *o = GDCM_NAME_SPACE::Orientation::New(); std::string ori = o->GetOrientation ( FileInternal ); o->Delete(); if (ori != "\\" && ori != GDCM_UNFOUND) CheckMandatoryEntry(0x0020,0x0020,ori,"CS"); - else + else CheckMandatoryEntry(0x0020,0x0020,"","CS"); // Default Patient Position to HFS @@ -1700,7 +1842,7 @@ void FileHelper::CheckMandatoryElements() /* // Deal with element 0x0000 (group length) of each group. // First stage : get all the different Groups - + GroupHT grHT; DocEntry *d = FileInternal->GetFirstEntry(); while(d) @@ -1714,8 +1856,12 @@ void FileHelper::CheckMandatoryElements() CheckMandatoryEntry(it->first, 0x0000, "0"); } // Third stage : update all 'zero level' groups length -*/ +*/ + + if (PhotometricInterpretation == 1) + { + } } void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr ) @@ -1731,7 +1877,7 @@ void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string va } } -/// \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)); @@ -1786,7 +1932,6 @@ void FileHelper::RestoreWriteMandatory() Archive->Restore(0x0020,0x000e); } - /** * \brief CallStartMethod */ @@ -1823,9 +1968,11 @@ void FileHelper::Initialize() { UserFunction = 0; ContentType = USER_OWN_IMAGE; - + WriteMode = WMODE_RAW; WriteType = ExplicitVR; + + PhotometricInterpretation = 2; // Black = 0 PixelReadConverter = new PixelReadConvert; PixelWriteConverter = new PixelWriteConvert; @@ -1861,9 +2008,95 @@ uint8_t *FileHelper::GetRaw() return raw; } +/** + * \brief Deal with Grey levels i.e. re-arange them + * to have low values = dark, high values = bright + */ +void FileHelper::ConvertFixGreyLevels(uint8_t *raw, size_t rawSize) +{ + uint32_t i; // to please M$VC6 + int16_t j; + + // Number of Bits Allocated for storing a Pixel is defaulted to 16 + // when absent from the file. + int bitsAllocated = FileInternal->GetBitsAllocated(); + if ( bitsAllocated == 0 ) + { + bitsAllocated = 16; + } + + else if (bitsAllocated > 8 && bitsAllocated < 16 && bitsAllocated != 12) + { + bitsAllocated = 16; + } + // Number of "Bits Stored", defaulted to number of "Bits Allocated" + // when absent from the file. + int bitsStored = FileInternal->GetBitsStored(); + if ( bitsStored == 0 ) + { + bitsStored = bitsAllocated; + } + + if (!FileInternal->IsSignedPixelData()) + { + if ( bitsAllocated == 8 ) + { + uint8_t *deb = (uint8_t *)raw; + for (i=0; i +void RescaleFunction(TBuffer* buffer, TSource *source, + double slope, double intercept, size_t size) +{ + size /= sizeof(TSource); + + if (slope != 1.0 && intercept != 0.0) + { + // Duff's device. Instead of this code: + // + // for(unsigned int i=0; i 0); + } + } + else if (slope == 1.0 && intercept != 0.0) + { + // Duff's device. Instead of this code: + // + // for(unsigned int i=0; i 0); + } + } + else if (slope != 1.0 && intercept == 0.0) + { + // Duff's device. Instead of this code: + // + // for(unsigned int i=0; i 0); + } + } + else + { + // Duff's device. Instead of this code: + // + // for(unsigned int i=0; i 0); + } + } +} + + +template +void RescaleFunction(ImageIOBase::IOComponentType bufferType, + void* buffer, TSource *source, + double slope, double intercept, size_t size) +{ + switch (bufferType) + { + case ImageIOBase::UCHAR: + RescaleFunction( (unsigned char *)buffer, source, slope, intercept, size); + break; + case ImageIOBase::CHAR: + RescaleFunction( (char *)buffer, source, slope, intercept, size); + break; + case ImageIOBase::USHORT: + RescaleFunction( (unsigned short *)buffer, source, slope, intercept,size); + break; + case ImageIOBase::SHORT: + RescaleFunction( (short *)buffer, source, slope, intercept, size); + break; + case ImageIOBase::UINT: + RescaleFunction( (unsigned int *)buffer, source, slope, intercept, size); + break; + case ImageIOBase::INT: + RescaleFunction( (int *)buffer, source, slope, intercept, size); + break; + case ImageIOBase::FLOAT: + RescaleFunction( (float *)buffer, source, slope, intercept, size); + break; + case ImageIOBase::DOUBLE: + RescaleFunction( (double *)buffer, source, slope, intercept, size); + break; + default: + ::itk::OStringStream message; + message << "itk::ERROR: GDCMImageIO: Unknown component type : " << bufferType; + ::itk::ExceptionObject e(__FILE__, __LINE__, message.str().c_str(),ITK_LOCATION); + throw e; + } +} +*/