Allow single point test "curves"
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".