Browse Source

Verify HEVC intra prediction against ITU

pull/2633/head
James Jackson-South 4 days ago
parent
commit
9bc5b98429
  1. 94
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs
  2. 64
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs
  3. 3
      tests/ImageSharp.Tests/TestImages.cs
  4. 3
      tests/Images/Input/Heif/Hevc/Conformance/CIP_A_Panasonic_3.bit
  5. 3
      tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit
  6. 3
      tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv
  7. 7
      tests/Images/Input/Heif/Hevc/Conformance/README.md

94
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs

@ -3,6 +3,7 @@
using System.Numerics;
using SixLabors.ImageSharp.Formats.Heif.Hevc;
using SixLabors.ImageSharp.Tests.TestUtilities;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc;
@ -12,6 +13,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc;
[Trait("Format", "Heic")]
public class HevcIntraPredictorTests
{
/// <summary>
/// The hardware configurations required to exercise each SIMD tier and the complete scalar fallback.
/// </summary>
private const HwIntrinsics PredictorConfigurations =
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic;
/// <summary>
/// Verifies fixed four-by-four prediction results derived from the HEVC intra-prediction equations.
/// </summary>
@ -92,10 +99,23 @@ public class HevcIntraPredictorTests
}
/// <summary>
/// Verifies that eligible thirty-two-sample references use strong bilinear smoothing rather than local three-tap filtering.
/// Verifies strong bilinear and normal three-tap reference filtering through every SIMD tier and the scalar fallback.
/// </summary>
[Fact]
public void ReferenceFiltersMatchScalarDefinitionsAcrossIntrinsicWidths()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateReferenceFilters, PredictorConfigurations);
/// <summary>
/// Compares every prediction mode and block width with a specification-shaped scalar oracle through every SIMD tier.
/// </summary>
[Fact]
public void StrongSmoothingReplacesEligibleNonlinearReferences()
public void EveryModeMatchesScalarOracleAcrossIntrinsicWidths()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEveryMode, PredictorConfigurations);
/// <summary>
/// Verifies strong bilinear and normal three-tap reference filtering under the selected hardware configuration.
/// </summary>
private static void ValidateReferenceFilters()
{
const int size = 32;
ushort[] top = new ushort[(size * 2) + 1];
@ -123,43 +143,53 @@ public class HevcIntraPredictorTests
Assert.Equal((ushort)(100 + i), filteredTop[i]);
Assert.Equal((ushort)(100 + (2 * i)), filteredLeft[i]);
}
HevcIntraPredictor.FilterReferenceSamples(top, left, filteredTop, filteredLeft, 5, 10, false);
Assert.Equal((ushort)((left[1] + (2 * top[0]) + top[1] + 2) >> 2), filteredTop[0]);
Assert.Equal(filteredTop[0], filteredLeft[0]);
for (int i = 1; i < top.Length - 1; i++)
{
Assert.Equal((ushort)((top[i - 1] + (2 * top[i]) + top[i + 1] + 2) >> 2), filteredTop[i]);
Assert.Equal((ushort)((left[i - 1] + (2 * left[i]) + left[i + 1] + 2) >> 2), filteredLeft[i]);
}
Assert.Equal(top[^1], filteredTop[^1]);
Assert.Equal(left[^1], filteredLeft[^1]);
}
/// <summary>
/// Compares every prediction mode and block width with a specification-shaped scalar oracle.
/// Compares every prediction mode and block width with the scalar oracle under the selected hardware configuration.
/// </summary>
/// <param name="log2Size">The base-two logarithm of the tested block side.</param>
/// <param name="bitDepth">The reconstructed component precision.</param>
[Theory]
[InlineData(2, 8)]
[InlineData(3, 10)]
[InlineData(4, 12)]
[InlineData(5, 12)]
public void EveryModeMatchesScalarOracle(int log2Size, int bitDepth)
private static void ValidateEveryMode()
{
int size = 1 << log2Size;
int maximum = (1 << bitDepth) - 1;
int referenceLength = (size * 2) + 1;
ushort[] top = new ushort[referenceLength];
ushort[] left = new ushort[referenceLength];
top[0] = left[0] = (ushort)(maximum / 3);
for (int i = 1; i < referenceLength; i++)
ReadOnlySpan<(int Log2Size, int BitDepth)> cases = [(2, 8), (3, 10), (4, 12), (5, 12)];
foreach ((int log2Size, int bitDepth) in cases)
{
top[i] = (ushort)((top[0] + (37 * i) + (3 * size)) & maximum);
left[i] = (ushort)((left[0] + (53 * i) + (5 * size)) & maximum);
}
int size = 1 << log2Size;
int maximum = (1 << bitDepth) - 1;
int referenceLength = (size * 2) + 1;
ushort[] top = new ushort[referenceLength];
ushort[] left = new ushort[referenceLength];
top[0] = left[0] = (ushort)(maximum / 3);
for (int i = 1; i < referenceLength; i++)
{
top[i] = (ushort)((top[0] + (37 * i) + (3 * size)) & maximum);
left[i] = (ushort)((left[0] + (53 * i) + (5 * size)) & maximum);
}
int stride = size + 3;
ushort[] expected = new ushort[stride * size];
ushort[] actual = new ushort[stride * size];
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(log2Size)];
for (int mode = 0; mode <= 34; mode++)
{
expected.AsSpan().Clear();
actual.AsSpan().Clear();
PredictScalar(top, left, expected, stride, size, mode, bitDepth, true);
HevcIntraPredictor.Predict(top, left, actual, stride, log2Size, mode, bitDepth, true, scratch);
Assert.True(expected.AsSpan().SequenceEqual(actual), $"Mode {mode}, size {size}, and bit depth {bitDepth} did not match the scalar oracle.");
int stride = size + 3;
ushort[] expected = new ushort[stride * size];
ushort[] actual = new ushort[stride * size];
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(log2Size)];
for (int mode = 0; mode <= 34; mode++)
{
expected.AsSpan().Clear();
actual.AsSpan().Clear();
PredictScalar(top, left, expected, stride, size, mode, bitDepth, true);
HevcIntraPredictor.Predict(top, left, actual, stride, log2Size, mode, bitDepth, true, scratch);
Assert.True(expected.AsSpan().SequenceEqual(actual), $"Mode {mode}, size {size}, and bit depth {bitDepth} did not match the scalar oracle.");
}
}
}

64
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs

@ -80,6 +80,70 @@ public class HevcPictureDecoderTests
}
}
/// <summary>
/// Verifies the official Main Still Picture stream containing every luma and chroma intra mode at every
/// conformance block size against its published native planar output.
/// </summary>
[Fact]
public void DecodeOfficialIntraPredictionPictureMatchesPublishedReference()
{
byte[] annexB = TestFile.Create(TestImages.Heif.IntraPredictionB).Bytes;
byte[] expectedYuv = TestFile.Create(TestImages.Heif.IntraPredictionBReference).Bytes;
ConvertAnnexBStillPicture(annexB, 8, 1, out byte[] configurationData, out byte[] itemData);
HevcCodecConfiguration configuration = new(configurationData);
HevcImageItemBitstream bitstream = new(itemData, configuration);
HevcPictureParameterSet pictureParameterSet = bitstream.SliceSegments[0].PictureParameterSet;
HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet;
using HevcPictureDecoder decoder = new(Configuration.Default, pictureParameterSet);
decoder.Decode(bitstream);
Assert.Equal(3, configuration.GeneralProfileIdc);
Assert.Equal(1920, sequenceParameterSet.DisplayWidth);
Assert.Equal(1080, sequenceParameterSet.DisplayHeight);
Assert.True(sequenceParameterSet.StrongIntraSmoothingEnabled);
Assert.False(sequenceParameterSet.IntraSmoothingDisabled);
Assert.False(pictureParameterSet.ConstrainedIntraPredictionEnabled);
int expectedLength = sequenceParameterSet.DisplayWidth * sequenceParameterSet.DisplayHeight;
int chromaWidth = GetDisplaySize(sequenceParameterSet.DisplayWidth, decoder.Picture.GetSubsamplingX(HevcPlane.Cb));
int chromaHeight = GetDisplaySize(sequenceParameterSet.DisplayHeight, decoder.Picture.GetSubsamplingY(HevcPlane.Cb));
expectedLength += 2 * chromaWidth * chromaHeight;
Assert.Equal(expectedLength, expectedYuv.Length);
int offset = 0;
AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Y, expectedYuv, ref offset);
AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Cb, expectedYuv, ref offset);
AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Cr, expectedYuv, ref offset);
Assert.Equal(expectedYuv.Length, offset);
}
/// <summary>
/// Verifies the independently coded first picture of the official constrained-intra stream against its
/// decoded-picture hashes while the production PPS path retains the enabled constraint.
/// </summary>
[Fact]
public void DecodeOfficialConstrainedIntraPictureMatchesPublishedDigest()
{
byte[] annexB = TestFile.Create(TestImages.Heif.ConstrainedIntraPredictionA).Bytes;
ConvertAnnexBStillPicture(annexB, 8, 1, out byte[] configurationData, out byte[] itemData);
HevcCodecConfiguration configuration = new(configurationData);
HevcImageItemBitstream bitstream = new(itemData, configuration);
HevcPictureParameterSet pictureParameterSet = bitstream.SliceSegments[0].PictureParameterSet;
HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet;
using HevcPictureDecoder decoder = new(Configuration.Default, pictureParameterSet);
decoder.Decode(bitstream);
Assert.Equal(1, configuration.GeneralProfileIdc);
Assert.Equal(416, sequenceParameterSet.DisplayWidth);
Assert.Equal(240, sequenceParameterSet.DisplayHeight);
Assert.True(pictureParameterSet.ConstrainedIntraPredictionEnabled);
Assert.Equal("69a20189e6bbb9c088e3adc967244ca1", GetPlaneDigest(decoder.Picture, HevcPlane.Y));
Assert.Equal("26502d354bb123f54c20413f14360ddb", GetPlaneDigest(decoder.Picture, HevcPlane.Cb));
Assert.Equal("baafaef47a55ae2e876862b30b3bc720", GetPlaneDigest(decoder.Picture, HevcPlane.Cr));
}
/// <summary>
/// Verifies coding-tree and transform-tree conformance streams against native-plane digests produced by the
/// pinned HM decoder from output that matches each archive's published checksum.

3
tests/ImageSharp.Tests/TestImages.cs

@ -1304,6 +1304,9 @@ public static class TestImages
public const string General12Bit420 = "Heif/Hevc/Conformance/GENERAL_12b_420_RExt_Sony_1.bit";
public const string General12Bit422 = "Heif/Hevc/Conformance/GENERAL_12b_422_RExt_Sony_1.bit";
public const string General12Bit444 = "Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2.bit";
public const string IntraPredictionB = "Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit";
public const string IntraPredictionBReference = "Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv";
public const string ConstrainedIntraPredictionA = "Heif/Hevc/Conformance/CIP_A_Panasonic_3.bit";
public const string RqtA = "Heif/Hevc/Conformance/RQT_A_HHI_4.bit";
public const string RqtB = "Heif/Hevc/Conformance/RQT_B_HHI_4.bit";
public const string RqtC = "Heif/Hevc/Conformance/RQT_C_HHI_4.bit";

3
tests/Images/Input/Heif/Hevc/Conformance/CIP_A_Panasonic_3.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:19f6a710e8b2c66f393392af24c11e5fbba10a5c9f6f28c8b26949942f384c1c
size 11957

3
tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:456608ae5c14fea0496d770ae9614cd3d7c6ae98c14b22d64bbd96b37f49d7a0
size 29168

3
tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:69626c83c9905fe2e3796b5425ccf2719b05b76b42cfeb398997a17207982a52
size 3110400

7
tests/Images/Input/Heif/Hevc/Conformance/README.md

@ -15,3 +15,10 @@ The Main-profile traversal matrix comes from the official `RQT_A_HHI_4` through
- `STRUCT_A_Samsung_7`: `02f02cc9a67222004ae18ee6bebd475a`
- `STRUCT_B_Samsung_7`: `1f472e014aff120681e8482c8a04ef93`
- `TUSIZE_A_Samsung_1`: `5e6c198c852e92cdcca0e137fc2610c4`
The intra-prediction fixtures come from the same official HEVC v1 attachment:
- `IPRED_B_Nokia_3` is the one-picture Main Still Picture stream identified by H.265.1 Table 1. Its published description states that it uses all 35 modes at each of the luma 32x32, 16x16, 8x8, and 4x4 sizes and the chroma 16x16, 8x8, and 4x4 sizes, for 245 mode-and-size combinations. The archive SHA-256 is `738E35AF4CED173E8CCB558464DC6A1B8B791FB1BB957C073633AA6037288CD0`; the published bitstream MD5 is `1be12a94d9da6b84af78cd6cfd5e4407`; and the retained published YUV has MD5 `ec3bcad4f9174404e52d132206b6617b`.
- `CIP_A_Panasonic_3` signals constrained intra prediction and contains one I picture followed by one B picture. The bounded still decoder test extracts only the independently coded I picture. The archive SHA-256 is `5C55B3594E5C951C9F5312913961750283EB2DADD15844FE35F9BEB94A9A548C`, and the published bitstream MD5 is `e1e00592fc8a158da9109b4dc05d03be`.
HM at commit `9c1f298659ab0cee9dc13d23d0304221575410b9` reproduces the published `IPRED_B_Nokia_3` YUV MD5 exactly and reports decoded-picture hashes `200ef9f7d2efde44ab872a78a40ece42`, `bfe9de3bef5ce5596a71bbfdbfb823fc`, and `5b14acdd5c5bf8c6887160817ee1ad11`. The same pinned decoder reproduces `CIP_A_Panasonic_3`'s published complete two-picture YUV MD5 `4cbf601ba98d63f642defab5eaa12c8d`; its independently coded first picture reports plane hashes `69a20189e6bbb9c088e3adc967244ca1`, `26502d354bb123f54c20413f14360ddb`, and `baafaef47a55ae2e876862b30b3bc720`.

Loading…
Cancel
Save