Browse Source

robust spectral verification

pull/322/head
Anton Firszov 9 years ago
parent
commit
9012fbfcaf
  1. 90
      tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs
  2. 46
      tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs
  3. 176
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  4. 26
      tests/ImageSharp.Tests/Formats/Jpg/VerifyJpeg.cs
  5. 1
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj

90
tests/ImageSharp.Tests/Formats/Jpg/LibJpegTools.cs

@ -7,9 +7,7 @@ namespace SixLabors.ImageSharp.Tests
using System.Linq;
using System.Numerics;
using System.Reflection;
using BitMiracle.LibJpeg.Classic;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
@ -55,59 +53,7 @@ namespace SixLabors.ImageSharp.Tests
this.ComponentCount = components.Length;
this.Components = components;
}
public static SpectralData Load(jpeg_decompress_struct cinfo)
{
//short[][][] result = new short[cinfo.Image_height][][];
//int blockPerMcu = (int)GetNonPublicMember(cinfo, "m_blocks_in_MCU");
//int mcuPerRow = (int)GetNonPublicMember(cinfo, "m_MCUs_per_row");
//int mcuRows = (int)GetNonPublicMember(cinfo, "m_MCU_rows_in_scan");
object coefController = GetNonPublicMember(cinfo, "m_coef");
Array wholeImage = (Array)GetNonPublicMember(coefController, "m_whole_image");
var result = new SpectralData(wholeImage);
return result;
}
public static SpectralData Load(Stream fileStream)
{
jpeg_error_mgr err = new jpeg_error_mgr();
jpeg_decompress_struct cinfo = new jpeg_decompress_struct(err);
cinfo.jpeg_stdio_src(fileStream);
cinfo.jpeg_read_header(true);
cinfo.Buffered_image = true;
cinfo.Do_block_smoothing = false;
cinfo.jpeg_start_decompress();
var output = CreateOutputArray(cinfo);
for (int scan = 0; scan < cinfo.Input_scan_number; scan++)
{
cinfo.jpeg_start_output(scan);
for (int i = 0; i < cinfo.Image_height; i++)
{
int numScanlines = cinfo.jpeg_read_scanlines(output, 1);
if (numScanlines != 1) throw new Exception("?");
}
}
var result = SpectralData.Load(cinfo);
return result;
}
private static byte[][] CreateOutputArray(jpeg_decompress_struct cinfo)
{
byte[][] output = new byte[cinfo.Image_height][];
for (int i = 0; i < cinfo.Image_height; i++)
{
output[i] = new byte[cinfo.Image_width * cinfo.Num_components];
}
return output;
}
public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder)
{
FrameComponent[] srcComponents = decoder.Frame.Components;
@ -434,27 +380,37 @@ namespace SixLabors.ImageSharp.Tests
return fi.GetValue(obj);
}
public static double CalculateAverageDifference(ComponentData a, ComponentData b)
public static (double total, double average) CalculateDifference(ComponentData expected, ComponentData actual)
{
BigInteger totalDiff = 0;
if (a.Size != b.Size)
if (actual.WidthInBlocks < expected.WidthInBlocks)
{
throw new Exception("a.Size != b.Size");
throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks");
}
int count = a.Blocks.Length;
if (actual.HeightInBlocks < expected.HeightInBlocks)
{
throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks");
}
for (int i = 0; i < count; i++)
int w = expected.WidthInBlocks;
int h = expected.HeightInBlocks;
for (int y = 0; y < h; y++)
{
Block8x8 aa = a.Blocks[i];
Block8x8 bb = b.Blocks[i];
for (int x = 0; x < w; x++)
{
Block8x8 aa = expected.Blocks[x, y];
Block8x8 bb = actual.Blocks[x, y];
long diff = Block8x8.TotalDifference(ref aa, ref bb);
totalDiff += diff;
long diff = Block8x8.TotalDifference(ref aa, ref bb);
totalDiff += diff;
}
}
double result = (double)totalDiff;
return result / (count * Block8x8.Size);
int count = w * h;
double total = (double)totalDiff;
double average = (double)totalDiff / (count * Block8x8.Size);
return (total, average);
}
private static string DumpToolFullPath => Path.Combine(

46
tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs

@ -0,0 +1,46 @@
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
public class LibJpegToolsTests
{
[Fact]
public void RunDumpJpegCoeffsTool()
{
if (!TestEnvironment.IsWindows) return;
string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress);
string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests));
string outputFile = Path.Combine(outputDir, "progress.dctdump");
LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile);
Assert.True(File.Exists(outputFile));
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)]
public void ExtractSpectralData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string testImage = provider.SourceFileOrDescription;
LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage);
Assert.True(data.ComponentCount == 3);
Assert.True(data.Components.Length == 3);
VerifyJpeg.SaveSpectralImage(provider, data);
// I knew this one well:
if (testImage == TestImages.Jpeg.Progressive.Progress)
{
VerifyJpeg.Components3(data.Components, 43, 61, 22, 31, 22, 31);
}
}
}
}

176
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -43,93 +43,7 @@ namespace SixLabors.ImageSharp.Tests
};
public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray();
[Fact]
public void RunDumpJpegCoeffsTool()
{
if (!TestEnvironment.IsWindows) return;
string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress);
string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests));
string outputFile = Path.Combine(outputDir, "progress.dctdump");
LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile);
Assert.True(File.Exists(outputFile));
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)]
public void ExtractSpectralData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string testImage = provider.SourceFileOrDescription;
LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage);
Assert.True(data.ComponentCount == 3);
Assert.True(data.Components.Length == 3);
this.SaveSpectralImage(provider, data);
// I knew this one well:
if (testImage == TestImages.Jpeg.Progressive.Progress)
{
VerifyJpeg.Components3(data.Components, 43, 61, 22, 31, 22, 31);
}
}
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void BuildLibJpegSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
LibJpegTools.SpectralData data = LibJpegTools.SpectralData.Load(ms);
Assert.True(data.ComponentCount > 0);
this.Output.WriteLine($"ComponentCount: {data.ComponentCount}");
this.SaveSpectralImage(provider, data);
}
}
[Theory]
//[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)]
public void HelloSerializedSpectralData<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
//LibJpegTools.SpectralData data = LibJpegTools.SpectralData.Load(ms);
OldJpegDecoderCore dec = new OldJpegDecoderCore(Configuration.Default, new JpegDecoder());
dec.ParseStream(new MemoryStream(sourceBytes));
LibJpegTools.SpectralData data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(dec);
Assert.True(data.ComponentCount > 0);
this.Output.WriteLine($"ComponentCount: {data.ComponentCount}");
string comp0FileName = TestFile.GetInputFileFullPath(provider.SourceFileOrDescription + ".comp0");
if (!File.Exists(comp0FileName))
{
this.Output.WriteLine("Missing file: " + comp0FileName);
}
byte[] stuff = File.ReadAllBytes(comp0FileName);
ref Block8x8 actual = ref Unsafe.As<byte, Block8x8>(ref stuff[0]);
ref Block8x8 expected = ref data.Components[0].Blocks[0];
Assert.Equal(actual, expected);
//this.SaveSpectralImage(provider, data);
}
}
[Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void PdfJsDecoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
@ -144,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests
decoder.ParseStream(ms);
var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.SaveSpectralImage(provider, data);
VerifyJpeg.SaveSpectralImage(provider, data);
}
}
@ -162,46 +76,52 @@ namespace SixLabors.ImageSharp.Tests
decoder.ParseStream(ms, false);
var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.SaveSpectralImage(provider, data);
VerifyJpeg.SaveSpectralImage(provider, data);
}
}
private void VerifySpectralCorrectness<TPixel>(
MemoryStream ms,
TestImageProvider<TPixel> provider,
LibJpegTools.SpectralData imageSharpData)
where TPixel : struct, IPixel<TPixel>
{
ms.Seek(0, SeekOrigin.Begin);
var libJpegData = LibJpegTools.SpectralData.Load(ms);
var libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription);
bool equality = libJpegData.Equals(imageSharpData);
this.Output.WriteLine("Spectral data equality: " + equality);
//if (!equality)
//{
int componentCount = imageSharpData.ComponentCount;
if (libJpegData.ComponentCount != componentCount)
{
throw new Exception("libJpegData.ComponentCount != componentCount");
}
double totalDifference = 0;
this.Output.WriteLine("*** Differences ***");
for (int i = 0; i < componentCount; i++)
{
LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i];
LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i];
int componentCount = imageSharpData.ComponentCount;
if (libJpegData.ComponentCount != componentCount)
{
throw new Exception("libJpegData.ComponentCount != componentCount");
}
double d = LibJpegTools.CalculateAverageDifference(libJpegComponent, imageSharpComponent);
double averageDifference = 0;
double totalDifference = 0;
double tolerance = 0;
this.Output.WriteLine($"Component{i}: {d}");
totalDifference += d;
}
totalDifference /= componentCount;
this.Output.WriteLine($"AVERAGE: {totalDifference}");
//}
this.Output.WriteLine("*** Differences ***");
for (int i = 0; i < componentCount; i++)
{
LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i];
LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i];
var d = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent);
this.Output.WriteLine($"Component{i}: {d}");
averageDifference += d.average;
totalDifference += d.total;
tolerance += libJpegComponent.Blocks.Length;
}
averageDifference /= componentCount;
Assert.Equal(libJpegData, imageSharpData);
tolerance /= 64; // fair enough?
this.Output.WriteLine($"AVERAGE: {averageDifference}");
this.Output.WriteLine($"TOTAL: {totalDifference}");
this.Output.WriteLine($"TOLERANCE = totalNumOfBlocks / 64 = {tolerance}");
Assert.True(totalDifference < tolerance);
}
[Theory]
@ -218,7 +138,7 @@ namespace SixLabors.ImageSharp.Tests
decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.VerifySpectralCorrectness<TPixel>(ms, imageSharpData);
this.VerifySpectralCorrectness<TPixel>(provider, imageSharpData);
}
}
@ -236,34 +156,8 @@ namespace SixLabors.ImageSharp.Tests
decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.VerifySpectralCorrectness<TPixel>(ms, imageSharpData);
this.VerifySpectralCorrectness<TPixel>(provider, imageSharpData);
}
}
private void SaveSpectralImage<TPixel>(TestImageProvider<TPixel> provider, LibJpegTools.SpectralData data)
where TPixel : struct, IPixel<TPixel>
{
foreach (LibJpegTools.ComponentData comp in data.Components)
{
this.Output.WriteLine("Min: " + comp.MinVal);
this.Output.WriteLine("Max: " + comp.MaxVal);
using (Image<Rgba32> image = comp.CreateGrayScaleImage())
{
string details = $"C{comp.Index}";
image.DebugSave(provider, details, appendPixelTypeToFileName: false);
}
}
Image<Rgba32> fullImage = data.TryCreateRGBSpectralImage();
if (fullImage != null)
{
fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false);
fullImage.Dispose();
}
}
}
}

26
tests/ImageSharp.Tests/Formats/Jpg/VerifyJpeg.cs

@ -4,8 +4,10 @@ namespace SixLabors.ImageSharp.Tests
using System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
using Xunit.Abstractions;
internal static class VerifyJpeg
{
@ -28,5 +30,29 @@ namespace SixLabors.ImageSharp.Tests
ComponentSize(c[1], xBc1, yBc1);
ComponentSize(c[2], xBc2, yBc2);
}
internal static void SaveSpectralImage<TPixel>(TestImageProvider<TPixel> provider, LibJpegTools.SpectralData data, ITestOutputHelper output = null)
where TPixel : struct, IPixel<TPixel>
{
foreach (LibJpegTools.ComponentData comp in data.Components)
{
output?.WriteLine("Min: " + comp.MinVal);
output?.WriteLine("Max: " + comp.MaxVal);
using (Image<Rgba32> image = comp.CreateGrayScaleImage())
{
string details = $"C{comp.Index}";
image.DebugSave(provider, details, appendPixelTypeToFileName: false);
}
}
Image<Rgba32> fullImage = data.TryCreateRGBSpectralImage();
if (fullImage != null)
{
fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false);
fullImage.Dispose();
}
}
}
}

1
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -16,7 +16,6 @@
<None Include="PixelFormats\PixelOperationsTests.Blender.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BitMiracle.LibJpeg.NET" Version="1.4.280" />
<PackageReference Include="CoreCompat.System.Drawing" Version="1.0.0-beta006" />
<PackageReference Include="xunit" Version="2.3.0-beta4-build3742" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta4-build3742" />

Loading…
Cancel
Save