X-Git-Url: https://git.creatis.insa-lyon.fr/pubgit/?a=blobdiff_plain;f=src%2FgdcmPixelReadConvert.cxx;h=67431bfc6858b620d4e3d7f9df49d4ee8f4e241b;hb=e9a711266d34e33b482edbe241b2e40db7d9915a;hp=7dc62ec99bdc02596f412bae40a94529a6fb1dc5;hpb=1996bbbcd752f893a137241cadbece7d758baa81;p=gdcm.git diff --git a/src/gdcmPixelReadConvert.cxx b/src/gdcmPixelReadConvert.cxx index 7dc62ec9..67431bfc 100644 --- a/src/gdcmPixelReadConvert.cxx +++ b/src/gdcmPixelReadConvert.cxx @@ -1,10 +1,10 @@ /*========================================================================= - + Program: gdcm Module: $RCSfile: gdcmPixelReadConvert.cxx,v $ Language: C++ - Date: $Date: 2005/06/22 07:52:32 $ - Version: $Revision: 1.69 $ + Date: $Date: 2005/11/09 10:18:44 $ + Version: $Revision: 1.98 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -31,7 +31,7 @@ namespace gdcm { -//bool ReadMPEGFile (std::ifstream *fp, void *image_buffer, size_t lenght); +bool ReadMPEGFile (std::ifstream *fp, char *inputdata, size_t lenght); bool gdcm_read_JPEG2000_file (void* raw, char *inputdata, size_t inputlength); //----------------------------------------------------------------------------- @@ -50,6 +50,10 @@ PixelReadConvert::PixelReadConvert() LutRedData = 0; LutGreenData = 0; LutBlueData = 0; + RLEInfo = 0; + JPEGInfo = 0; + UserFunction = 0; + FileInternal = 0; } /// Canonical Destructor @@ -107,24 +111,56 @@ void PixelReadConvert::GrabInformationsFromFile( File *file ) YSize = file->GetYSize(); ZSize = file->GetZSize(); SamplesPerPixel = file->GetSamplesPerPixel(); - PixelSize = file->GetPixelSize(); + //PixelSize = file->GetPixelSize(); Useless PixelSign = file->IsSignedPixelData(); SwapCode = file->GetSwapCode(); - std::string ts = file->GetTransferSyntax(); - IsRaw = - ( ! file->IsDicomV3() ) - || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRLittleEndian - || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRLittleEndianDLXGE - || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ExplicitVRLittleEndian - || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ExplicitVRBigEndian - || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::DeflatedExplicitVRLittleEndian; - - IsMPEG = Global::GetTS()->IsMPEG(ts); - IsJPEG2000 = Global::GetTS()->IsJPEG2000(ts); - IsJPEGLS = Global::GetTS()->IsJPEGLS(ts); - IsJPEGLossy = Global::GetTS()->IsJPEGLossy(ts); - IsJPEGLossless = Global::GetTS()->IsJPEGLossless(ts); - IsRLELossless = Global::GetTS()->IsRLELossless(ts); + + if (! file->IsDicomV3() ) // Should be ACR-NEMA file + { + IsRaw = true; + // Don't loose time checking unexistant Transfer Syntax ! + IsPrivateGETransferSyntax = IsMPEG + = IsJPEG2000 = IsJPEGLS = IsJPEGLossy + = IsJPEGLossless = IsRLELossless + = false; + } + else + { + std::string ts = file->GetTransferSyntax(); + + IsRaw = false; + while (true) + { + // mind the order : check the most usual first. + if( IsRaw = Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ExplicitVRLittleEndian) break; + if( IsRaw = Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRLittleEndian ) break; + if( IsRaw = Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ExplicitVRBigEndian) break; + if( IsRaw = Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRBigEndianPrivateGE) break; + if( IsRaw = Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::DeflatedExplicitVRLittleEndian) break; + break; + } + + IsPrivateGETransferSyntax = + ( Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRBigEndianPrivateGE ); + + IsMPEG = IsJPEG2000 = IsJPEGLS = IsJPEGLossy = IsJPEGLossless = IsRLELossless = false; + + if (!IsRaw) + { + while(true) + { + // mind the order : check the most usual first. + if( IsJPEGLossy = Global::GetTS()->IsJPEGLossy(ts) ) break; + if( IsJPEGLossless = Global::GetTS()->IsJPEGLossless(ts) ) break; + if( IsRLELossless = Global::GetTS()->IsRLELossless(ts) ) break; + if( IsJPEG2000 = Global::GetTS()->IsJPEG2000(ts) ) break; + if( IsMPEG = Global::GetTS()->IsMPEG(ts) ) break; + if( IsJPEGLS = Global::GetTS()->IsJPEGLS(ts) ) break; + gdcmWarningMacro("Unexepected Transfer Syntax :[" << ts << "]"); + break; + } + } + } PixelOffset = file->GetPixelOffset(); PixelDataLength = file->GetPixelAreaLength(); @@ -144,10 +180,13 @@ void PixelReadConvert::GrabInformationsFromFile( File *file ) if ( HasLUT ) { // Just in case some access to a File element requires disk access. - LutRedDescriptor = file->GetEntryValue( 0x0028, 0x1101 ); - LutGreenDescriptor = file->GetEntryValue( 0x0028, 0x1102 ); - LutBlueDescriptor = file->GetEntryValue( 0x0028, 0x1103 ); + LutRedDescriptor = file->GetEntryString( 0x0028, 0x1101 ); + LutGreenDescriptor = file->GetEntryString( 0x0028, 0x1102 ); + LutBlueDescriptor = file->GetEntryString( 0x0028, 0x1103 ); + // FIXME : The following comment is probabely meaningless, since LUT are *always* + // loaded at parsing time, whatever their length is. + // Depending on the value of Document::MAX_SIZE_LOAD_ELEMENT_VALUE // [ refer to invocation of Document::SetMaxSizeLoadEntry() in // Document::Document() ], the loading of the value (content) of a @@ -157,7 +196,7 @@ void PixelReadConvert::GrabInformationsFromFile( File *file ) // \TODO Reading a [Bin|Val]Entry directly from disk is a kludge. // We should NOT bypass the [Bin|Val]Entry class. Instead // an access to an UNLOADED content of a [Bin|Val]Entry occurence - // (e.g. BinEntry::GetBinArea()) should force disk access from + // (e.g. DataEntry::GetBinArea()) should force disk access from // within the [Bin|Val]Entry class itself. The only problem // is that the [Bin|Val]Entry is unaware of the FILE* is was // parsed from. Fix that. FIXME. @@ -167,7 +206,7 @@ void PixelReadConvert::GrabInformationsFromFile( File *file ) LutRedData = (uint8_t*)file->GetEntryBinArea( 0x0028, 0x1201 ); if ( ! LutRedData ) { - gdcmWarningMacro( "Unable to read Red Palette Color Lookup Table data" ); + gdcmWarningMacro("Unable to read Red Palette Color Lookup Table data"); } // //// Green round: @@ -175,7 +214,7 @@ void PixelReadConvert::GrabInformationsFromFile( File *file ) LutGreenData = (uint8_t*)file->GetEntryBinArea(0x0028, 0x1202 ); if ( ! LutGreenData) { - gdcmWarningMacro( "Unable to read Green Palette Color Lookup Table data" ); + gdcmWarningMacro("Unable to read Green Palette Color Lookup Table data"); } // //// Blue round: @@ -183,9 +222,10 @@ void PixelReadConvert::GrabInformationsFromFile( File *file ) LutBlueData = (uint8_t*)file->GetEntryBinArea( 0x0028, 0x1203 ); if ( ! LutBlueData ) { - gdcmWarningMacro( "Unable to read Blue Palette Color Lookup Table data" ); + gdcmWarningMacro("Unable to read Blue Palette Color Lookup Table data"); } } + FileInternal = file; ComputeRawAndRGBSizes(); } @@ -207,7 +247,7 @@ bool PixelReadConvert::ReadAndDecompressPixelData( std::ifstream *fp ) } fp->seekg( PixelOffset, std::ios::beg ); - if( fp->fail() || fp->eof()) + if ( fp->fail() || fp->eof() ) { gdcmWarningMacro( "Unable to find PixelOffset in file." ); return false; @@ -227,12 +267,12 @@ bool PixelReadConvert::ReadAndDecompressPixelData( std::ifstream *fp ) // after the field containing the image data. In this case, these // bad data are added to the size of the image (in the PixelDataLength // variable). But RawSize is the right size of the image ! - if( PixelDataLength != RawSize) + if ( PixelDataLength != RawSize ) { gdcmWarningMacro( "Mismatch between PixelReadConvert : " - << PixelDataLength << " and RawSize : " << RawSize ); + << PixelDataLength << " and RawSize : " << RawSize ); } - if( PixelDataLength > RawSize) + if ( PixelDataLength > RawSize ) { fp->read( (char*)Raw, RawSize); } @@ -249,7 +289,8 @@ bool PixelReadConvert::ReadAndDecompressPixelData( std::ifstream *fp ) } else if ( IsRLELossless ) { - if ( ! RLEInfo->DecompressRLEFile( fp, Raw, XSize, YSize, ZSize, BitsAllocated ) ) + if ( ! RLEInfo->DecompressRLEFile + ( fp, Raw, XSize, YSize, ZSize, BitsAllocated ) ) { gdcmWarningMacro( "RLE decompressor failed." ); return false; @@ -259,7 +300,8 @@ bool PixelReadConvert::ReadAndDecompressPixelData( std::ifstream *fp ) { //gdcmWarningMacro( "Sorry, MPEG not yet taken into account" ); //return false; - //ReadMPEGFile(fp, Raw, PixelDataLength); // fp has already been seek to start of mpeg + // fp has already been seek to start of mpeg + ReadMPEGFile(fp, (char*)Raw, PixelDataLength); return true; } else @@ -267,7 +309,8 @@ bool PixelReadConvert::ReadAndDecompressPixelData( std::ifstream *fp ) // Default case concerns JPEG family if ( ! ReadAndDecompressJPEGFile( fp ) ) { - gdcmWarningMacro( "JPEG decompressor failed." ); + gdcmWarningMacro( "JPEG decompressor ( ReadAndDecompressJPEGFile()" + << " method ) failed." ); return false; } } @@ -277,6 +320,8 @@ bool PixelReadConvert::ReadAndDecompressPixelData( std::ifstream *fp ) ConvertReorderEndianity(); ConvertReArrangeBits(); ConvertFixGreyLevels(); + if (UserFunction) // user is allowed to Mirror, TopDown, Rotate,...the image + UserFunction( Raw, FileInternal); ConvertHandleColor(); return true; @@ -328,7 +373,7 @@ bool PixelReadConvert::BuildRGBImage() AllocateRGB(); int j; - if( BitsAllocated <= 8 ) + if ( BitsAllocated <= 8 ) { uint8_t *localRGB = RGB; for (size_t i = 0; i < RawSize; ++i ) @@ -449,6 +494,7 @@ bool PixelReadConvert::ReadAndDecompressJPEGFile( std::ifstream *fp ) return true; } // wow what happen, must be an error + gdcmWarningMacro( "gdcm_read_JPEG2000_file() failed "); return false; } else if ( IsJPEGLS ) @@ -511,23 +557,31 @@ bool PixelReadConvert::ReadAndDecompressJPEGFile( std::ifstream *fp ) } /** - * \brief Build Red/Green/Blue/Alpha LUT from File - * when (0028,0004),Photometric Interpretation = [PALETTE COLOR ] - * and (0028,1101),(0028,1102),(0028,1102) - * - xxx Palette Color Lookup Table Descriptor - are found - * and (0028,1201),(0028,1202),(0028,1202) - * - xxx Palette Color Lookup Table Data - are found + * \brief Build Red/Green/Blue/Alpha LUT from File when : + * - (0028,0004) : Photometric Interpretation == [PALETTE COLOR ] + * and + * - (0028,1101),(0028,1102),(0028,1102) + * xxx Palette Color Lookup Table Descriptor are found + * and + * - (0028,1201),(0028,1202),(0028,1202) + * xxx Palette Color Lookup Table Data - are found * \warning does NOT deal with : - * 0028 1100 Gray Lookup Table Descriptor (Retired) - * 0028 1221 Segmented Red Palette Color Lookup Table Data - * 0028 1222 Segmented Green Palette Color Lookup Table Data - * 0028 1223 Segmented Blue Palette Color Lookup Table Data + * - 0028 1100 Gray Lookup Table Descriptor (Retired) + * - 0028 1221 Segmented Red Palette Color Lookup Table Data + * - 0028 1222 Segmented Green Palette Color Lookup Table Data + * - 0028 1223 Segmented Blue Palette Color Lookup Table Data * no known Dicom reader deals with them :-( * @return a RGBA Lookup Table */ void PixelReadConvert::BuildLUTRGBA() { + + // Note to code reviewers : + // The problem is *much more* complicated, since a lot of manufacturers + // Don't follow the norm : + // have a look at David Clunie's remark at the end of this .cxx file. if ( LutRGBA ) + { return; } @@ -557,7 +611,7 @@ void PixelReadConvert::BuildLUTRGBA() nbRead = sscanf( LutRedDescriptor.c_str(), "%d\\%d\\%d", &lengthR, &debR, &nbitsR ); - if( nbRead != 3 ) + if ( nbRead != 3 ) { gdcmWarningMacro( "Wrong Red LUT descriptor" ); } @@ -568,7 +622,7 @@ void PixelReadConvert::BuildLUTRGBA() nbRead = sscanf( LutGreenDescriptor.c_str(), "%d\\%d\\%d", &lengthG, &debG, &nbitsG ); - if( nbRead != 3 ) + if ( nbRead != 3 ) { gdcmWarningMacro( "Wrong Green LUT descriptor" ); } @@ -579,7 +633,7 @@ void PixelReadConvert::BuildLUTRGBA() nbRead = sscanf( LutRedDescriptor.c_str(), "%d\\%d\\%d", &lengthB, &debB, &nbitsB ); - if( nbRead != 3 ) + if ( nbRead != 3 ) { gdcmWarningMacro( "Wrong Blue LUT descriptor" ); } @@ -591,16 +645,16 @@ void PixelReadConvert::BuildLUTRGBA() gdcmWarningMacro(" lengthB " << lengthB << " debB " << debB << " nbitsB " << nbitsB); - if( !lengthR ) // if = 2^16, this shall be 0 see : CP-143 + if ( !lengthR ) // if = 2^16, this shall be 0 see : CP-143 lengthR=65536; - if( !lengthG ) // if = 2^16, this shall be 0 + if ( !lengthG ) // if = 2^16, this shall be 0 lengthG=65536; - if( !lengthB ) // if = 2^16, this shall be 0 + if ( !lengthB ) // if = 2^16, this shall be 0 lengthB=65536; //////////////////////////////////////////////////////// - if( ( ! LutRedData ) || ( ! LutGreenData ) || ( ! LutBlueData ) ) + if ( ( ! LutRedData ) || ( ! LutGreenData ) || ( ! LutBlueData ) ) { gdcmWarningMacro( "(At least) a LUT is missing" ); return; @@ -608,7 +662,7 @@ void PixelReadConvert::BuildLUTRGBA() // ------------------------------------------------------------- - if( BitsAllocated <= 8 ) + if ( BitsAllocated <= 8 ) { // forge the 4 * 8 Bits Red/Green/Blue/Alpha LUT LutRGBA = new uint8_t[ 1024 ]; // 256 * 4 (R, G, B, Alpha) @@ -619,7 +673,7 @@ void PixelReadConvert::BuildLUTRGBA() memset( LutRGBA, 0, 1024 ); int mult; - if( ( nbitsR == 16 ) && ( BitsAllocated == 8 ) ) + if ( ( nbitsR == 16 ) && ( BitsAllocated == 8 ) ) { // when LUT item size is different than pixel size mult = 2; // high byte must be = low byte @@ -726,6 +780,7 @@ void PixelReadConvert::BuildLUTRGBA() a16 += 4; } /* Just to 'see' the LUT, at debug time +// Don't remove this commented out code. a16=(uint16_t*)LutRGBA; for (int j=0;j<65536;j++) @@ -744,11 +799,54 @@ void PixelReadConvert::BuildLUTRGBA() void PixelReadConvert::ConvertSwapZone() { unsigned int i; + uint16_t localSwapCode = SwapCode; + + // If this file is 'ImplicitVR BigEndian PrivateGE Transfer Syntax', + // then the header is in little endian format and the pixel data is in + // big endian format. When reading the header, GDCM has already established + // a byte swapping code suitable for this machine to read the + // header. In TS::ImplicitVRBigEndianPrivateGE, this code will need + // to be switched in order to read the pixel data. This must be + // done REGARDLESS of the processor endianess! + // + // Example: Assume we are on a little endian machine. When + // GDCM reads the header, the header will match the machine + // endianess and the swap code will be established as a no-op. + // When GDCM reaches the pixel data, it will need to switch the + // swap code to do big endian to little endian conversion. + // + // Now, assume we are on a big endian machine. When GDCM reads the + // header, the header will be recognized as a different endianess + // than the machine endianess, and a swap code will be established + // to convert from little endian to big endian. When GDCM readers + // the pixel data, the pixel data endianess will now match the + // machine endianess. But we currently have a swap code that + // converts from little endian to big endian. In this case, we + // need to switch the swap code to a no-op. + // + // Therefore, in either case, if the file is in + // 'ImplicitVR BigEndian PrivateGE Transfer Syntax', then GDCM needs to switch + // the byte swapping code when entering the pixel data. - if( BitsAllocated == 16 ) + if ( IsPrivateGETransferSyntax ) + { + // PrivateGETransferSyntax only exists for 'true' Dicom images + // we assume there is no 'exotic' 32 bits endianess! + switch (localSwapCode) + { + case 1234: + localSwapCode = 4321; + break; + case 4321: + localSwapCode = 1234; + break; + } + } + + if ( BitsAllocated == 16 ) { uint16_t *im16 = (uint16_t*)Raw; - switch( SwapCode ) + switch( localSwapCode ) { case 1234: break; @@ -764,13 +862,13 @@ void PixelReadConvert::ConvertSwapZone() gdcmWarningMacro("SwapCode value (16 bits) not allowed."); } } - else if( BitsAllocated == 32 ) + else if ( BitsAllocated == 32 ) { uint32_t s32; uint16_t high; uint16_t low; uint32_t *im32 = (uint32_t*)Raw; - switch ( SwapCode ) + switch ( localSwapCode ) { case 1234: break; @@ -830,7 +928,7 @@ void PixelReadConvert::ConvertReorderEndianity() uint16_t *deb = (uint16_t *)Raw; for(int i = 0; i> (BitsStored - HighBitPosition - 1)) & pmask; deb++; } } - else + else // Pixels are signed { // smask : to check the 'sign' when BitsStored != BitsAllocated - uint16_t smask = 0x8000; - smask = smask >> ( BitsAllocated - BitsStored ); + uint16_t smask = 0x0001; + smask = smask << ( 16 - (BitsAllocated - BitsStored + 1) ); // nmask : to propagate sign bit on negative values - int16_t nmask = 0x8000; - nmask = nmask >> ( BitsAllocated - BitsStored ); - + int16_t nmask = (int16_t)0x8000; + nmask = nmask >> ( BitsAllocated - BitsStored - 1 ); + for(int i = 0; i> (BitsStored - HighBitPosition - 1); if ( *deb & smask ) + { *deb = *deb | nmask; + } else + { *deb = *deb & pmask; + } deb++; } } @@ -972,11 +1075,11 @@ bool PixelReadConvert::ConvertReArrangeBits() throw ( FormatError ) else { // smask : to check the 'sign' when BitsStored != BitsAllocated - uint32_t smask = 0x80000000; - smask = smask >> ( BitsAllocated - BitsStored ); + uint32_t smask = 0x00000001; + smask = smask >> ( 32 - (BitsAllocated - BitsStored +1 )); // nmask : to propagate sign bit on negative values int32_t nmask = 0x80000000; - nmask = nmask >> ( BitsAllocated - BitsStored ) + nmask = nmask >> ( BitsAllocated - BitsStored -1 ); for(int i = 0; i ConvertHandleColor" + gdcmDebugMacro("--> ConvertHandleColor " << "Planar Configuration " << PlanarConfiguration ); if ( ! IsRawRGB() ) { // [Planar 2] OR [Photo D]: LUT intervention done outside - gdcmWarningMacro("--> RawRGB : LUT intervention done outside"); + gdcmDebugMacro("--> RawRGB : LUT intervention done outside"); return; } @@ -1155,13 +1258,13 @@ void PixelReadConvert::ConvertHandleColor() if ( IsYBRFull ) { // [Planar 1] AND [Photo C] (remember YBR_FULL_422 acts as RGB) - gdcmWarningMacro("--> YBRFull"); + gdcmDebugMacro("--> YBRFull"); ConvertYcBcRPlanesToRGBPixels(); } else { // [Planar 1] AND [Photo C] - gdcmWarningMacro("--> YBRFull"); + gdcmDebugMacro("--> YBRFull"); ConvertRGBPlanesToRGBPixels(); } return; @@ -1172,7 +1275,7 @@ void PixelReadConvert::ConvertHandleColor() if (IsRLELossless) { - gdcmWarningMacro("--> RLE Lossless"); + gdcmDebugMacro("--> RLE Lossless"); ConvertRGBPlanesToRGBPixels(); } @@ -1267,13 +1370,160 @@ void PixelReadConvert::Print( std::ostream &os, std::string const &indent ) //----------------------------------------------------------------------------- } // end namespace gdcm -// NOTES on File internal calls -// User -// ---> GetImageData -// ---> GetImageDataIntoVector -// |---> GetImageDataIntoVectorRaw -// | lut intervention -// User -// ---> GetImageDataRaw -// ---> GetImageDataIntoVectorRaw +// Note to developpers : +// Here is a very detailled post from David Clunie, on the troubles caused +// 'non standard' LUT and LUT description +// We shall have to take it into accound in our code. +// Some day ... + + +/* +Subject: Problem with VOI LUTs in Agfa and Fuji CR and GE DX images, was Re: VOI LUT issues +Date: Sun, 06 Feb 2005 17:13:40 GMT +From: David Clunie +Reply-To: dclunie@dclunie.com +Newsgroups: comp.protocols.dicom +References: <1107553502.040221.189550@o13g2000cwo.googlegroups.com> + +> THE LUT that comes with [my] image claims to be 16-bit, but none of the +> values goes higher than 4095. That being said, though, none of my +> original pixel values goes higher than that, either. I have read +> elsewhere on this group that when that happens you are supposed to +> adjust the LUT. Can someone be more specific? There was a thread from +> 2002 where Marco and David were mentioning doing precisely that. +> +> Thanks +> +> -carlos rodriguez + + +You have encountered the well known "we know what the standard says but +we are going to ignore it and do what we have been doing for almost +a decade regardless" CR vendor bug. Agfa started this, but they are not +the only vendor doing this now; GE and Fuji may have joined the club. + +Sadly, one needs to look at the LUT Data, figure out what the maximum +value actually encoded is, and find the next highest power of 2 (e.g. +212 in this case), to figure out what the range of the data is +supposed to be. I have assumed that if the maximum value in the LUT +data is less than a power of 2 minus 1 (e.g. 0xebc) then the intent +of the vendor was not to use the maximum available grayscale range +of the display (e.g. the maximum is 0xfff in this case). An alternative +would be to scale to the actual maximum rather than a power of two. + +Very irritating, and in theory not totally reliable if one really +intended the full 16 bits and only used, say 15, but that is extremely +unlikely since everything would be too dark, and this heuristic +seems to work OK. + +There has never been anything in the standard that describes having +to go through these convolutions. Since the only value in the +standard that describes the bit depth of the LUT values is LUT +Descriptor value 3 and that is (usually) always required to be +either 8 or 16, it mystifies me how the creators' of these images +imagine that the receiver is going to divine the range that is intended. Further, the standard is quite explicit that this 3rd +value defines the range of LUT values, but as far as I am aware, all +the vendors are ignoring the standard and indeed sending a third value +of 16 in all cases. + +This problem is not confined to CR, and is also seen with DX products. + +Typically I have seen: + +- Agfa CR, which usually (always ?) sends LUTs, values up to 0x0fff +- Fuji CR, which occasionally send LUTs, values up to 0x03ff +- GE DX, for presentation, which always have LUTs, up to 0x3fff + +Swissray, Siemens, Philips, Canon and Kodak never seem to send VOI LUTs +at this point (which is a whole other problem). Note that the presence +or absence of a VOI LUT as opposed to window values may be configurable +on the modality in some cases, and I have just looked at what I happen +to have received from a myriad of sites over whose configuration I have +no control. This may be why the majority of Fuji images have no VOI LUTs, +but a few do (or it may be the Siemens system that these Fuji images went +through that perhaps added it). I do have some test Hologic DX images that +are not from a clinical site that do actually get this right (a value +of 12 for the third value and a max of 0xfff). + +Since almost every vendor that I have encountered that encodes LUTs +makes this mistake, perhaps it is time to amend the standard to warn +implementor's of receivers and/or sanction this bad behavior. We have +talked about this in the past in WG 6 but so far everyone has been +reluctant to write into the standard such a comment. Maybe it is time +to try again, since if one is not aware of this problem, one cannot +effectively implement display using VOI LUTs, and there is a vast +installed base to contend with. + +I did not check presentation states, in which VOI LUTs could also be +encountered, for the prevalence of this mistake, nor did I look at the +encoding of Modality LUT's, which are unusual. Nor did I check digital +mammography images. I would be interested to hear from anyone who has. + +David + +PS. The following older thread in this newsgroup discusses this: + +"http://groups-beta.google.com/group/comp.protocols.dicom/browse_frm/t hread/6a033444802a35fc/0f0a9a1e35c1468e?q=voi+lut&_done=%2Fgroup%2Fcom p.protocols.dicom%2Fsearch%3Fgroup%3Dcomp.protocols.dicom%26q%3Dvoi+lu t%26qt_g%3D1%26searchnow%3DSearch+this+group%26&_doneTitle=Back+to+Sea rch&&d#0f0a9a1e35c1468e" + +PPS. From a historical perspective, the following may be of interest. + +In the original standard in 1993, all that was said about this was a +reference to the corresponding such where Modality LUTs are described +that said: + +"The third value specifies the number of bits for each entry in the +LUT Data. It shall take the value 8 or 16. The LUT Data shall be stored +in a format equivalent to 8 or 16 bits allocated and high bit equal +1-bits allocated." + +Since the high bit hint was not apparently explicit enough, a very +early CP, CP 15 (submitted by Agfa as it happens), replaced this with: + +"The third value conveys the range of LUT entry values. It shall take +the value 8 or 16, corresponding with the LUT entry value range of +256 or 65536. + +Note: The third value is not required for describing the + LUT data and is only included for informational usage + and for maintaining compatibility with ACRNEMA 2.0. + +The LUT Data contains the LUT entry values." + +That is how it read in the 1996, 1998 and 1999 editions. + +By the 2000 edition, Supplement 33 that introduced presentation states +extensively reworked this entire section and tried to explain this in +different words: + +"The output range is from 0 to 2^n-1 where n is the third value of LUT +Descriptor. This range is always unsigned." + +and also added a note to spell out what the output range meant in the +VOI LUT section: + +"9. The output of the Window Center/Width or VOI LUT transformation +is either implicitly scaled to the full range of the display device +if there is no succeeding transformation defined, or implicitly scaled +to the full input range of the succeeding transformation step (such as +the Presentation LUT), if present. See C.11.6.1." + +It still reads this way in the 2004 edition. +Note that LUTs in other applications than the general VOI LUT allow for +values other than 8 or 16 in the third value of LUT descriptor to permit +ranges other than 0 to 255 or 65535. + +In addition, the DX Image Module specializes the VOI LUT +attributes as follows, in PS 3.3 section C.8.11.3.1.5 (added in Sup 32): + +"The third value specifies the number of bits for each entry in the LUT +Data (analogous to ìbits storedî). It shall be between 10-16. The LUT +Data shall be stored in a format equivalent to 16 ìbits allocatedî and +ìhigh bitî equal to ìbits storedî - 1. The third value conveys the range +of LUT entry values. These unsigned LUT entry values shall range between +0 and 2^n-1, where n is the third value of the LUT Descriptor." + +So in the case of the GE DX for presentation images, the third value of +LUT descriptor is allowed to be and probably should be 14 rather than 16. + +*/