Newer
Older
SmTIAS-Capture / android / app / src / test / kotlin / com / example / mini_tias / ImageStatisticsTest.kt
package com.example.mini_tias

import java.nio.ShortBuffer
import org.junit.Assert.assertEquals
import org.junit.Test

/**
 * [ImageStatistics] の数理ロジック(mean/max/p99/飽和率・低露光率)の単体テスト.
 *
 * 合成した 10-bit BGGR Bayer データを `computeFromShortBuffer` に与えて期待値を検証する.
 * 実機・Android Framework に依存しない純粋な JVM テスト(実行: `./gradlew test`).
 *
 * BGGR 配列の 2×2 ブロック走査:
 *   (row, col)      偶数行: B G B G ...
 *                   奇数行: G R G R ...
 *   1 ブロック = B=(0,0), G=(0,1), G=(1,0), R=(1,1).G はブロック内 2 サンプルの平均.
 */
class ImageStatisticsTest {

    private fun compute(values: ShortArray, width: Int, height: Int): Map<String, Any?> =
        ImageStatistics.computeFromShortBuffer(
            ShortBuffer.wrap(values),
            width, // rowStrideShorts(パディングなし)
            width,
            height,
        )

    @Test
    fun `一様な値の場合 mean と max が一致し飽和・低露光は 0 になる`() {
        // 4×4 すべて 500
        val values = ShortArray(16) { 500 }
        val result = compute(values, 4, 4)

        val mean = result["mean_per_channel"] as Map<*, *>
        assertEquals(500.0, mean["R"] as Double, 1e-9)
        assertEquals(500.0, mean["G"] as Double, 1e-9)
        assertEquals(500.0, mean["B"] as Double, 1e-9)

        val max = result["max_per_channel"] as Map<*, *>
        assertEquals(500, max["R"] as Int)
        assertEquals(500, max["G"] as Int)
        assertEquals(500, max["B"] as Int)

        assertEquals(0.0, result["saturated_pixel_ratio"] as Double, 1e-9)
        assertEquals(0.0, result["underexposed_pixel_ratio"] as Double, 1e-9)

        // p99: 500 は bin 125(500*256/1024)→ (125+1)*1024/256 - 1 = 503
        val p99 = result["p99_per_channel"] as Map<*, *>
        assertEquals(503, p99["R"] as Int)
        assertEquals(503, p99["G"] as Int)
        assertEquals(503, p99["B"] as Int)
    }

    @Test
    fun `飽和画素と低露光画素がチャネル別 mean・max・比率に正しく反映される`() {
        // 4×4.左上ブロックの B=1000(飽和), G の片側=50(低露光).他はすべて 500.
        // row0: 1000  50 500 500
        // row1:  500 500 500 500
        // row2:  500 500 500 500
        // row3:  500 500 500 500
        val values = shortArrayOf(
            1000, 50, 500, 500,
            500, 500, 500, 500,
            500, 500, 500, 500,
            500, 500, 500, 500,
        )
        val result = compute(values, 4, 4)

        val mean = result["mean_per_channel"] as Map<*, *>
        // B: (1000+500+500+500)/4 = 625
        assertEquals(625.0, mean["B"] as Double, 1e-9)
        // G: ((50+500)/2 + 500 + 500 + 500)/4 = (275+1500)/4 = 443.75
        assertEquals(443.75, mean["G"] as Double, 1e-9)
        // R: すべて 500
        assertEquals(500.0, mean["R"] as Double, 1e-9)

        val max = result["max_per_channel"] as Map<*, *>
        assertEquals(1000, max["B"] as Int)
        assertEquals(500, max["R"] as Int)

        // 飽和は B=1000 の 1 画素,低露光は G=50 の 1 画素.総 Bayer 画素 = 16.
        assertEquals(1.0 / 16.0, result["saturated_pixel_ratio"] as Double, 1e-9)
        assertEquals(1.0 / 16.0, result["underexposed_pixel_ratio"] as Double, 1e-9)

        val thresholds = result["thresholds"] as Map<*, *>
        assertEquals(1000, thresholds["saturated"] as Int)
        assertEquals(100, thresholds["underexposed"] as Int)
    }
}