mth5.io.phoenix.readers.mtu

MTU_utils - Utility to read the Canadian Phoenix MTU-5A instrument time series binary files in Matlab (and Python)

A bunch of simple scripts to read the legacy Phoenix MTU-5A binary format files … including the time series (.TSN) and table (.TBL) formats.

Original files by:

DONG Hao donghao@cugb.edu.cn China University of Geosciences, Beijing

Updated and adapted to Python by: Peacock, J.R. (2025-12-31)

Submodules

Classes

MTUTable

MTUTSN

Reader for legacy Phoenix MTU-5A instrument time series binary files.

Package Contents

class mth5.io.phoenix.readers.mtu.MTUTable(file_path: str | pathlib.Path | None = None, **kwargs)[source]

The Phoenix TBL file is a series of 25-byte blocks containing key-value pairs: - Bytes 0-11: Tag name (4-character string, null-padded) - Bytes 12-24: Value (13 bytes, mixed data types)

Values can be decoded as follows: 1. INT (4 bytes): struct.unpack(‘<i’, bytes[0:4]) - Little-endian signed int 2. DOUBLE (8 bytes): struct.unpack(‘<d’, bytes[0:8]) - Little-endian double 3. CHAR (variable): bytes.decode(‘latin-1’).strip() - Null-terminated string 4. BYTE (1 byte): struct.unpack(‘<B’, bytes[0:1]) - Unsigned byte 5. TIME (6 bytes): [sec, min, hour, day, month, year-2000] format

The TBL_TAG_TYPES dictionary maps each known tag to its data type, enabling automatic decoding via decode_tbl_value() function. Unknown tags return raw bytes.

Example usage:

# Automatic decoding: tbl_dict = get_dictionary_from_tbl(‘file.TBL’, decode_values=True)

# Manual decoding with read_tbl (legacy): info = read_tbl(‘/path’, ‘file.TBL’)

read_tbl - reads a (binary) TBL table file of the legacy Phoenix format (MTU-5A) and output the “info” metadata dictionary.

Parameters:
  • fpath – path to the tbl

  • fname – name of the tbl file (including extensions)

Returns:

output dict of the TBL metadata

Return type:

info

SITE: site name SNUM: serial number (of the box) FILE: file name recorded CMPY: company/institute of the survey SRVY: survey project name EXLN: Ex channel dipole length EYLN: Ey channel dipole length NREF: North reference (true, or magnetic north) LNGG: longitude in degree-minute format (DDD MM.MM) LATG: latitude in degree-minute format (DD MM.MM) ELEV: elevation (in metres) HXSN: Hx channel coil serial number HYSN: Hy channel coil serial number HZSN: Hz channel coil serial number STIM: starting time (UTC) ETIM: ending time (UTC) LFRQ: powerline frequency for filtering (can only be 50 or 60 Hz) HGN: final H-channel gain HGNC: H-channel gain control: HGN = PA * 2^HGNC (note: PA =

PreAmplifier gain)

EGN: final E-channel gain EGNC: E-channel gain control: HGN = PA * 2^HGNC (note: PA =

PreAmplifier gain)

HSMP: L3 and L4 time slot in second (MTU-5A) or minute (MTU-5P),

this means the instrument will record L3NS seconds for L3 and L4NS seconds for L4, for every HSMP time slot.

L3NS: L3 sample time (in second) L4NS: L4 sample time (in second) SRL3: L3 sample rate SRL4: L4 sample rate SRL5: L5 sample rate HATT: H channel attenuation (1/4.3 for MTU-5A) HNOM: H channel normalization (mA/nT) TCMB: Type of comb filter (probably used to suppress the harmonics of the

powerline noise.

TALS: Type of anti-aliasing filter LPFR: Parameter of Low-pass/VLF filter. this is a quite complicated

part as the low-pass filter is simply an R-C circuit with a switch to connect to different capacitors. To ensure enough bandwidth (proportion to 1/RC), one should use smaller capacitors with larger ground resistance.

ACDC: AC/DC coupling (DC = 0, AC = 1; MT should always be DC) FSCV: full scaling A-D converter voltage (in unit of V) ======================================================================= note: Phoenix Legacy TBL is a straight-forward parameter-value metadata file, stored in a bizarre format. The parameter tag and value are stored in a series of 25-byte data blocks, in mixed data type: the first 12 bytes are reserved for the tag name (first 4 bytes as char). The values are stored in the 13 bytes afterwards, in various formats (char, int, float, etc.).

So a good practice is to read in those blocks one by one and extract all of them. However, not every thing is useful for the metadata, so I only extract a few of them, for now.

Original author: Hao 2012.07.04 Beijing

Translated to Python and enhanced by: J. Peacock (2025-12-31)

Main changes:

  • Encapsulated in MTUTable class

  • Automatic type detection and decoding based on TBL_TAG_TYPES

  • Added properties to extract metadata as mt_metadata objects


file_path
tbl_dict: dict[str, int | float | str | bytes]
TBL_TAG_TYPES
decode_tbl_value(value_bytes: bytes, data_type: str) int | float | str | bytes[source]

Decode TBL value bytes based on the specified data type.

Parameters:
  • value_bytes (bytes) – 13 bytes from position 12-24 in the 25-byte block containing the value.

  • data_type (str) – Type of the data: ‘int’, ‘double’, ‘char’, ‘byte’, or ‘time’.

Returns:

Decoded value in appropriate Python type. Returns raw bytes if decoding fails or data_type is unrecognized.

Return type:

int or float or str or bytes

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> value = tbl.decode_tbl_value(b'š...', 'int')
>>> print(value)
1690
read_tbl() None[source]

Read and decode the TBL file, populating the tbl_dict attribute.

This method reads the TBL file specified during initialization and decodes all tag-value pairs according to their known types. The results are stored in self.tbl_dict.

Returns:

Results are stored in the tbl_dict attribute.

Return type:

None

Examples

>>> tbl = MTUTable('/data/phoenix', '1690C16C.TBL')
>>> tbl.read_tbl()
>>> print(tbl.tbl_dict['SITE'])
'10441W10'
>>> print(tbl.tbl_dict['SNUM'])
1690
property channel_keys: dict[str, int]

Get list of channel keys present in the TBL metadata.

Returns:

Dictionary of channel keys and their corresponding values found in tbl_dict (e.g., ‘CHEX’, ‘CHEY’, ‘CHHX’, etc.).

Return type:

dict[str, int]

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> keys = tbl.channel_keys
>>> print(keys)
{'ex': 1, 'ey': 2, 'hx': 3, 'hy': 4, 'hz': 5}
property survey_metadata: mt_metadata.timeseries.Survey

Extract survey metadata from TBL file.

Returns:

mt_metadata Survey object populated with survey-level information from the TBL file (survey ID, company/author).

Return type:

Survey

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Survey object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> survey = tbl.survey_metadata
>>> print(survey.id)
'MT_Survey_2024'
property station_metadata: mt_metadata.timeseries.Station

Extract station metadata from TBL file.

Returns:

mt_metadata Station object populated with station-level information including location (latitude, longitude, elevation, declination) and time period.

Return type:

Station

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Station object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> station = tbl.station_metadata
>>> print(station.id)
'10441W10'
>>> print(f"{station.location.latitude:.6f}")
41.006467
property run_metadata: mt_metadata.timeseries.Run

Extract run metadata from TBL file.

Returns:

mt_metadata Run object populated with data logger information and channel metadata.

Return type:

Run

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Run object with a warning.

The run includes all channel metadata (ex, ey, hx, hy, hz) obtained from their respective property methods.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> run = tbl.run_metadata
>>> print(run.id)
'run_1690'
>>> print(run.data_logger.id)
'MTU_1690'
property ex_metadata: mt_metadata.timeseries.Electric

Extract Ex electric channel metadata from TBL file.

Returns:

mt_metadata Electric object for Ex component with dipole length, azimuth, AC/DC start values, and channel number.

Return type:

Electric

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Electric object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> ex = tbl.ex_metadata
>>> print(ex.dipole_length)
100.0
property ey_metadata: mt_metadata.timeseries.Electric

Extract Ey electric channel metadata from TBL file.

Returns:

mt_metadata Electric object for Ey component with dipole length, azimuth (Ex azimuth + 90°), AC/DC start values, and channel number.

Return type:

Electric

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Electric object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> ey = tbl.ey_metadata
>>> print(ey.dipole_length)
100.0
property hx_metadata: mt_metadata.timeseries.Magnetic

Extract Hx magnetic channel metadata from TBL file.

Returns:

mt_metadata Magnetic object for Hx component with maximum field, channel number, azimuth, and sensor serial number.

Return type:

Magnetic

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Magnetic object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> hx = tbl.hx_metadata
>>> print(hx.sensor.id)
'coil1693'
property hy_metadata: mt_metadata.timeseries.Magnetic

Extract Hy magnetic channel metadata from TBL file.

Returns:

mt_metadata Magnetic object for Hy component with maximum field, channel number, azimuth (Hx azimuth + 90°), and sensor serial number.

Return type:

Magnetic

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Magnetic object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> hy = tbl.hy_metadata
>>> print(hy.sensor.id)
'coil1694'
property hz_metadata: mt_metadata.timeseries.Magnetic

Extract Hz magnetic channel metadata from TBL file.

Returns:

mt_metadata Magnetic object for Hz component with maximum field, channel number, and sensor serial number.

Return type:

Magnetic

Notes

If TBL metadata has not been loaded (via read_tbl()), returns an empty Magnetic object with a warning.

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> hz = tbl.hz_metadata
>>> print(hz.sensor.id)
'coil1695'
property ex_calibration: float | None

Calculate Ex channel calibration factor.

Returns:

Calibration factor to convert raw ADC values to mV/km. Returns None if TBL metadata has not been loaded.

Return type:

float or None

Notes

The calibration factor is calculated as:

\text{cal} = \frac{\text{FSCV}}{2^{23}} \times \frac{1000}{\text{EGN}} \times \frac{1000}{\text{EXLN}}

where:

  • FSCV: Full-scale converter voltage

  • EGN: Electric channel gain

  • EXLN: Ex dipole length in meters

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> cal = tbl.ex_calibration
>>> print(f"{cal:.6f}")
0.000762
property ey_calibration: float | None

Calculate Ey channel calibration factor.

Returns:

Calibration factor to convert raw ADC values to mV/km. Returns None if TBL metadata has not been loaded.

Return type:

float or None

Notes

The calibration factor is calculated as:

\text{cal} = \frac{\text{FSCV}}{2^{23}} \times \frac{1000}{\text{EGN}} \times \frac{1000}{\text{EYLN}}

where:

  • FSCV: Full-scale converter voltage

  • EGN: Electric channel gain

  • EYLN: Ey dipole length in meters

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> cal = tbl.ey_calibration
>>> print(f"{cal:.6f}")
0.000762
property magnetic_calibration: float | None

Calculate magnetic channel calibration factor.

Returns:

Calibration factor to convert raw ADC values to nT. Returns None if TBL metadata has not been loaded.

Return type:

float or None

Notes

The calibration factor is calculated as:

\text{cal} = \frac{\text{FSCV}}{2^{23}} \times \frac{1000}{\text{HGN} \times \text{HATT} \times \text{HNOM}}

where:

  • FSCV: Full-scale converter voltage

  • HGN: Magnetic channel gain

  • HATT: Magnetic channel attenuation

  • HNOM: Magnetic channel normalization (mA/nT)

This calibration applies to all magnetic channels (Hx, Hy, Hz).

Examples

>>> tbl = MTUTable('/data', 'file.TBL')
>>> tbl.read_tbl()
>>> cal = tbl.magnetic_calibration
>>> print(f"{cal:.9f}")
0.000000229
class mth5.io.phoenix.readers.mtu.MTUTSN(file_path: str | pathlib.Path | None = None, **kwargs)[source]

Reader for legacy Phoenix MTU-5A instrument time series binary files.

Reads time series data from Phoenix MTU-5A (.TS2, .TS3, .TS4, .TS5) and V5-2000 system (.TSL, .TSH) binary files. The data consists of 24-bit signed integers organized in data blocks with headers.

Parameters:

file_path (str or Path or None, optional) – Path to the TSN file to read. If None, the reader is created without loading data. Default is None.

file_path

Path to the currently loaded TSN file.

Type:

Path or None

ts

Time series data array with shape (n_channels, n_samples).

Type:

ndarray or None

tag

Metadata dictionary containing file information.

Type:

dict

Examples

Read a TS3 file:

>>> from pathlib import Path
>>> reader = MTUTSN('data/1690C16C.TS3')
>>> print(reader.ts.shape)
(3, 86400)
>>> print(reader.tag['sample_rate'])
24

Create reader without loading data:

>>> reader = MTUTSN()
>>> reader.read('data/1690C16C.TS3')

Access metadata:

>>> reader = MTUTSN('data/1690C16C.TS4')
>>> reader.read()
>>> print(f"Channels: {reader.tag['n_ch']}")
Channels: 4
>>> print(f"Blocks: {reader.tag['n_block']}")
Blocks: 48
property file_path: pathlib.Path | None

Get the TSN file path.

ts = None
ts_metadata = None
get_sign24(x: numpy.ndarray | list | int) numpy.ndarray[source]

Convert unsigned 24-bit integers to signed integers.

Converts unsigned 24-bit values (0 to 16777215) to their signed equivalents (-8388608 to 8388607) by applying two’s complement.

Parameters:

x (ndarray or list or int) – Unsigned 24-bit integer value(s) to convert.

Returns:

Signed 24-bit integer value(s) as int32 array.

Return type:

ndarray

Examples

Convert a single positive value:

>>> reader = MTUTSN()
>>> reader.get_sign24(100)
array([100], dtype=int32)

Convert a single negative value (unsigned representation):

>>> reader.get_sign24(16777215)  # -1 in 24-bit signed
array([-1], dtype=int32)

Convert an array:

>>> values = np.array([0, 8388607, 8388608, 16777215])
>>> reader.get_sign24(values)
array([       0,  8388607, -8388608,       -1], dtype=int32)
read(file_path: str | pathlib.Path | None = None) None[source]

Read and parse a Phoenix MTU time series binary file.

Reads complete time series data from legacy Phoenix MTU-5A instrument files (.TS2, .TS3, .TS4, .TS5) or V5-2000 system files (.TSL, .TSH). Each file contains multiple data blocks with 24-bit signed integer samples organized by channel.

Parameters:

file_path (str or Path or None, optional) – Path to the TSN file to read. If None, uses the current file_path attribute. Default is None.

Returns:

  • ts (ndarray) – Time series data array with shape (n_channels, total_samples). Data type is float64. Each row represents one channel, and each column is a time sample.

  • tag (dict) – Metadata dictionary containing file information with keys:

    • ’box_number’ (int): Instrument serial number

    • ’ts_type’ (str): Instrument type (‘MTU-5’ or ‘V5-2000’)

    • ’sample_rate’ (int): Sampling frequency in Hz

    • ’n_ch’ (int): Number of channels

    • ’n_scan’ (int): Number of scans per data block

    • ’start’ (MTime): UTC timestamp of first sample

    • ’ts_length’ (float): Duration of each block in seconds

    • ’n_block’ (int): Total number of data blocks in file

Raises:
  • EOFError – If the file is empty or cannot be read.

  • ValueError – If the file has an unsupported extension or channel count.

  • FileNotFoundError – If the specified file does not exist.

Examples

Read a 3-channel TS3 file:

>>> reader = MTUTSN()
>>> ts, tag = reader.read('data/1690C16C.TS3')
>>> print(f"Shape: {ts.shape}")
Shape: (3, 86400)
>>> print(f"Sample rate: {tag['sample_rate']} Hz")
Sample rate: 24 Hz
>>> print(f"Duration: {ts.shape[1] / tag['sample_rate']:.1f} seconds")
Duration: 3600.0 seconds

Read a 4-channel TS4 file:

>>> reader = MTUTSN('data/1690C16C.TS4')
>>> print(f"Channels: {reader.tag['n_ch']}")
Channels: 4
>>> print(f"Start time: {reader.tag['start'].isoformat()}")
Start time: 2016-07-16T00:00:00+00:00

Read and process data:

>>> ts, tag = MTUTSN().read('data/station.TS5')
>>> # Calculate statistics for each channel
>>> for i in range(tag['n_ch']):
...     print(f"Ch{i} mean: {ts[i].mean():.2f}, std: {ts[i].std():.2f}")
Ch0 mean: 123.45, std: 456.78
Ch1 mean: -234.56, std: 567.89
...
to_runts(table_filepath: str | pathlib.Path | None = None, calibrate=True) mth5.timeseries.RunTS[source]

Create an MTUTable object from the TSN file and associated TBL file.

Parameters:

table_filepath (str or Path) – Path to the corresponding TBL file.

Returns:

An MTUTable object containing metadata from the TBL file.

Return type:

MTUTable

Examples

>>> reader = MTUTSN('data/1690C16C.TS3')
>>> mtu_table = reader.to_runts('data/1690C16C.TBL')
>>> print(mtu_table.metadata)
{...}