bjontegaard icon indicating copy to clipboard operation
bjontegaard copied to clipboard

Allow single point test "curves"

Open YodaEmbedding opened this issue 1 year ago • 0 comments

Request

Given:

import bjontegaard as bd


rate_anchor = [0.1, 0.2, 0.4, 0.8]
psnr_anchor = [30.0, 35.0, 40.0, 45.0]
rate_test = [0.18]
psnr_test = [36.0]

kwargs = dict(
    method="akima",
    require_matching_points=False,
    min_overlap=0.0,
)

bd_rate = bd.bd_rate(rate_anchor, psnr_anchor, rate_test, psnr_test, **kwargs)
bd_psnr = bd.bd_psnr(rate_anchor, psnr_anchor, rate_test, psnr_test, **kwargs)

print(f"BD-Rate={bd_rate:.2f}% | BD-PSNR={bd_psnr:.2f} dB")

Expected output:

BD-Rate=-21.65% | BD-PSNR=1.76 dB

Actual output:

bjontegaard/bjontegaard_delta.py:49: UserWarning: Curves do not overlap. BD cannot be calculated.
  warnings.warn("Curves do not overlap. BD cannot be calculated.")
BD-Rate=nan% | BD-PSNR=nan dB

Rationale

Consider a single test point $(R, D)$. Let its corresponding "fake" test curve be defined as $\{ (R, D), (R + \epsilon, D + \epsilon) \}$. As $\epsilon \to 0$, BD rate just becomes a distance measure between the test point and the interpolated point on the anchor curve with the same value of PSNR.

$$ \begin{align} \Delta R &= \frac{1}{\epsilon} \int_{D}^{D + \epsilon} \left[ R_{\text{test}}(y) - R_{\text{anchor}}(y) \right] \, dy \ & \overset{\epsilon \to 0}{\longrightarrow} R_{\text{test}}(D) - R_{\text{anchor}}(D) \end{align} $$

Disclaimer: I didn't really check that the equation above makes sense. Also, pretend that $R \to \log R$, if needed.

As a numerical example, consider the following method which fakes a test "curve" using a small epsilon deviation about the single test point:

import bjontegaard as bd


def calculate_bd(rate_anchor, psnr_anchor, rate_test, psnr_test, eps=1e-6, **kwargs):
    if len(rate_test) == 1:
        # Fake a "curve" using a small % deviation of epsilon.
        [rate_test] = rate_test
        [psnr_test] = psnr_test
        rate_test = [rate_test, rate_test * (1 + eps)]
        psnr_test = [psnr_test, psnr_test * (1 + eps)]

    bd_rate = bd.bd_rate(rate_anchor, psnr_anchor, rate_test, psnr_test, **kwargs)
    bd_psnr = bd.bd_psnr(rate_anchor, psnr_anchor, rate_test, psnr_test, **kwargs)

    return bd_rate, bd_psnr


rate_anchor = [0.1, 0.2, 0.4, 0.8]
psnr_anchor = [30.0, 35.0, 40.0, 45.0]
rate_test = [0.18]
psnr_test = [36.0]


for i in range(10):
    eps = 10 ** (-i)
    bd_rate, bd_psnr = calculate_bd(
        rate_anchor,
        psnr_anchor,
        rate_test,
        psnr_test,
        eps=eps,
        method="akima",
        require_matching_points=False,
        min_overlap=0.0,
    )
    print(f"eps={eps:.0e} | BD-Rate={bd_rate:.2f}% | BD-PSNR={bd_psnr:.2f} dB")

Output:

eps=1e+00 | BD-Rate=-54.21% | BD-PSNR=17.26 dB
eps=1e-01 | BD-Rate=-35.97% | BD-PSNR=3.22 dB
eps=1e-02 | BD-Rate=-23.20% | BD-PSNR=1.90 dB
eps=1e-03 | BD-Rate=-21.81% | BD-PSNR=1.77 dB
eps=1e-04 | BD-Rate=-21.67% | BD-PSNR=1.76 dB
eps=1e-05 | BD-Rate=-21.65% | BD-PSNR=1.76 dB
eps=1e-06 | BD-Rate=-21.65% | BD-PSNR=1.76 dB
eps=1e-07 | BD-Rate=-21.65% | BD-PSNR=1.76 dB
eps=1e-08 | BD-Rate=-21.65% | BD-PSNR=1.76 dB
eps=1e-09 | BD-Rate=-21.65% | BD-PSNR=1.76 dB

Clearly, in the limit, the single point "curve" converges to a well-defined value of BD-Rate and BD-PSNR.


Other

I suppose a similar argument might also apply to single-point anchor "curves".

YodaEmbedding avatar Apr 01 '24 04:04 YodaEmbedding