Our Company has a regular need to save quite large colour line scan images and gray scale CT images. Our customers do not want the storage to be lossy, so to date we've always stored the images using ImageEN to create 16-bit tiff files.
For some customers it would be a huge storage advantage to be able to store non-lossy Jpeg 2000 files, but I've had problems doing this due to unexpected image corruption. I've created two Delphi methods to demonstrate problems I've had with 48-bit RGB colour and 16-bit gray scale storing of JP2 data with ImageEN.
48-bit RGB
The method generates and then attempts to save a 16-bit 5000x60000 RGB image in JP2 format.
procedure ImageEN_JP2_RGB48Test;
var
Image: TImageEnIO;
Bitmap: TIEBitmap;
C, X, Y, Z: Integer;
Pixels: PWordArray;
IsError: Boolean;
Value, MinErrorVal, MaxErrorVal: Word;
ErrorStartX, ErrorStartY, ErrorStartZ: Integer;
T: UInt64;
const
CMaxRows = 60000;
CMaxColumns = 5000;
CFileName = 'TestImage.jp2';
CDllName = 'ielib64.dll';
begin
IEGlobalSettings.JPEG2000Engine := ieenDLL;
with Memo1.Lines do
begin
Clear;
Add('Testing TImageEnIO saving of a large 48-bit RGB image to JP2');
Add(String.Empty);
Image := TImageEnIO.Create(nil);
try
if not TFile.Exists(CDllName) then
begin
Add('Place ' + CDllName + ' alongside this executable');
exit;
end;
Add(CDllName + ' file version: ' + GetEXEVersionData(CDllName).FileVersion);
Add('ImageEnVersion: ' + Image.ImageEnVersion);
Add(Format('Creating a %d x %d RGB image', [CMaxColumns, CMaxRows]));
Image.NativePixelFormat := True;
Bitmap := TIEBitmap.Create(CMaxColumns, CMaxRows, ie48RGB);
Image.AttachedIEBitmap := Bitmap;
Image.Params.J2000_ColorSpace := ioJ2000_RGB;
Image.Params.J2000_Rate := 1;
Image.Params.J2000_Quality := C100_Percent;
Image.Params.BitsPerSample := 16;
Image.Params.SamplesPerPixel := 3;
Image.Params.Width := CMaxColumns;
Image.Params.Height := CMaxRows;
Value := 0;
for Y := 0 to Image.Params.Height - 1 do
begin
Pixels := Image.IEBitmap.ScanLine[Y];
begin
for X := 0 to Image.Params.Width - 1 do
begin
for Z := 0 to Image.Params.SamplesPerPixel - 1 do
begin
Pixels[X * 3 + Z] := Value;
Inc(Value);
end;
end;
end;
end;
Add('Checking for created image metadata');
C := Count;
try
if Image.IEBitmap.PixelFormat <> ie48RGB then
Add('Unexpected Image.IEBitmap.PixelFormat');
if Image.IEBitmap.Width <> CMaxColumns then
Add('Unexpected Image.IEBitmap.Width');
if Image.IEBitmap.Height <> CMaxRows then
Add('Unexpected Image.IEBitmap.Height');
if Image.Params.J2000_ColorSpace <> ioJ2000_RGB then
Add('Unexpected Image.Params.J2000_ColorSpace');
if Image.Params.BitsPerSample <> 16 then
Add('Unexpected Image.Params.BitsPerSample');
if Image.Params.SamplesPerPixel <> 3 then
Add('Unexpected Image.Params.SamplesPerPixel');
if Image.Params.Width <> CMaxColumns then
Add('Unexpected Image.Params.Width');
if Image.Params.Height <> CMaxRows then
Add('Unexpected Image.Params.Height');
finally
Add(Format('%d metadata errors were found', [Count - C]));
end;
Add('Saving the image to ' + CFileName);
if TFile.Exists(CFileName) then
TFile.Delete(CFileName);
T := GetTickCount64;
try
Image.SaveToFile(CFileName);
finally
Add(Format('File saving took %f seconds', [(GetTickCount64 - T) / 1000]));
end;
Add('Checking for metadata corruption after saving');
C := Count;
try
if Image.IEBitmap.PixelFormat <> ie48RGB then
Add('Unexpected Image.IEBitmap.PixelFormat');
if Image.IEBitmap.Width <> CMaxColumns then
Add('Unexpected Image.IEBitmap.Width');
if Image.IEBitmap.Height <> CMaxRows then
Add('Unexpected Image.IEBitmap.Height');
if Image.Params.J2000_ColorSpace <> ioJ2000_RGB then
Add('Unexpected Image.Params.J2000_ColorSpace');
if Image.Params.BitsPerSample <> 16 then
Add('Unexpected Image.Params.BitsPerSample');
if Image.Params.SamplesPerPixel <> 3 then
Add('Unexpected Image.Params.SamplesPerPixel');
if Image.Params.Width <> CMaxColumns then
Add('Unexpected Image.Params.Width');
if Image.Params.Height <> CMaxRows then
Add('Unexpected Image.Params.Height');
finally
Add(Format('%d metadata errors were found', [Count - C]));
end;
finally
Image.Free;
end;
Image := TImageEnIO.Create(nil);
try
Image.NativePixelFormat := True;
Add('Loading the image from ' + CFileName);
T := GetTickCount64;
try
Image.LoadFromFile(CFileName);
finally
Add(Format('File loading took %f seconds', [(GetTickCount64 - T) / 1000]));
end;
Add('Checking the loaded image metadata');
C := Count;
try
if Image.IEBitmap.PixelFormat <> ie48RGB then
Add('Unexpected Image.IEBitmap.PixelFormat');
if Image.IEBitmap.Width <> CMaxColumns then
Add('Unexpected Image.IEBitmap.Width');
if Image.IEBitmap.Height <> CMaxRows then
Add('Unexpected Image.IEBitmap.Height');
if Image.Params.J2000_ColorSpace <> ioJ2000_RGB then
Add('Unexpected Image.Params.J2000_ColorSpace');
if Image.Params.BitsPerSample <> 16 then
Add('Unexpected Image.Params.BitsPerSample');
if Image.Params.SamplesPerPixel <> 3 then
Add('Unexpected Image.Params.SamplesPerPixel');
if Image.Params.Width <> CMaxColumns then
Add('Unexpected Image.Params.Width');
if Image.Params.Height <> CMaxRows then
Add('Unexpected Image.Params.Height');
finally
Add(Format('%d metadata errors were found', [Count - C]));
end;
Add('Checking the image for pixel errors');
IsError := False;
ErrorStartX := 0;
ErrorStartY := 0;
ErrorStartZ := 0;
MinErrorVal := Word.MaxValue;
MaxErrorVal := Word.MinValue;
try
Value := 0;
for Y := 0 to Image.Params.Height - 1 do
begin
Pixels := Image.IEBitmap.ScanLine[Y];
begin
for X := 0 to Image.Params.Width - 1 do
begin
for Z := 0 to Image.Params.SamplesPerPixel - 1 do
begin
if (not IsError) and (Pixels[X * 3 + Z] <> Value) then
begin
ErrorStartX := X;
ErrorStartY := Y;
ErrorStartZ := Z;
MinErrorVal := Word.MaxValue;
MaxErrorVal := Word.MinValue;
IsError := True;
end;
if IsError then
begin
MinErrorVal := Min(MinErrorVal, Pixels[X * 3 + Z]);
MaxErrorVal := Max(MaxErrorVal, Pixels[X * 3 + Z]);
end;
Inc(Value);
end;
end;
end;
end;
finally
if not IsError then
Add('There were no image pixel errors')
else
begin
Add(Format('Image pixel errors start at row %d column %d word %d', [ErrorStartY, ErrorStartX, ErrorStartZ]));
Add(Format('Erroroneous pixel values ranged from %d to %d', [Integer(MinErrorVal), Integer(MaxErrorVal)]));
end;
end;
Add(String.Empty);
Add('Testing has ended');
finally
FreeAndNil(Image);
FreeAndNil(Bitmap);
end;
end;
end;
This code generates the following output:
Testing TImageEnIO saving of a large 48-bit RGB image to JP2
ielib64.dll file version: 6.0.0.0
ImageEnVersion: 10.1.0
Creating a 5000 x 60000 RGB image
Checking for created image metadata
0 metadata errors were found
Saving the image to TestImage.jp2
File saving took 39.74 seconds
Checking for metadata corruption after saving
0 metadata errors were found
Loading the image from TestImage.jp2
File loading took 23.64 seconds
Checking the loaded image metadata
0 metadata errors were found
Checking the image for pixel errors
Image pixel errors start at row 59995 column 0 word 0
Erroroneous pixel values ranged from 0 to 0
Testing has ended
The error is simply that the last few pixel rows are all unexpectedly zero. This doesn't happen if the number of pixel rows is less than 20000, but seems to be related to the size of the image as the same thing happens if the number of columns is made much larger.
16-bit gray scale
The method is very similar to the 48-bit RGB method, generating and then attempting to save a 16-bit 9000x100000 gray scale image in JP2 format. The size of the image is larger because the corruption doesn't happen with a 5000x60000 gray scale image, so maybe the problem is related to the overall image size rather than dimensions.
procedure ImageEN_JP2_G16Test;
var
Image: TImageEnIO;
Bitmap: TIEBitmap;
C, X, Y: Integer;
Pixels: PWordArray;
IsError: Boolean;
Value, MinErrorVal, MaxErrorVal: Word;
ErrorStartX, ErrorStartY: Integer;
T: UInt64;
const
CMaxRows = 100000;
CMaxColumns = 9000;
CFileName = 'TestImage.jp2';
CDllName = 'ielib64.dll';
begin
IEGlobalSettings.JPEG2000Engine := ieenDLL;
with Memo1.Lines do
begin
Clear;
Add('Testing TImageEnIO saving of a large 16-bit gray scale image to JP2');
Add(String.Empty);
Image := TImageEnIO.Create(nil);
try
if not TFile.Exists(CDllName) then
begin
Add('Place ' + CDllName + ' alongside this executable');
exit;
end;
Add(CDllName + ' file version: ' + GetEXEVersionData(CDllName).FileVersion);
Add('ImageEnVersion: ' + Image.ImageEnVersion);
Add(Format('Creating a %d x %d gray scale image', [CMaxColumns, CMaxRows]));
Image.NativePixelFormat := True;
Bitmap := TIEBitmap.Create(CMaxColumns, CMaxRows, ie16G);
Image.AttachedIEBitmap := Bitmap;
Image.Params.J2000_ColorSpace := ioJ2000_GRAYLEV;
Image.Params.J2000_Rate := 1;
Image.Params.J2000_Quality := C100_Percent;
Image.Params.BitsPerSample := 16;
Image.Params.SamplesPerPixel := 1;
Image.Params.Width := CMaxColumns;
Image.Params.Height := CMaxRows;
Value := 0;
for Y := 0 to Image.Params.Height - 1 do
begin
Pixels := Image.IEBitmap.ScanLine[Y];
for X := 0 to Image.Params.Width - 1 do
begin
Pixels[X] := Value;
Inc(Value);
end;
end;
Add('Checking for created image metadata');
C := Count;
try
if Image.IEBitmap.PixelFormat <> ie16G then
Add('Unexpected Image.IEBitmap.PixelFormat');
if Image.IEBitmap.Width <> CMaxColumns then
Add('Unexpected Image.IEBitmap.Width');
if Image.IEBitmap.Height <> CMaxRows then
Add('Unexpected Image.IEBitmap.Height');
if Image.Params.J2000_ColorSpace <> ioJ2000_GRAYLEV then
Add('Unexpected Image.Params.J2000_ColorSpace');
if Image.Params.BitsPerSample <> 16 then
Add('Unexpected Image.Params.BitsPerSample');
if Image.Params.SamplesPerPixel <> 1 then
Add('Unexpected Image.Params.SamplesPerPixel');
if Image.Params.Width <> CMaxColumns then
Add('Unexpected Image.Params.Width');
if Image.Params.Height <> CMaxRows then
Add('Unexpected Image.Params.Height');
finally
Add(Format('%d metadata errors were found', [Count - C]));
end;
Add('Saving the image to ' + CFileName);
if TFile.Exists(CFileName) then
TFile.Delete(CFileName);
T := GetTickCount64;
try
Image.SaveToFile(CFileName);
finally
Add(Format('File saving took %f seconds', [(GetTickCount64 - T) / 1000]));
end;
Add('Checking for metadata corruption after saving');
C := Count;
try
if Image.IEBitmap.PixelFormat <> ie16G then
Add('Unexpected Image.IEBitmap.PixelFormat');
if Image.IEBitmap.Width <> CMaxColumns then
Add('Unexpected Image.IEBitmap.Width');
if Image.IEBitmap.Height <> CMaxRows then
Add('Unexpected Image.IEBitmap.Height');
if Image.Params.J2000_ColorSpace <> ioJ2000_GRAYLEV then
Add('Unexpected Image.Params.J2000_ColorSpace');
if Image.Params.BitsPerSample <> 16 then
Add('Unexpected Image.Params.BitsPerSample');
if Image.Params.SamplesPerPixel <> 1 then
Add('Unexpected Image.Params.SamplesPerPixel');
if Image.Params.Width <> CMaxColumns then
Add('Unexpected Image.Params.Width');
if Image.Params.Height <> CMaxRows then
Add('Unexpected Image.Params.Height');
finally
Add(Format('%d metadata errors were found', [Count - C]));
end;
finally
Image.Free;
end;
Image := TImageEnIO.Create(nil);
try
Image.NativePixelFormat := True;
Add('Loading the image from ' + CFileName);
T := GetTickCount64;
try
Image.LoadFromFile(CFileName);
finally
Add(Format('File loading took %f seconds', [(GetTickCount64 - T) / 1000]));
end;
Add('Checking the loaded image metadata');
C := Count;
try
if Image.IEBitmap.PixelFormat <> ie16G then
Add('Unexpected Image.IEBitmap.PixelFormat');
if Image.IEBitmap.Width <> CMaxColumns then
Add('Unexpected Image.IEBitmap.Width');
if Image.IEBitmap.Height <> CMaxRows then
Add('Unexpected Image.IEBitmap.Height');
if Image.Params.J2000_ColorSpace <> ioJ2000_GRAYLEV then
Add('Unexpected Image.Params.J2000_ColorSpace');
if Image.Params.BitsPerSample <> 16 then
Add('Unexpected Image.Params.BitsPerSample');
if Image.Params.SamplesPerPixel <> 1 then
Add('Unexpected Image.Params.SamplesPerPixel');
if Image.Params.Width <> CMaxColumns then
Add('Unexpected Image.Params.Width');
if Image.Params.Height <> CMaxRows then
Add('Unexpected Image.Params.Height');
finally
Add(Format('%d metadata errors were found', [Count - C]));
end;
Add('Checking the image for pixel errors');
IsError := False;
ErrorStartX := 0;
ErrorStartY := 0;
MinErrorVal := Word.MaxValue;
MaxErrorVal := Word.MinValue;
try
Value := 0;
for Y := 0 to Image.Params.Height - 1 do
begin
Pixels := Image.IEBitmap.ScanLine[Y];
begin
for X := 0 to Image.Params.Width - 1 do
begin
if (not IsError) and (Pixels[X] <> Value) then
begin
ErrorStartX := X;
ErrorStartY := Y;
MinErrorVal := Word.MaxValue;
MaxErrorVal := Word.MinValue;
IsError := True;
end;
if IsError then
begin
MinErrorVal := Min(MinErrorVal, Pixels[X]);
MaxErrorVal := Max(MaxErrorVal, Pixels[X]);
end;
Inc(Value);
end;
end;
end;
finally
if not IsError then
Add('There were no image pixel errors')
else
begin
Add(Format('Image pixel errors start at row %d column %d', [ErrorStartY, ErrorStartX]));
Add(Format('Erroroneous pixel values ranged from %d to %d', [Integer(MinErrorVal), Integer(MaxErrorVal)]));
end;
end;
Add(String.Empty);
Add('Testing has ended');
finally
FreeAndNil(Image);
FreeAndNil(Bitmap);
end;
end;
end;
This code generates the following output:
Testing TImageEnIO saving of a large 16-bit gray scale image to JP2
ielib64.dll file version: 6.0.0.0
ImageEnVersion: 10.1.0
Creating a 9000 x 100000 gray scale image
Checking for created image metadata
0 metadata errors were found
Saving the image to TestImage.jp2
File saving took 64.64 seconds
Checking for metadata corruption after saving
Unexpected Image.IEBitmap.PixelFormat
1 metadata errors were found
Loading the image from TestImage.jp2
File loading took 27.77 seconds
Checking the loaded image metadata
0 metadata errors were found
Checking the image for pixel errors
Image pixel errors start at row 99996 column 0
Erroroneous pixel values ranged from 0 to 0
Testing has ended
Again the main error is that the last few pixel rows are all unexpectedly zero. In addition notice that for gray-scale images the data has been corrupted immediately after the save, as the IEBitmap has been unexpectedly converted to 48-bit RGB format.
We're using the very latest ImageEN version downloaded yesterday, with the latest ielib64.dll library (file version 6.0.0.0 according to its properties in Windows). The 32-bit version of the same dll library appears to generate identical results and problems.
I'm hope you're able to help with fixes very soon.