---
title: physics_iq
---

<div class="python-reference">
<a id="fastvideo.eval.datasets.physics_iq-classes"></a>
<a id="fastvideo.eval.datasets.physics_iq-functions"></a>
<a id="fastvideo.eval.datasets.physics_iq"></a>
<p>Physics-IQ benchmark prompt corpus.</p>
<p>Yields one sample dict per take-1 scenario, paired with its take-2
reference and both takes' real motion masks. Each row drops straight
into :meth:<code>fastvideo.eval.Evaluator.evaluate</code> for the <code>physics_iq</code>
metric:</p>
<pre><code>&#123;
    "prompt": &lt;description&gt;,
    "reference":            "&lt;take-1 mp4&gt;",
    "reference_take2":      "&lt;take-2 mp4&gt;",
    "reference_mask":       "&lt;take-1 mask mp4&gt;",
    "reference_take2_mask": "&lt;take-2 mask mp4&gt;",
    "scenario": &lt;scenario_id&gt;,
    "view": &lt;camera view&gt;,
    "auxiliary_info": &#123; ... metadata ... &#125;,
&#125;
</code></pre>
<p>Self-contained dataset: the manifest CSV is vendored under
<code>fastvideo/eval/metrics/physics_iq/_vendored/descriptions.csv</code>;
per-scenario videos/masks/switch-frames auto-fetch on first use from the public
DeepMind bucket into <code>$&#123;FASTVIDEO_EVAL_CACHE&#125;/datasets/physics_iq/</code>.
Pass <code>auto_download=False</code> (or <code>dataset_root=</code> pointing at a
pre-downloaded copy) to opt out of network fetches.</p>
<h2 id="fastvideo.eval.datasets.physics_iq.PromptDataset"><code>fastvideo.eval.datasets.physics_iq.PromptDataset</code></h2>
<pre class="doc-signature"><code>PromptDataset()</code></pre>
<p>Iterable corpus of sample dicts. Subclasses populate <code>self._rows</code>.</p>

<h3 id="fastvideo.eval.datasets.physics_iq.PromptDataset.by_dimension"><code>fastvideo.eval.datasets.physics_iq.PromptDataset.by_dimension</code></h3>
<pre class="doc-signature"><code>by_dimension() -&gt; dict&#91;str, list&#91;dict&#93;&#93;</code></pre>
<p>Group samples by dimension. A multi-dim sample appears under each.</p>
<h2 id="fastvideo.eval.datasets.physics_iq.register_dataset"><code>fastvideo.eval.datasets.physics_iq.register_dataset</code></h2>
<pre class="doc-signature"><code>register_dataset(name: str)</code></pre>
<p>Decorator to register a prompt-dataset class.</p>
<p>Usage::</p>
<pre><code>@register_dataset("vbench")
class VBenchPromptDataset(BasePromptDataset):
    ...
</code></pre>
<h2 id="fastvideo.eval.datasets.physics_iq.get_cache_dir"><code>fastvideo.eval.datasets.physics_iq.get_cache_dir</code></h2>
<pre class="doc-signature"><code>get_cache_dir() -&gt; Path</code></pre>
<p>Eval cache root.</p>
<p>Layout::</p>
<pre><code>get_cache_dir() / models /   ← URL-fetched checkpoints (LAION head,
                               AMT, GRiT, …)
get_cache_dir() / torch  /   ← redirected ``TORCH_HOME`` (DINO etc.)
get_cache_dir() / clip   /   ← passed as ``download_root`` to
                               ``clip.load(...)`` callsites
~/.cache/huggingface/hub /   ← left at HF's default; widely shared
                               with other ML projects
</code></pre>
<p>Override priority: <code>FASTVIDEO_EVAL_CACHE</code> &gt; <code>$&#123;FASTVIDEO_CACHE_ROOT&#125;/eval</code>.</p>
<p>Metric authors writing new code: when wrapping a third-party loader
that has its own cache convention (CLIP's <code>download_root</code>, pyiqa's
<code>cache_dir</code>, etc.), pass <code>str(get_cache_dir() / "&lt;library&gt;")</code> so
users get a single <code>FASTVIDEO_EVAL_CACHE</code> knob to redirect them all.</p>
<h2 id="fastvideo.eval.datasets.physics_iq.PhysicsIQScenario"><code>fastvideo.eval.datasets.physics_iq.PhysicsIQScenario</code></h2>
<pre class="doc-signature"><code>PhysicsIQScenario(scenario_id: str, view: str, scenario_name: str, take1_video_path: str, take2_video_path: str, switch_frame_path: str, caption: str, expected_gen_filename: str, generated_video_path: str | None = None, take1_mask_path: str | None = None, take2_mask_path: str | None = None)</code></pre>
<p>One row of the Physics-IQ manifest, fully resolved on disk.</p>

<details><summary>Source</summary><pre><code>@dataclass(frozen=True)
class PhysicsIQScenario:
    &quot;&quot;&quot;One row of the Physics-IQ manifest, fully resolved on disk.&quot;&quot;&quot;
    scenario_id: str
    view: str
    scenario_name: str
    take1_video_path: str
    take2_video_path: str
    switch_frame_path: str
    caption: str
    expected_gen_filename: str
    generated_video_path: str | None = None
    take1_mask_path: str | None = None
    take2_mask_path: str | None = None</code></pre></details>
<h2 id="fastvideo.eval.datasets.physics_iq.PhysicsIQPromptDataset"><code>fastvideo.eval.datasets.physics_iq.PhysicsIQPromptDataset</code></h2>
<pre class="doc-signature"><code>PhysicsIQPromptDataset(dataset_root: str | Path | None = None, *, fps: int = _DEFAULT_FPS, limit: int | None = None, generated_dir: str | Path | None = None, auto_download: bool = True)</code></pre>
<p>Physics-IQ benchmark prompt corpus.</p>
<p>Self-contained: <code>get_dataset("physics_iq")</code> works with no kwargs.
The manifest CSV is vendored next to the metric, and per-scenario
assets auto-fetch on first miss from the public bucket into
<code>$&#123;FASTVIDEO_EVAL_CACHE&#125;/datasets/physics_iq/</code>.</p>
<h3>Parameters</h3>
<dl>
<dt><code>dataset_root: str | Path | None</code></dt><dd><p>path to a pre-downloaded copy of the Physics-IQ
release. Defaults to <code>$&#123;FASTVIDEO_EVAL_CACHE&#125;/datasets/physics_iq</code>;
override only if you already have a local mirror.</p></dd>
<dt><code>fps: int</code></dt><dd><p>target frame rate. The release ships at 30 FPS; other rates
transcode once on first access into <code>&lt;root&gt;/.physics_iq_cache/</code>.</p></dd>
<dt><code>limit: int | None</code></dt><dd><p>optional truncation for quick smoke runs. Apply this kwarg
(not a post-construction slice) so we only fetch the assets
for the scenarios actually requested.</p></dd>
<dt><code>generated_dir: str | Path | None</code></dt><dd><p>optional directory of pre-generated videos —
attaches each manifest row's expected output path to the
sample dict under <code>auxiliary_info["generated_video_path"]</code>.</p></dd>
<dt><code>auto_download: bool</code></dt><dd><p>when True (the default), missing testing videos,
masks, and switch frames are fetched from the public bucket
into <code>dataset_root</code>. Set False for air-gapped runs; the
loader will then raise <code>FileNotFoundError</code> on miss.</p></dd>
</dl>

<details><summary>Source</summary><pre><code>@register_dataset(&quot;physics_iq&quot;)
class PhysicsIQPromptDataset(PromptDataset):
    &quot;&quot;&quot;Physics-IQ benchmark prompt corpus.

    Self-contained: ``get_dataset(&quot;physics_iq&quot;)`` works with no kwargs.
    The manifest CSV is vendored next to the metric, and per-scenario
    assets auto-fetch on first miss from the public bucket into
    ``$&#123;FASTVIDEO_EVAL_CACHE&#125;/datasets/physics_iq/``.

    Args:
        dataset_root: path to a pre-downloaded copy of the Physics-IQ
            release. Defaults to ``$&#123;FASTVIDEO_EVAL_CACHE&#125;/datasets/physics_iq``;
            override only if you already have a local mirror.
        fps: target frame rate. The release ships at 30 FPS; other rates
            transcode once on first access into ``&lt;root&gt;/.physics_iq_cache/``.
        limit: optional truncation for quick smoke runs. Apply this kwarg
            (not a post-construction slice) so we only fetch the assets
            for the scenarios actually requested.
        generated_dir: optional directory of pre-generated videos —
            attaches each manifest row&#x27;s expected output path to the
            sample dict under ``auxiliary_info&#91;&quot;generated_video_path&quot;&#93;``.
        auto_download: when True (the default), missing testing videos,
            masks, and switch frames are fetched from the public bucket
            into ``dataset_root``. Set False for air-gapped runs; the
            loader will then raise ``FileNotFoundError`` on miss.
    &quot;&quot;&quot;

    description = (&quot;Physics-IQ benchmark, 396 take-1 scenarios across 66 unique physics &quot;
                   &quot;setups × 3 perspective views, each paired with a take-2 reference.&quot;)
    requires_reference_video = True

    def __init__(
        self,
        dataset_root: str | Path | None = None,
        *,
        fps: int = _DEFAULT_FPS,
        limit: int | None = None,
        generated_dir: str | Path | None = None,
        auto_download: bool = True,
    ) -&gt; None:
        super().__init__()

        repo_root = Path(dataset_root or _default_dataset_root()).expanduser().resolve()
        self.repo_root = repo_root
        self.dataset_dir = _resolve_dataset_dir(repo_root)
        self.descriptions_path = _resolve_descriptions_path(repo_root, self.dataset_dir)
        self.cache_dir = repo_root / &quot;.physics_iq_cache&quot;
        self.fps = fps
        self.auto_download = auto_download
        self.bucket_url = _bucket_url()

        scenarios = self._iter_scenarios(
            fps=fps,
            generated_dir=generated_dir,
            limit=limit,
        )
        self._rows = &#91;_scenario_to_row(s) for s in scenarios&#93;

    def _iter_scenarios(
        self,
        *,
        fps: int,
        generated_dir: str | Path | None,
        limit: int | None,
    ) -&gt; list&#91;PhysicsIQScenario&#93;:
        with self.descriptions_path.open(&quot;r&quot;, newline=&quot;&quot;) as handle:
            rows = list(csv.DictReader(handle))

        take2_by_suffix = &#123;_scenario_suffix(row&#91;&quot;scenario&quot;&#93;): row for row in rows if TAKE2_TOKEN in row&#91;&quot;scenario&quot;&#93;&#125;
        take1_rows = &#91;row for row in rows if TAKE1_TOKEN in row&#91;&quot;scenario&quot;&#93;&#93;
        if limit is not None:
            take1_rows = take1_rows&#91;:limit&#93;

        generated_dir_path = (Path(generated_dir).expanduser().resolve() if generated_dir else None)
        scenarios: list&#91;PhysicsIQScenario&#93; = &#91;&#93;

        for row in take1_rows:
            scenario_filename = row&#91;&quot;scenario&quot;&#93;
            scenario_id, view, _, scenario_name = _parse_scenario_filename(scenario_filename)
            take2_row = take2_by_suffix.get(_scenario_suffix(scenario_filename))
            if take2_row is None:
                raise FileNotFoundError(f&quot;Could not find take-2 row matching &#123;scenario_filename&#125;&quot;)
            take2_id, _, _, _ = _parse_scenario_filename(take2_row&#91;&quot;scenario&quot;&#93;)

            take1_video_path = self._resolve_testing_video_path(
                scenario_id=scenario_id,
                view=view,
                take=TAKE1_TOKEN,
                scenario_name=scenario_name,
                fps=fps,
            )
            take2_video_path = self._resolve_testing_video_path(
                scenario_id=take2_id,
                view=view,
                take=TAKE2_TOKEN,
                scenario_name=scenario_name,
                fps=fps,
            )
            switch_frame_path = self._resolve_switch_frame_path(
                scenario_id=scenario_id,
                view=view,
                scenario_name=scenario_name,
            )
            take1_mask_path = self._resolve_real_mask_path(
                scenario_id=scenario_id,
                view=view,
                take=TAKE1_TOKEN,
                scenario_name=scenario_name,
                fps=fps,
            )
            take2_mask_path = self._resolve_real_mask_path(
                scenario_id=take2_id,
                view=view,
                take=TAKE2_TOKEN,
                scenario_name=scenario_name,
                fps=fps,
            )
            generated_video_path = (str(generated_dir_path /
                                        row&#91;&quot;generated_video_name&quot;&#93;) if generated_dir_path is not None else None)

            scenarios.append(
                PhysicsIQScenario(
                    scenario_id=scenario_id,
                    view=view,
                    scenario_name=scenario_name,
                    take1_video_path=str(take1_video_path),
                    take2_video_path=str(take2_video_path),
                    switch_frame_path=str(switch_frame_path),
                    caption=row&#91;&quot;description&quot;&#93;,
                    expected_gen_filename=row&#91;&quot;generated_video_name&quot;&#93;,
                    generated_video_path=generated_video_path,
                    take1_mask_path=str(take1_mask_path),
                    take2_mask_path=str(take2_mask_path),
                ))
        return scenarios

    def _resolve_testing_video_path(
        self,
        *,
        scenario_id: str,
        view: str,
        take: str,
        scenario_name: str,
        fps: int,
    ) -&gt; Path:
        target_dir = self.dataset_dir / &quot;split-videos&quot; / &quot;testing&quot; / f&quot;&#123;fps&#125;FPS&quot;
        target_name = (f&quot;&#123;scenario_id&#125;_testing-videos_&#123;fps&#125;FPS_&#123;view&#125;_&#123;take&#125;_&#123;scenario_name&#125;.mp4&quot;)
        target_path = target_dir / target_name
        if target_path.exists():
            return target_path

        # 30-FPS source: either present locally or auto-fetchable.
        source_name = (f&quot;&#123;scenario_id&#125;_testing-videos_30FPS_&#123;view&#125;_&#123;take&#125;_&#123;scenario_name&#125;.mp4&quot;)
        source_rel = f&quot;split-videos/testing/30FPS/&#123;source_name&#125;&quot;
        source_path = self.dataset_dir / source_rel
        self._ensure_remote_asset(source_rel, source_path)
        if fps == _DEFAULT_FPS:
            return source_path

        # FPS-convert and cache so repeat runs are free.
        cache_dir = self.cache_dir / &quot;split-videos&quot; / &quot;testing&quot; / f&quot;&#123;fps&#125;FPS&quot;
        cache_dir.mkdir(parents=True, exist_ok=True)
        cached_path = cache_dir / target_name
        if not cached_path.exists():
            _convert_video_fps(source_path, cached_path, fps_new=fps)
        return cached_path

    def _resolve_switch_frame_path(
        self,
        *,
        scenario_id: str,
        view: str,
        scenario_name: str,
    ) -&gt; Path:
        rel = (f&quot;switch-frames/&#123;scenario_id&#125;_switch-frames_anyFPS_&#123;view&#125;_&#123;scenario_name&#125;.jpg&quot;)
        target_path = self.dataset_dir / rel
        self._ensure_remote_asset(rel, target_path)
        return target_path

    def _resolve_real_mask_path(
        self,
        *,
        scenario_id: str,
        view: str,
        take: str,
        scenario_name: str,
        fps: int,
    ) -&gt; Path:
        # Source release ships masks at 30 FPS only; non-30 rates are
        # regenerated downstream from the (downsampled) real videos by
        # the metric — see upstream ``run_physics_iq.py::ensure_binary_mask_structure``.
        # We only auto-fetch 30 FPS here.
        rel = (f&quot;video-masks/real/30FPS/&quot;
               f&quot;&#123;scenario_id&#125;_video-masks_30FPS_&#123;view&#125;_&#123;take&#125;_&#123;scenario_name&#125;.mp4&quot;)
        target_path = self.dataset_dir / rel
        self._ensure_remote_asset(rel, target_path)
        if fps == _DEFAULT_FPS:
            return target_path
        # Caller asked for a non-30 rate; metric layer handles the
        # regeneration. Return the canonical 30 FPS path so the metric
        # always sees a valid mp4 it can transcode.
        return target_path

    def _ensure_remote_asset(self, rel_path: str, target_path: Path) -&gt; Path:
        &quot;&quot;&quot;Download ``&lt;bucket_url&gt;/&lt;rel_path&gt;`` into *target_path* on miss.

        Atomic via a sibling ``.part`` file; safe under concurrent runs
        because the final ``rename`` is atomic on POSIX. Raises
        ``FileNotFoundError`` if the file is missing and ``auto_download``
        is False.
        &quot;&quot;&quot;
        if target_path.exists():
            return target_path
        if not self.auto_download:
            raise FileNotFoundError(f&quot;Physics-IQ asset missing: &#123;target_path&#125;. &quot;
                                    &quot;Set auto_download=True or pass dataset_root= a pre-downloaded copy.&quot;)
        target_path.parent.mkdir(parents=True, exist_ok=True)
        url = f&quot;&#123;self.bucket_url&#125;/&#123;rel_path.lstrip(&#x27;/&#x27;)&#125;&quot;
        tmp_path = target_path.with_suffix(target_path.suffix + &quot;.part&quot;)
        try:
            urlretrieve(url, tmp_path)
        except Exception as exc:
            if tmp_path.exists():
                tmp_path.unlink()
            raise FileNotFoundError(f&quot;Failed to fetch Physics-IQ asset &#123;url&#125; -&gt; &#123;target_path&#125;: &quot;
                                    f&quot;&#123;type(exc).__name__&#125;: &#123;exc&#125;&quot;) from exc
        tmp_path.rename(target_path)
        return target_path</code></pre></details>
</div>
