---
title: metric
---

<div class="python-reference">
<a id="fastvideo.eval.metrics.audio.frechet_distance.metric-classes"></a>
<a id="fastvideo.eval.metrics.audio.frechet_distance.metric-functions"></a>
<a id="fastvideo.eval.metrics.audio.frechet_distance.metric"></a>
<p>Frechet Audio Distance over PaSST embeddings (<code>FD_PaSST</code>).</p>
<p>Corpus-vs-corpus Fréchet distance between Gaussian moments of two PaSST
768-d embedding sets. Ports <code>av_bench.metrics.fad.compute_fd</code> 1:1.</p>
<p>References are supplied per-sample via <code>reference_audio</code> /
<code>role="reference"</code>, or once from a <code>.pt</code> cache at
<code>$FASTVIDEO_FAD_REF_FEATURES</code>. Skips when either side has fewer than
two finite embeddings.</p>
<h2 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric</code></h2>
<pre class="doc-signature"><code>BaseMetric()</code></pre>
<p>Abstract base class for all eval metrics.</p>
<p>Two execution shapes:</p>
<ul>
<li>
<p><strong>Per-sample</strong> (<code>is_set_metric=False</code>, default) — implement
  :meth:<code>compute</code>. The Evaluator calls it once per input sample and
  returns one :class:<code>MetricResult</code> per sample.</p>
</li>
<li>
<p><strong>Set-vs-set</strong> (<code>is_set_metric=True</code>) — implement
  :meth:<code>accumulate</code> (called once per sample to buffer features)
  and :meth:<code>finalize</code> (called once after all samples to compute
  the corpus-level result). Use :meth:<code>reset</code> to clear buffers and
  :meth:<code>merge_from</code> to fold multi-GPU per-worker state together.</p>
</li>
</ul>
<p>Optionally override :meth:<code>setup</code> to eagerly load models. Metrics
that chunk along the time dim for memory hardcode their own chunk
size in <code>__init__</code> (see <code>optical_flow</code> for the canonical
example). Eval always processes one video per
:meth:<code>Evaluator.evaluate</code> call; <code>compute</code> / <code>accumulate</code>
receive a single sample, not a batch.</p>

<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.to"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.to</code></h3>
<pre class="doc-signature"><code>to(device: str | torch.device) -&gt; BaseMetric</code></pre>
<p>Move metric (and its internal models) to <em>device</em>.</p>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.setup"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.setup</code></h3>
<pre class="doc-signature"><code>setup() -&gt; None</code></pre>
<p>Eagerly load models. Called once by :class:<code>EvalWorker</code>.</p>
<p>Default is a no-op; metrics with no eager state (pixel math,
closed-form ops) inherit this. Override only if your metric
needs to load weights.</p>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.compute"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.compute</code></h3>
<pre class="doc-signature"><code>compute(sample: dict) -&gt; MetricResult</code></pre>
<p>Per-sample metrics: compute the score for one sample.</p>
<p><code>sample["video"]</code> is <code>(T, C, H, W)</code> float in <code>[0, 1]</code>.
<code>sample["reference"]</code> (if used) has the same shape. Return
<code>self._skip(sample, reason)</code> for missing inputs.</p>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.reset"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.reset</code></h3>
<pre class="doc-signature"><code>reset() -&gt; None</code></pre>
<p>Clear accumulator state at the start of each evaluate() call.</p>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.accumulate"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.accumulate</code></h3>
<pre class="doc-signature"><code>accumulate(sample: dict) -&gt; None</code></pre>
<p>Buffer per-sample features for a corpus-level metric.</p>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.finalize"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.finalize</code></h3>
<pre class="doc-signature"><code>finalize() -&gt; MetricResult</code></pre>
<p>Compute the corpus-level result from buffered state.</p>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.merge_from"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.BaseMetric.merge_from</code></h3>
<pre class="doc-signature"><code>merge_from(other: BaseMetric) -&gt; None</code></pre>
<p>Multi-GPU: fold another worker's accumulator state into this one.</p>
<h2 id="fastvideo.eval.metrics.audio.frechet_distance.metric.register"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.register</code></h2>
<pre class="doc-signature"><code>register(name: str)</code></pre>
<p>Decorator to register a metric class.</p>
<p>Usage::</p>
<pre><code>@register("ssim")
class SSIMMetric(BaseMetric):
    ...
</code></pre>
<h2 id="fastvideo.eval.metrics.audio.frechet_distance.metric.MetricResult"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.MetricResult</code></h2>
<pre class="doc-signature"><code>MetricResult(name: str, score: float | None, details: dict&#91;str, Any&#93; = dict())</code></pre>
<p>Standard result container returned by all metrics.</p>
<p><code>score</code> is <code>None</code> when the metric was skipped (e.g. missing
required input).  Check <code>details["skipped"]</code> for the reason.</p>

<h2 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric</code></h2>
<pre class="doc-signature"><code>FrechetAudioDistanceMetric()</code></pre>
<p>Corpus-vs-corpus Frechet Audio Distance with PaSST embeddings.</p>

<details><summary>Source</summary><pre><code>@register(&quot;audio.frechet_distance&quot;)
class FrechetAudioDistanceMetric(BaseMetric):
    &quot;&quot;&quot;Corpus-vs-corpus Frechet Audio Distance with PaSST embeddings.&quot;&quot;&quot;

    name = &quot;audio.frechet_distance&quot;
    requires_reference = True
    higher_is_better = False
    needs_gpu = True
    is_set_metric = True
    backbone = &quot;passt&quot;
    dependencies = &#91;&quot;hear21passt&quot;, &quot;librosa&quot;, &quot;pyloudnorm&quot;&#93;

    def __init__(self) -&gt; None:
        super().__init__()
        self._model: Any = None
        self._gen_buf: list&#91;np.ndarray&#93; = &#91;&#93;
        self._ref_buf: list&#91;np.ndarray&#93; = &#91;&#93;
        self._cached_ref_path: str | None = envs.FASTVIDEO_FAD_REF_FEATURES.get()
        self._cached_ref_mu: np.ndarray | None = None
        self._cached_ref_sigma: np.ndarray | None = None
        self._n_cached_ref: int = 0

    def to(self, device):
        super().to(device)
        if self._model is not None:
            self._model = self._model.to(self.device)
        return self

    def setup(self) -&gt; None:
        if self._model is None:
            from hear21passt.base import get_basic_model

            model = get_basic_model(mode=&quot;all&quot;)
            model.eval()
            model = model.to(self.device)
            self._model = model
        if self._cached_ref_path and self._cached_ref_mu is None:
            self._load_cached_ref()

    def _load_cached_ref(self) -&gt; None:
        assert self._cached_ref_path is not None
        path = Path(self._cached_ref_path)
        if not path.exists():
            raise FileNotFoundError(f&quot;&#123;REF_FEATURES_ENV&#125; set to &#123;path&#125;, but the file does not exist. &quot;
                                    f&quot;Either unset the env var to use sample-supplied references or &quot;
                                    f&quot;pre-compute the reference features file.&quot;)
        ref = torch.load(str(path), weights_only=True, map_location=&quot;cpu&quot;)
        if hasattr(ref, &quot;numpy&quot;):
            ref = ref.numpy()
        ref = np.asarray(ref)
        if ref.ndim != 2 or ref.shape&#91;1&#93; != PASST_EMBED_DIM:
            raise ValueError(f&quot;Expected a 2-D tensor of shape (N, &#123;PASST_EMBED_DIM&#125;) at &#123;path&#125;; got shape &#123;ref.shape&#125;.&quot;)
        finite = np.isfinite(ref).all(axis=1)
        ref = ref&#91;finite&#93;
        if ref.shape&#91;0&#93; &lt; 2:
            raise ValueError(f&quot;Cached reference features at &#123;path&#125; have only &#123;ref.shape&#91;0&#93;&#125; finite rows; need &gt;= 2.&quot;)
        self._cached_ref_mu = ref.mean(axis=0)
        self._cached_ref_sigma = np.cov(ref, rowvar=False)
        self._n_cached_ref = int(ref.shape&#91;0&#93;)

    def reset(self) -&gt; None:
        self._gen_buf.clear()
        self._ref_buf.clear()

    def accumulate(self, sample: dict) -&gt; None:
        if self._model is None:
            self.setup()
        if sample.get(&quot;role&quot;) == &quot;reference&quot;:
            ref_path = sample.get(&quot;audio&quot;) or sample.get(&quot;reference_audio&quot;)
            if ref_path is not None:
                self._ref_buf.append(_passt_embed(self._model, ref_path, self.device))
            return
        gen_path = sample.get(&quot;audio&quot;)
        if gen_path is not None:
            self._gen_buf.append(_passt_embed(self._model, gen_path, self.device))
        if self._cached_ref_mu is not None:
            return
        ref_path = sample.get(&quot;reference_audio&quot;)
        if ref_path is not None:
            self._ref_buf.append(_passt_embed(self._model, ref_path, self.device))

    def merge_from(self, other: FrechetAudioDistanceMetric) -&gt; None:
        self._gen_buf.extend(other._gen_buf)
        self._ref_buf.extend(other._ref_buf)

    def finalize(self) -&gt; MetricResult:
        gen_all = np.stack(self._gen_buf) if self._gen_buf else np.empty((0, ))
        gen = gen_all&#91;np.isfinite(gen_all).all(axis=1)&#93; if gen_all.size else gen_all
        n_gen = int(gen.shape&#91;0&#93;)
        n_gen_dropped = len(self._gen_buf) - n_gen

        if self._cached_ref_mu is not None:
            mu_r = self._cached_ref_mu
            sigma_r = self._cached_ref_sigma
            n_ref = self._n_cached_ref
            n_ref_dropped = 0
            ref_source = &quot;cached&quot;
        else:
            ref_all = np.stack(self._ref_buf) if self._ref_buf else np.empty((0, ))
            ref = ref_all&#91;np.isfinite(ref_all).all(axis=1)&#93; if ref_all.size else ref_all
            n_ref = int(ref.shape&#91;0&#93;)
            n_ref_dropped = len(self._ref_buf) - n_ref
            ref_source = &quot;samples&quot;
            if n_ref &lt; 2 or n_gen &lt; 2:
                return MetricResult(
                    name=self.name,
                    score=None,
                    details=&#123;
                        &quot;skipped&quot;:
                        f&quot;FAD needs &gt;=2 finite-embed samples per side &quot;
                        f&quot;(got n_gen=&#123;n_gen&#125; valid of &#123;len(self._gen_buf)&#125;, &quot;
                        f&quot;n_ref=&#123;n_ref&#125; valid of &#123;len(self._ref_buf)&#125;)&quot;,
                        &quot;n_gen&quot;:
                        n_gen,
                        &quot;n_ref&quot;:
                        n_ref,
                        &quot;n_gen_dropped_nonfinite&quot;:
                        n_gen_dropped,
                        &quot;n_ref_dropped_nonfinite&quot;:
                        n_ref_dropped,
                        &quot;ref_source&quot;:
                        ref_source,
                    &#125;,
                )
            mu_r, sigma_r = ref.mean(axis=0), np.cov(ref, rowvar=False)

        if n_gen &lt; 2:
            return MetricResult(
                name=self.name,
                score=None,
                details=&#123;
                    &quot;skipped&quot;: f&quot;FAD needs &gt;=2 finite-embed gen samples (got &#123;n_gen&#125; valid of &#123;len(self._gen_buf)&#125;)&quot;,
                    &quot;n_gen&quot;: n_gen,
                    &quot;n_ref&quot;: n_ref,
                    &quot;n_gen_dropped_nonfinite&quot;: n_gen_dropped,
                    &quot;n_ref_dropped_nonfinite&quot;: n_ref_dropped,
                    &quot;ref_source&quot;: ref_source,
                &#125;,
            )

        mu_g, sigma_g = gen.mean(axis=0), np.cov(gen, rowvar=False)
        fd = _frechet_distance(mu_g, sigma_g, mu_r, sigma_r)
        return MetricResult(
            name=self.name,
            score=fd,
            details=&#123;
                &quot;n_gen&quot;: n_gen,
                &quot;n_ref&quot;: n_ref,
                &quot;n_gen_dropped_nonfinite&quot;: n_gen_dropped,
                &quot;n_ref_dropped_nonfinite&quot;: n_ref_dropped,
                &quot;ref_source&quot;: ref_source,
            &#125;,
        )</code></pre></details>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.to"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.to</code></h3>
<pre class="doc-signature"><code>to(device)</code></pre>

<details><summary>Source</summary><pre><code>def to(self, device):
    super().to(device)
    if self._model is not None:
        self._model = self._model.to(self.device)
    return self</code></pre></details>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.setup"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.setup</code></h3>
<pre class="doc-signature"><code>setup() -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def setup(self) -&gt; None:
    if self._model is None:
        from hear21passt.base import get_basic_model

        model = get_basic_model(mode=&quot;all&quot;)
        model.eval()
        model = model.to(self.device)
        self._model = model
    if self._cached_ref_path and self._cached_ref_mu is None:
        self._load_cached_ref()</code></pre></details>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.reset"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.reset</code></h3>
<pre class="doc-signature"><code>reset() -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def reset(self) -&gt; None:
    self._gen_buf.clear()
    self._ref_buf.clear()</code></pre></details>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.accumulate"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.accumulate</code></h3>
<pre class="doc-signature"><code>accumulate(sample: dict) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def accumulate(self, sample: dict) -&gt; None:
    if self._model is None:
        self.setup()
    if sample.get(&quot;role&quot;) == &quot;reference&quot;:
        ref_path = sample.get(&quot;audio&quot;) or sample.get(&quot;reference_audio&quot;)
        if ref_path is not None:
            self._ref_buf.append(_passt_embed(self._model, ref_path, self.device))
        return
    gen_path = sample.get(&quot;audio&quot;)
    if gen_path is not None:
        self._gen_buf.append(_passt_embed(self._model, gen_path, self.device))
    if self._cached_ref_mu is not None:
        return
    ref_path = sample.get(&quot;reference_audio&quot;)
    if ref_path is not None:
        self._ref_buf.append(_passt_embed(self._model, ref_path, self.device))</code></pre></details>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.merge_from"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.merge_from</code></h3>
<pre class="doc-signature"><code>merge_from(other: FrechetAudioDistanceMetric) -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def merge_from(self, other: FrechetAudioDistanceMetric) -&gt; None:
    self._gen_buf.extend(other._gen_buf)
    self._ref_buf.extend(other._ref_buf)</code></pre></details>
<h3 id="fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.finalize"><code>fastvideo.eval.metrics.audio.frechet_distance.metric.FrechetAudioDistanceMetric.finalize</code></h3>
<pre class="doc-signature"><code>finalize() -&gt; MetricResult</code></pre>

<details><summary>Source</summary><pre><code>def finalize(self) -&gt; MetricResult:
    gen_all = np.stack(self._gen_buf) if self._gen_buf else np.empty((0, ))
    gen = gen_all&#91;np.isfinite(gen_all).all(axis=1)&#93; if gen_all.size else gen_all
    n_gen = int(gen.shape&#91;0&#93;)
    n_gen_dropped = len(self._gen_buf) - n_gen

    if self._cached_ref_mu is not None:
        mu_r = self._cached_ref_mu
        sigma_r = self._cached_ref_sigma
        n_ref = self._n_cached_ref
        n_ref_dropped = 0
        ref_source = &quot;cached&quot;
    else:
        ref_all = np.stack(self._ref_buf) if self._ref_buf else np.empty((0, ))
        ref = ref_all&#91;np.isfinite(ref_all).all(axis=1)&#93; if ref_all.size else ref_all
        n_ref = int(ref.shape&#91;0&#93;)
        n_ref_dropped = len(self._ref_buf) - n_ref
        ref_source = &quot;samples&quot;
        if n_ref &lt; 2 or n_gen &lt; 2:
            return MetricResult(
                name=self.name,
                score=None,
                details=&#123;
                    &quot;skipped&quot;:
                    f&quot;FAD needs &gt;=2 finite-embed samples per side &quot;
                    f&quot;(got n_gen=&#123;n_gen&#125; valid of &#123;len(self._gen_buf)&#125;, &quot;
                    f&quot;n_ref=&#123;n_ref&#125; valid of &#123;len(self._ref_buf)&#125;)&quot;,
                    &quot;n_gen&quot;:
                    n_gen,
                    &quot;n_ref&quot;:
                    n_ref,
                    &quot;n_gen_dropped_nonfinite&quot;:
                    n_gen_dropped,
                    &quot;n_ref_dropped_nonfinite&quot;:
                    n_ref_dropped,
                    &quot;ref_source&quot;:
                    ref_source,
                &#125;,
            )
        mu_r, sigma_r = ref.mean(axis=0), np.cov(ref, rowvar=False)

    if n_gen &lt; 2:
        return MetricResult(
            name=self.name,
            score=None,
            details=&#123;
                &quot;skipped&quot;: f&quot;FAD needs &gt;=2 finite-embed gen samples (got &#123;n_gen&#125; valid of &#123;len(self._gen_buf)&#125;)&quot;,
                &quot;n_gen&quot;: n_gen,
                &quot;n_ref&quot;: n_ref,
                &quot;n_gen_dropped_nonfinite&quot;: n_gen_dropped,
                &quot;n_ref_dropped_nonfinite&quot;: n_ref_dropped,
                &quot;ref_source&quot;: ref_source,
            &#125;,
        )

    mu_g, sigma_g = gen.mean(axis=0), np.cov(gen, rowvar=False)
    fd = _frechet_distance(mu_g, sigma_g, mu_r, sigma_r)
    return MetricResult(
        name=self.name,
        score=fd,
        details=&#123;
            &quot;n_gen&quot;: n_gen,
            &quot;n_ref&quot;: n_ref,
            &quot;n_gen_dropped_nonfinite&quot;: n_gen_dropped,
            &quot;n_ref_dropped_nonfinite&quot;: n_ref_dropped,
            &quot;ref_source&quot;: ref_source,
        &#125;,
    )</code></pre></details>
</div>
