X-Git-Url: https://git.creatis.insa-lyon.fr/pubgit/?a=blobdiff_plain;f=src%2FgdcmPixelReadConvert.cxx;h=19317dbfac06611cf78495a6fc0e7955d99227d5;hb=4073467af614547a6cdd2b5d09d481fbea1a1f0c;hp=a82fbae4930805f1505a8f832d25a1573b0bee8e;hpb=fff2901e2f9b0f65aabb20358dce8b39c918b04a;p=gdcm.git diff --git a/src/gdcmPixelReadConvert.cxx b/src/gdcmPixelReadConvert.cxx index a82fbae4..19317dbf 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 08:00:29 $ - Version: $Revision: 1.70 $ + Date: $Date: 2005/10/26 07:15:31 $ + Version: $Revision: 1.90 $ Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de l'Image). All rights reserved. See Doc/License.txt or @@ -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,51 @@ 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(); + + +// if ( ts == GDCM_UNKNOWN ) +// { +// gdcmErrorMacro( "Could someone tell me how in the world could this happen !" ); +// --> +//--> on ALL acr-nema images ! JPRx +//--> +// abort(); // DO NOT REMOVE. WE SHOULD NEVER READ SUCH IMAGE EVER (only gdcm can write such broekn dicom file) +// } + + IsRaw = + ( ! file->IsDicomV3() ) // Should be ACR-NEMA file + || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRLittleEndian + || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRBigEndianPrivateGE + || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ExplicitVRLittleEndian + || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ExplicitVRBigEndian + || Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::DeflatedExplicitVRLittleEndian; + + IsPrivateGETransferSyntax = + ( Global::GetTS()->GetSpecialTransferSyntax(ts) == TS::ImplicitVRBigEndianPrivateGE ); + + 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); + } PixelOffset = file->GetPixelOffset(); PixelDataLength = file->GetPixelAreaLength(); @@ -144,10 +175,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 ); + // 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 +191,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 +201,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 +209,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 +217,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 +242,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 +262,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 +284,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 +295,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, Raw, PixelDataLength); return true; } else @@ -277,6 +314,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 +367,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 ) @@ -511,23 +550,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 +604,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 +615,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 +626,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 +638,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 +655,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 +666,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 +773,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 +792,56 @@ 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 ) +/* //Let me check something. + //I wait for the Dashboard ! + 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 +857,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 +923,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 +1070,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 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. + +*/