Uji regresi visual pada pipeline pemrosesan citra sering mengalami kegagalan palsu (flaky test) saat dieksekusi di pipeline CI/CD. Masalah paling umum pada algoritma ordered dithering—seperti varian algoritma Yliluoma—adalah pixel drift: pergeseran nilai warna sebesar 1 bit atau perubahan seleksi pola kuantisasi pada piksel perbatasan. Pemeriksaan berbasis byte-by-byte hash (seperti SHA-256) gagal menangani karakteristik ini karena perbedaan mikro-arsitektur CPU.

Akar Masalah: Presisi Floating-Point dan Non-Determinisma Komputasi

Algoritma dithering modern, khususnya variasi Yliluoma (misalnya Yliluoma 1 atau Yliluoma 2), meminimalkan galat persepsi visual dengan mengevaluasi kombinasi dua warna palet terdekat melalui matriks Bayer terdistribusi. Kalkulasi ini melibatkan metrik jarak warna (color distance), sering kali mengadaptasi model Euclidean tertimbang berbasis luminansi:

Distance = 0.299 * (R1 - R2)^2 + 0.587 * (G1 - G2)^2 + 0.114 * (B1 - B2)^2

Flaky snapshot terjadi ketika kode yang sama dieksekusi pada arsitektur berbeda (misalnya, workstation pengembang berbasis Apple Silicon ARM64 versus runner CI berbasis Intel Xeon x86_64). Penyebab utamanya:

  • Instruksi Fused Multiply-Add (FMA): Perbedaan optimasi kompiler dalam menggabungkan operasi perkalian dan penjumlahan ke dalam satu siklus FMA menghasilkan presisi pembulatan berbeda dibandingkan komputasi bertahap.
  • Rounding Modes: Penanganan nilai tie-breaking saat konversi floating-point ke integer 8-bit (uint8) memicu pemilihan indeks palet yang berbeda ketika selisih jarak berada pada batas sub-presisi (misal: 0.49999997 vs 0.50000001).
  • Divergensi Vectorization (SIMD): Implementasi AVX2/AVX-512 pada x86 versus NEON pada ARM mengelola akumulator register floating-point dengan karakteristik presisi yang berbeda.

Solusi 1: Eliminasi Non-Determinisma via Fixed-Point LUT

Alih-alih mengandalkan floating-point runtime, seluruh kalkulasi metrik jarak warna harus diubah ke representasi aritmetika fixed-point integer atau di-pra-komputasi ke dalam Look-Up Table (LUT).

Untuk palet warna terbatas (misalnya palet 16 atau 256 warna), kita dapat membuat tabel jarak integer yang deterministik secara cross-platform menggunakan format Q16 atau bobot integer standar:

// Mengganti floating-point weights dengan integer scaling (total sum: 1000)
// Distance = (299 * dR^2 + 587 * dG^2 + 114 * dB^2)
inline uint32_t color_distance_sq_fixed(uint8_t r1, uint8_t g1, uint8_t b1, 
                                        uint8_t r2, uint8_t g2, uint8_t b2) {
    int32_t dr = (int32_t)r1 - (int32_t)r2;
    int32_t dg = (int32_t)g1 - (int32_t)g2;
    int32_t db = (int32_t)b1 - (int32_t)b2;
    return (uint32_t)(299 * dr * dr + 587 * dg * dg + 114 * db * db);
}

Dengan integer murni, operasi tie-breaking deterministik pada semua target arsitektur tanpa terpengaruh flag optimasi seperti -ffast-math.

Solusi 2: Standardisasi Golden Snapshot Harness

Pengujian snapshot visual yang menguji rendering grafis tidak boleh bergantung pada implementasi rasterizer GPU lokal atau konfigurasi OS host. Standardisasi harness meliputi:

  1. Software Rasterization Pinned: Jalankan pipeline rendering murni di CPU (software mode) selama pengujian regresi untuk menghindari variasi driver kartu grafis (Nvidia/AMD/Intel/Mesa).
  2. Karantina Metadata Gambar: Format PNG menyertakan timestamp, metadata ICC profile, dan tag gamma secara bawaan. Harness snapshot harus membandingkan buffer piksel mentah (raw RGB/RGBA buffer) atau membersihkan seluruh chunk metadata sebelum komparasi.

Solusi 3: Perceptual Assertion (SSIM) Menggantikan Raw Byte Equality

Memvalidasi visual snapshot menggunakan memcmp atau hash kriptografis adalah anti-pattern pada pipeline visual. Pendekatan tepat menggunakan metrik Structural Similarity Index Measure (SSIM) yang dikombinasikan dengan batas toleransi delta piksel (bounded pixel drift threshold).

Kriteria assertion yang tangguh harus menetapkan dua batas:

  • Nilai SSIM Global: Memastikan keseluruhan struktur, kontras, dan luminansi pola dither identik (misal: SSIM >= 0.9995).
  • Bounded Pixel Error: Membatasi jumlah absolut piksel yang bergeser dan deviasi maksimal nilainya (misal: maksimum 0.05% total piksel berubah, dengan nilai pergeseran channel |Δ| <= 1).

Implementasi: Harness Assertion Deterministik

Berikut adalah implementasi modul verifikasi snapshot visual untuk pipeline testing (Python / NumPy) yang mengisolasi flaky failure:

import numpy as np

def calculate_ssim(img1: np.ndarray, img2: np.ndarray) -> float:
    """Menghitung Structural Similarity Index (SSIM) sederhana untuk citra monokrom/luminansi."""
    c1 = (0.01 * 255) ** 2
    c2 = (0.03 * 255) ** 2

    img1 = img1.astype(np.float64)
    img2 = img2.astype(np.float64)

    mu1 = img1.mean()
    mu2 = img2.mean()
    sigma1_sq = ((img1 - mu1) ** 2).mean()
    sigma2_sq = ((img2 - mu2) ** 2).mean()
    sigma12 = ((img1 - mu1) * (img2 - mu2)).mean()

    numerator = (2 * mu1 * mu2 + c1) * (2 * sigma12 + c2)
    denominator = (mu1**2 + mu2**2 + c1) * (sigma1_sq + sigma2_sq + c2)
    return float(numerator / denominator)

def assert_dither_snapshot(actual: np.ndarray, golden: np.ndarray, 
                           max_drift_ratio: float = 0.0005, 
                           min_ssim: float = 0.9995):
    """
    Validasi snapshot dithering:
    1. Dimensi harus identik.
    2. Perbedaan nilai per-channel maksimal 1 level kuantisasi.
    3. Persentase piksel yang menyimpang di bawah ambang batas toleransi.
    4. Indeks struktural (SSIM) harus berada di atas target.
    """
    assert actual.shape == golden.shape, (
        f"Dimensi tidak cocok: {actual.shape} vs {golden.shape}"
    )

    diff = np.abs(actual.astype(np.int16) - golden.astype(np.int16))
    drifted_pixels = np.any(diff > 0, axis=-1)
    drift_count = np.count_nonzero(drifted_pixels)
    total_pixels = actual.shape[0] * actual.shape[1]
    drift_ratio = drift_count / total_pixels

    # Cek deviasi nilai absolut: drift akibat presisi tidak boleh melebihi 1 bit kuantisasi
    max_delta = np.max(diff) if total_pixels > 0 else 0
    assert max_delta <= 1, (
        f"Piksel menyimpang melebihi margin presisi. Max delta: {max_delta}"
    )

    assert drift_ratio <= max_drift_ratio, (
        f"Terlalu banyak piksel bergeser: {drift_count}/{total_pixels} "
        f"({drift_ratio:.4%}), batas: {max_drift_ratio:.4%}"
    )

    ssim_val = calculate_ssim(actual, golden)
    assert ssim_val >= min_ssim, (
        f"SSIM di bawah batas toleransi: {ssim_val:.6f} < {min_ssim}"
    )

Trade-offs dan Kesimpulan

Penggunaan verifikasi toleransi SSIM memiliki trade-off: regresi fungsional minor (seperti kesalahan pergeseran koordinat 1 piksel pada seluruh matriks Bayer) berisiko lolos jika ambang batas dibuat terlalu longgar. Oleh karena itu, pengujian harus dikombinasikan dengan verifikasi matematis unit-test pada kernel matriks dither itu sendiri secara terpisah.

Mengganti kalkulasi floating-point ke fixed-point LUT pada implementasi dithering menuntaskan sumber determinisma di level algoritma, sementara perceptual diff assertion pada harness pengujian melindungi pipeline dari variasi mikro-arsitektur yang tidak dapat dihindari.