# Transfer-Function Dataset Accessor (ds.tf)
## Overview
MTpy-v2 stores every MT transfer function as an xarray.Dataset whose data variables (transfer_function, transfer_function_error, transfer_function_model_error) use labelled output, input, and period dimensions. The `tf` accessor is registered on every such dataset so that all derived quantities — impedance, phase tensor, tipper, and correction routines — are reachable through a single, discoverable entry point:
`python
ds.tf.<property_or_method>
`
The accessor is the single source of truth for transfer-function math. Both the MT object and the MTData collection delegate their core operations (rotate, interpolate, remove_static_shift, remove_distortion) to it.
—
## Architecture
``` xr.Dataset (period × output × input) │ └── .tf ← TFDatasetAccessor (registered via @xr.register_dataset_accessor)
│ ├── Data access │ z(), z_error(), z_model_error() │ tipper(), tipper_error(), tipper_model_error() │ ├── Derived Z quantities (properties, all lazy) │ resistivity, phase │ resistivity_error, phase_error │ resistivity_model_error, phase_model_error │ Per-component aliases: res_xx / res_xy / res_yx / res_yy │ phase_xx / phase_xy / phase_yx / phase_yy │ Determinant variants: det, res_det, phase_det (+ error counterparts) │ ├── Tipper quantities │ t_zx, t_zy (+ error variants) │ tipper_amplitude, tipper_phase, tipper_mag_real/imag, tipper_angle_real/imag │ ├── Phase tensor quantities │ pt_xx / pt_xy / pt_yx / pt_yy (+ error counterparts) │ pt_phimax / pt_phimin, pt_azimuth, pt_skew │ pt_ellipticity, pt_eccentricity, pt_trace, pt_det │ pt_alpha, pt_beta (+ all error counterparts) │ phase_tensor → PhaseTensor object │ invariants → dict of standard PT invariants │ ├── Object converters │ to_z() → Z │ to_tipper() → Tipper │ to_pt() → PhaseTensor │ ├── Builders (write back into the dataset) │ with_z(z_obj) → updates transfer_function* variables │ with_tipper(t_obj) → updates tipper channel block │ with_res_phase(…) → constructs Z from ρ/φ, then writes │ set_resistivity_phase(…) → alias │ set_amp_phase(…) → Tipper setter alias │ set_mag_direction(…) → Tipper setter alias │ ├── Operations (return new Dataset or modify in-place) │ rotate(alpha, coordinate_reference_frame=’ned’, inplace=False) │ interpolate(new_periods, method=’slinear’, inplace=False) │ └── Correction / analysis helpers
remove_ss(ss_x, ss_y, as_dataset=False) remove_distortion(distortion_tensor, as_dataset=False) estimate_dimensionality() estimate_distortion() estimate_depth_of_investigation()
### Where the math lives
All numerical formulas are collected in mtpy/core/transfer_function/tf_helpers.py. The legacy Z, Tipper, and PhaseTensor classes import from the same module, so there is exactly one implementation of every formula shared across the entire stack.
—
## Integration with MT and MTData
### MT
The MT object stores its transfer function as self._transfer_function (an xr.Dataset). The accessor is therefore always available as:
`python
mt._transfer_function.tf
`
The following MT methods delegate entirely to the accessor:
|---|—| | mt.rotate(angle) | self._transfer_function.tf.rotate(angle, inplace=True) | | mt.interpolate(periods) | self._transfer_function.tf.interpolate(periods) | | mt.remove_static_shift(sx, sy) | self._transfer_function.tf.remove_ss(sx, sy, as_dataset=True) | | mt.remove_distortion(D) | self._transfer_function.tf.remove_distortion(D, as_dataset=True) |
### MTData
MTData is a tree-backed collection of MT stations stored as xr.Dataset nodes inside an xr.DataTree. The two per-station static helpers that were previously large self-contained routines now delegate to the accessor:
|---|—| | _rotate_station_dataset(ds, angle, crf) | ds.load().tf.rotate(angle, coordinate_reference_frame=crf) | | _interpolate_station_dataset(ds, periods) | ds.tf.interpolate(periods) |
Bulk methods rotate(), interpolate(), rotate_dask(), interpolate_dask(), and interpolate_lazy() all flow through these helpers and therefore implicitly use the accessor.
—
## Usage Examples
### Accessing derived quantities
mt = MT(“path/to/station.edi”) mt.read()
ds = mt._transfer_function # xr.Dataset
# Impedance tensor (complex, shape n_periods × 2 × 2) z_array = ds.tf.z()
# Apparent resistivity and phase (shape n_periods × 2 × 2) rho = ds.tf.resistivity phi = ds.tf.phase
# Off-diagonal components only rho_xy = ds.tf.res_xy # shape (n_periods,) phi_yx = ds.tf.phase_yx
# Determinant resistivity rho_det = ds.tf.res_det
# Phase tensor azimuth azimuth = ds.tf.pt_azimuth ```
### Converting to legacy objects
`python
z_obj = ds.tf.to_z() # Z instance
t_obj = ds.tf.to_tipper() # Tipper instance
pt_obj = ds.tf.to_pt() # PhaseTensor instance
`
### Rotating a dataset
```python # Returns a new dataset rotated 30 degrees clockwise (NED convention) rotated_ds = ds.tf.rotate(30)
# In-place rotation ds.tf.rotate(30, inplace=True) ```
### Interpolating to a new period grid
new_periods = np.logspace(-3, 3, 50) interp_ds = ds.tf.interpolate(new_periods)
# The resulting dataset has len(new_periods) entries along the period axis print(interp_ds.tf.z().shape) # (50, 2, 2) ```
### Removing static shift and distortion
```python # Remove static shift: sx applied to Ex row, sy to Ey row corrected_ds = ds.tf.remove_ss(sx=0.8, sy=1.2, as_dataset=True)
# Remove a known distortion tensor import numpy as np D = np.array([[1.1, 0.05], [0.02, 0.95]]) undistorted_ds = ds.tf.remove_distortion(D, as_dataset=True) ```
### Updating impedance values (builder pattern)
```python from mtpy.core.transfer_function.z import Z
# Create or modify a Z object then write it back z_obj = ds.tf.to_z() z_obj.z[:, 0, 0] = 0.0 # zero out Zxx ds.tf.with_z(z_obj, inplace=True) ```
### Builder methods (with_*) end-to-end
# Start from an existing station dataset ds = mt._transfer_function
# 1) Build/update impedance directly with with_z z_obj = ds.tf.to_z() z_obj.z[:, 0, 0] = np.nan # clear Zxx z_obj.z[:, 1, 1] = np.nan # clear Zyy ds1 = ds.tf.with_z(z_obj, inplace=False)
# 2) Build/update tipper directly with with_tipper t_obj = ds1.tf.to_tipper() t_obj.tipper[:, 0, 0] *= 0.95 t_obj.tipper[:, 0, 1] *= 1.05 ds2 = ds1.tf.with_tipper(t_obj, inplace=False)
# 3) Construct impedance from resistivity/phase with with_res_phase rho = ds2.tf.resistivity phi = ds2.tf.phase
# Example transformation: increase off-diagonal rho by 10% rho_mod = rho.copy() rho_mod[:, 0, 1] *= 1.10 rho_mod[:, 1, 0] *= 1.10
- ds3 = ds2.tf.with_res_phase(
resistivity=rho_mod, phase=phi, period=ds2.period.values, inplace=False,
)
# Optional aliases (equivalent builder entry points) ds4 = ds3.tf.set_resistivity_phase(
resistivity=ds3.tf.resistivity, phase=ds3.tf.phase, period=ds3.period.values, inplace=False,
)
### Tipper builder aliases
```python # Set tipper from amplitude/phase amp = ds.tf.tipper_amplitude phs = ds.tf.tipper_phase ds_amp = ds.tf.set_amp_phase(amp, phs, inplace=False)
# Set tipper from magnitude/direction pairs mag_real = ds_amp.tf.tipper_mag_real ang_real = ds_amp.tf.tipper_angle_real mag_imag = ds_amp.tf.tipper_mag_imag ang_imag = ds_amp.tf.tipper_angle_imag ds_mag = ds_amp.tf.set_mag_direction(
mag_real, ang_real, mag_imag, ang_imag, inplace=False,
)
### Working via MT
The MT API is the recommended entry point for single-station workflows. All the above operations are available directly:
mt = MT(“station.edi”) mt.read()
mt.rotate(30) # rotates in place mt.interpolate(np.logspace(-2, 2, 40)) # returns a new MT mt.remove_static_shift(ss_x=0.8, ss_y=1.2) mt.remove_distortion() ```
### Working via MTData
For multi-station workflows use MTData, which applies the same accessor operations to every station in the collection:
```python from mtpy.core import MTData
md = MTData() md.add_stations(mt_list)
# Rotate all stations by 15 degrees md.rotate(15)
# Interpolate all stations to a shared period grid periods = np.logspace(-3, 3, 60) md.interpolate(periods)
# Access the underlying xarray dataset for one station station_ds = md.get_station(“surveyA/st01”) print(station_ds.tf.res_xy) ```
—
## Testing
The accessor is covered by tests/core/transfer_function/test_tf_accessor.py (26+ test methods) and tests/core/test_mt.py::TestMTAccessorIntegration (14 tests verifying that MT methods correctly round-trip through the accessor).