# observation_sampling { #tvbo.adapters.observation_sampling }

`adapters.observation_sampling`

Backend-agnostic resolver for observation-monitor sampling step counts.

Observation models (e.g. ``BOLD_TVB``) are declared once as a backend-neutral YAML pipeline. The *number of samples* an observation emits, however, depends on the integration time-step ``dt`` chosen at run time, not on any value that can be frozen into the YAML. Freezing a step count into the pipeline (as ``subsample_to_period.stepsize = 180``) only holds when the input already sits on a particular stock grid; a backend that applies that literal to the raw integration grid produces the wrong sample count.

This module is the single source of truth that every Python backend (tvboptim, jax, tvb) uses to turn ``(declarative observation, integration dt)`` into the integer step counts that drive downsampling. Backends legitimately diverge only in *how* those counts are applied (circular-buffer convolution vs. functional window-mean+subsample vs. TVB monitor step-mod loop); the counts themselves must be identical.

It is intentionally free of heavy dependencies (no jax/tvb/juliacall, no ``tvbo.classes``) so both the tvboptim runtime module and the export adapters can import it cheaply and without circular-import risk.

## Classes

| Name | Description |
| --- | --- |
| [ObservationSampling](#tvbo.adapters.observation_sampling.ObservationSampling) | Resolved sampling step counts for one observation at a given ``dt``. |

### ObservationSampling { #tvbo.adapters.observation_sampling.ObservationSampling }

```python
adapters.observation_sampling.ObservationSampling(
    period,
    downsample_period,
    interim_istep,
    output_istep,
    output_interim_count,
)
```

Resolved sampling step counts for one observation at a given ``dt``.

#### Attributes {.doc-section .doc-section-attributes}

| Name                 | Type          | Description                                                            |
|----------------------|---------------|------------------------------------------------------------------------|
| period               | float \| None | Output sampling period in ms (``TR``).                                 |
| downsample_period    | float         | Interim/stock-grid period in ms.                                       |
| interim_istep        | int           | Integration steps per interim (stock) sample.                          |
| output_istep         | int           | Integration steps per output sample (``period / dt``).                 |
| output_interim_count | int           | Interim samples per output sample (``output_istep // interim_istep``). |

## Functions

| Name | Description |
| --- | --- |
| [resolve_observation_sampling](#tvbo.adapters.observation_sampling.resolve_observation_sampling) | Resolve an observation's sampling step counts from the integration ``dt``. |
| [tvb_iround](#tvbo.adapters.observation_sampling.tvb_iround) | TVB's ``iround``, character for character, so a monitor's step count is the one TVB would compute. |

### resolve_observation_sampling { #tvbo.adapters.observation_sampling.resolve_observation_sampling }

```python
adapters.observation_sampling.resolve_observation_sampling(
    observation,
    dt,
    *,
    default_downsample_period=4.0,
)
```

Resolve an observation's sampling step counts from the integration ``dt``.

This is the single supervenient resolver: it computes the integer step counts from the declarative observation plus the run-time ``dt``. Every Python backend routes through it so the emitted sample count is identical.

#### Parameters {.doc-section .doc-section-parameters}

| Name                      | Type   | Description                                                                                                              | Default    |
|---------------------------|--------|--------------------------------------------------------------------------------------------------------------------------|------------|
| observation               | Any    | A schema observation (must expose ``pipeline`` and either ``period`` or a ``TR`` parameter for output-sampled monitors). | _required_ |
| dt                        | float  | Integration time-step in ms.                                                                                             | _required_ |
| default_downsample_period | float  | Fallback interim period (ms) when the pipeline declares no ``stock_dt`` argument.                                        | `4.0`      |

#### Returns {.doc-section .doc-section-returns}

| Name   | Type                | Description                                                         |
|--------|---------------------|---------------------------------------------------------------------|
| An     | ObservationSampling | class:`ObservationSampling` with the resolved step counts. When the |
|        | ObservationSampling | observation declares no output period, ``output_istep`` /           |
|        | ObservationSampling | ``output_interim_count`` are 0.                                     |

### tvb_iround { #tvbo.adapters.observation_sampling.tvb_iround }

```python
adapters.observation_sampling.tvb_iround(value)
```

TVB's ``iround``, character for character, so a monitor's step count is the one TVB would compute.

The body is copied rather than reasoned about: it delegates to Python's ``round``, which is round-half-to-EVEN, and the ``- 0.5`` correction that follows only guards the float-representation case the TVB docstring cites. So this is not ``floor(x + 0.5)`` and differs from it at every exact half — ``tvb_iround(2.5)`` is 2, not 3. It is the canonical rounding shared by every backend, so that boundary ratios resolve to the same step count everywhere; a backend spelling it ``int(round(x))`` gets the same answer, and should call this anyway so the definition stays in one place.