mt_metadata.timeseries.run
Classes
Base class for all metadata objects with Pydantic validation. |
Module Contents
- class mt_metadata.timeseries.run.Run(**data)
Bases:
mt_metadata.base.MetadataBaseBase class for all metadata objects with Pydantic validation.
MetadataBase extends DotNotationBaseModel (which inherits from Pydantic’s BaseModel) to provide automatic validation according to metadata standards. It adds functionality beyond dictionaries, supporting JSON, XML, pandas Series, and other formats for metadata interchange.
- _skip_equals
Private attribute listing fields to skip in equality comparisons
- Type:
list[str]
- _fields
Private attribute caching field information
- Type:
dict[str, Any]
Notes
All field assignments are validated automatically via Pydantic
None values are converted to appropriate defaults (empty string or 0.0)
Supports nested attribute access via dot notation
Thread-safe for read operations after initialization
- channels_recorded_auxiliary: Annotated[list[str], Field(default_factory=list, description='List of auxiliary channels recorded', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['[T]']})]
- channels_recorded_electric: Annotated[list[str], Field(default_factory=list, description='List of electric channels recorded', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['[Ex , Ey]']})]
- channels_recorded_magnetic: Annotated[list[str], Field(default_factory=list, description='List of magnetic channels recorded', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['[Hx , Hy , Hz]']})]
- channels_recorded_all()
List of all channels recorded in the run.
- comments: Annotated[mt_metadata.common.Comment, Field(default_factory=Comment, description='Any comments on the run.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['cows chewed cables']})]
- data_type: Annotated[mt_metadata.common.DataTypeEnum, Field(default=DataTypeEnum.BBMT, description='Type of data recorded for this run.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['BBMT']})]
- id: Annotated[str, Field(default='', description='Run ID should be station name followed by a number or character. Characters should only be used if the run number is small, if the run number is high consider using digits with zeros. For example if you have 100 runs the run ID could be 001 or {station}001.', alias=None, pattern='^[a-zA-Z0-9_]*$', json_schema_extra={'units': None, 'required': True, 'examples': ['001']})]
- sample_rate: Annotated[float, Field(default=0.0, description='Digital sample rate for the run', alias=None, json_schema_extra={'units': 'samples per second', 'required': True, 'examples': ['100']})]
- acquired_by: Annotated[mt_metadata.common.AuthorPerson, Field(default_factory=AuthorPerson, description='Information about the group that collected the data.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Person()']})]
- metadata_by: Annotated[mt_metadata.common.AuthorPerson, Field(default_factory=AuthorPerson, description='Information about the group that collected the metadata.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Person()']})]
- provenance: Annotated[mt_metadata.common.Provenance, Field(default_factory=Provenance, description='Provenance information about the run.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Provenance()']})]
- time_period: Annotated[mt_metadata.common.TimePeriod, Field(default_factory=TimePeriod, description='Time period for the run.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ["TimePeriod(start='2020-01-01', end='2020-12-31')"]})]
- data_logger: Annotated[mt_metadata.timeseries.DataLogger, Field(default_factory=DataLogger, description='Data Logger information used to collect the run.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['DataLogger()']})]
- fdsn: Annotated[mt_metadata.common.Fdsn, Field(default_factory=Fdsn, description='FDSN information for the run.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Fdsn()']})]
- channels: Annotated[mt_metadata.common.list_dict.ListDict | list | dict | collections.OrderedDict, Field(default_factory=ListDict, description='ListDict of channel objects collected in this run.', alias=None, exclude=True, json_schema_extra={'units': None, 'required': False, 'examples': ['ListDict(Electric(), Magnetic(), Auxiliary())']})]
- classmethod validate_comments(value, info)
Validate that the value is a valid comment.
- classmethod validate_data_type(value, info)
Validate that the data_type is a string.
- classmethod validate_list_of_strings(value, info)
Validate that the value is a list of strings.
- validate_channels_recorded()
Validate that the value is a list of strings.
- classmethod validate_channels(value, info)
- merge(other, inplace=True)
Merge channels from another Run into this run.
Combines channels from two runs and updates the channels recorded lists and time period.
- Parameters:
other (Run) – Another Run object whose channels will be merged into this run.
inplace (bool, optional) – If True, update this run and update time period. If False, return a copy of the merged run (default is True).
- Returns:
If inplace is False, returns a copy of the merged Run. Otherwise None.
- Return type:
Run | None
- Raises:
TypeError – If other is not a Run object.
Examples
Merge runs in place:
>>> run1 = Run(id='001') >>> run1.add_channel(Electric(component='ex')) >>> run2 = Run(id='002') >>> run2.add_channel(Magnetic(component='hx')) >>> run1.merge(run2, inplace=True) >>> print(run1.channels_recorded_all) ['ex', 'hx']
Merge and return new run:
>>> merged_run = run1.merge(run2, inplace=False) >>> print(merged_run.channels_recorded_all) ['ex', 'hx']
See also
updateUpdate metadata from another run
- update(other, match=[])
Update attribute values from another Run object.
Copies non-None, non-default attribute values from another Run object to this one. Skips empty values like None, 0.0, [], empty strings, and default timestamps.
- Parameters:
other (Run) – Another Run object to copy attributes from.
match (list[str] | None, optional) – List of attribute names that must match between runs before updating. If any don’t match, raises ValueError. Typically used for ‘id’ to ensure runs are compatible (default is None).
- Raises:
ValueError – If any attributes in match list don’t have equal values.
TypeError – If other is not a compatible Run type.
Examples
Basic update:
>>> run1 = Run(id='001', sample_rate=256.0) >>> run2 = Run(id='001', sample_rate=0.0) >>> run2.acquired_by.author = 'J. Doe' >>> run1.update(run2) >>> print(run1.acquired_by.author) 'J. Doe' >>> print(run1.sample_rate) # Not updated (run2 has default 0.0) 256.0
Update with matching check:
>>> run1 = Run(id='001') >>> run2 = Run(id='002') >>> try: ... run1.update(run2, match=['id']) ... except ValueError as e: ... print("IDs don't match!") IDs don't match!
Notes
Channel metadata is also updated. For each channel in other, if the channel exists in this run, it’s updated; if not, it’s added.
Skipped values:
None
0.0
Empty lists []
Empty strings ‘’
Default timestamp ‘1980-01-01T00:00:00+00:00’
See also
mergeMerge channels from another run
- has_channel(component)
Check if a channel with the given component exists in the run.
- Parameters:
component (str) – Channel component name to search for (e.g., ‘ex’, ‘hy’).
- Returns:
True if channel exists, False otherwise.
- Return type:
bool
Examples
>>> run = Run(id='001') >>> run.add_channel(Electric(component='ex')) >>> print(run.has_channel('ex')) True >>> print(run.has_channel('ey')) False
See also
get_channelRetrieve a channel object
channel_indexGet the index of a channel
- channel_index(component)
Get the index of a channel in the channels_recorded_all list.
- Parameters:
component (str) – Channel component name to search for (e.g., ‘ex’, ‘hy’).
- Returns:
Index of the channel if found, None otherwise.
- Return type:
int | None
Examples
>>> run = Run(id='001') >>> run.add_channel(Electric(component='ex')) >>> run.add_channel(Electric(component='ey')) >>> run.add_channel(Magnetic(component='hx')) >>> print(run.channel_index('ey')) 1 >>> print(run.channel_index('hz')) None
Notes
Channels are sorted alphabetically in channels_recorded_all.
See also
has_channelCheck if channel exists
get_channelRetrieve channel object
- get_channel(component)
Retrieve a channel object by component name.
- Parameters:
component (str) – Channel component name to retrieve (e.g., ‘ex’, ‘hy’).
- Returns:
Channel object if found, None otherwise. Return type depends on the channel type.
- Return type:
Examples
>>> run = Run(id='001') >>> ex = Electric(component='ex', dipole_length=100.0) >>> run.add_channel(ex) >>> channel = run.get_channel('ex') >>> print(type(channel).__name__) 'Electric' >>> print(channel.dipole_length) 100.0 >>> print(run.get_channel('ey')) None
See also
has_channelCheck if channel exists
add_channelAdd a channel to the run
- add_channel(channel_obj, update=True)
Add or update a channel in the run.
If the channel already exists (matched by component), its metadata is updated. If it doesn’t exist, it’s added to the channels list. Can accept channel objects, dictionaries, or component strings.
- Parameters:
channel_obj (Electric | Magnetic | Auxiliary | dict | str) –
Channel to add. Can be:
Channel object (Electric, Magnetic, or Auxiliary)
Dictionary with channel attributes (must include ‘type’ or ‘component’)
String component name (e.g., ‘ex’, ‘hy’, ‘temp’)
If string, channel type is inferred:
Starts with ‘e’ → Electric
Starts with ‘h’ or ‘b’ or equals ‘magnetic’ → Magnetic
Otherwise → Auxiliary
update (bool, optional) – If True, update the run’s time period to include this channel’s time period. If False, don’t update time period (default is True).
Examples
Add channel objects:
>>> run = Run(id='001') >>> ex = Electric(component='ex', dipole_length=100.0) >>> run.add_channel(ex) >>> print(run.channels_recorded_electric) ['ex']
Add from string (infers type):
>>> run.add_channel('hy') >>> run.add_channel('temperature') >>> print(run.channels_recorded_magnetic) ['hy'] >>> print(run.channels_recorded_auxiliary) ['temperature']
Add from dictionary:
>>> channel_dict = { ... 'type': 'electric', ... 'component': 'ey', ... 'dipole_length': 95.0 ... } >>> run.add_channel(channel_dict)
Update existing channel:
>>> ex_updated = Electric(component='ex', dipole_length=105.0) >>> run.add_channel(ex_updated) # Updates existing 'ex' >>> print(run.get_channel('ex').dipole_length) 105.0
Add without updating time period:
>>> run.add_channel('hz', update=False)
Notes
This method automatically:
Updates channels_recorded lists
Updates run time period (if update=True)
Converts string/dict inputs to proper channel objects
Logs when updating existing channels
See also
remove_channelRemove a channel from the run
get_channelRetrieve a channel object
update_time_periodManually update time period
- remove_channel(channel_id)
Remove a channel from the run.
- Parameters:
channel_id (str) – Channel component name to remove (e.g., ‘ex’, ‘hy’).
Examples
>>> run = Run(id='001') >>> run.add_channel(Electric(component='ex')) >>> run.add_channel(Electric(component='ey')) >>> print(run.channels_recorded_electric) ['ex', 'ey'] >>> run.remove_channel('ex') >>> print(run.channels_recorded_electric) ['ey'] >>> run.remove_channel('ez') # Doesn't exist # Logs warning: Could not find ez to remove.
Notes
Automatically updates the channels_recorded lists after removal. Logs a warning if the channel is not found.
See also
add_channelAdd a channel to the run
has_channelCheck if channel exists
- update_channel_keys()
Update channel dictionary keys to match current component values.
Updates the keys in the channels ListDict to match current channel components. Useful when channel components have been modified after channels were added, ensuring channels can be accessed by their current component values.
- Returns:
Mapping of old keys to new keys showing what was changed.
- Return type:
dict[str, str]
Examples
Fix keys after modifying components:
>>> run = Run(id='001') >>> channel = Electric(component='') >>> run.add_channel(channel) >>> # Channel is stored with empty string key >>> channel.component = 'ex' >>> key_mapping = run.update_channel_keys() >>> print(key_mapping) {'': 'ex'} >>> # Now accessible as run.channels['ex'] >>> print(run.get_channel('ex').component) 'ex'
Multiple key updates:
>>> run = Run(id='001') >>> ch1 = Electric(component='e1') >>> ch2 = Magnetic(component='h1') >>> run.add_channel(ch1) >>> run.add_channel(ch2) >>> ch1.component = 'ex' >>> ch2.component = 'hx' >>> mapping = run.update_channel_keys() >>> print(mapping) {'e1': 'ex', 'h1': 'hx'}
Notes
This is typically only needed if you’ve directly modified channel component attributes after adding them to the run. Normal usage doesn’t require calling this method.
See also
add_channelAdd channels to the run
get_channelAccess channels by component
- property n_channels: int
Number of channels in the run.
- Returns:
Count of channels currently in the run.
- Return type:
int
Examples
>>> run = Run(id='001') >>> print(run.n_channels) 0 >>> run.add_channel('ex') >>> run.add_channel('hy') >>> print(run.n_channels) 2
- update_time_period()
Update run’s time period to encompass all channel time periods.
Examines all channels in the run and updates the run’s start and end times to include the earliest start and latest end from all channels. Ignores default timestamp ‘1980-01-01T00:00:00+00:00’.
Examples
>>> from mt_metadata.timeseries import Run, Electric >>> run = Run(id='001') >>> ex = Electric(component='ex') >>> ex.time_period.start = '2020-01-01T00:00:00+00:00' >>> ex.time_period.end = '2020-01-01T01:00:00+00:00' >>> run.add_channel(ex, update=False) >>> print(run.time_period.start) 1980-01-01T00:00:00+00:00 >>> run.update_time_period() >>> print(run.time_period.start) 2020-01-01T00:00:00+00:00
Multiple channels:
>>> ey = Electric(component='ey') >>> ey.time_period.start = '2020-01-01T00:30:00+00:00' >>> ey.time_period.end = '2020-01-01T02:00:00+00:00' >>> run.add_channel(ey, update=True) >>> print(run.time_period.start) # Uses earliest 2020-01-01T00:00:00+00:00 >>> print(run.time_period.end) # Uses latest 2020-01-01T02:00:00+00:00
Notes
Only updates if channels exist (n_channels > 0)
Ignores channels with default timestamp
Always expands time period, never shrinks it
Automatically called by add_channel() when update=True
See also
add_channelAdd channel and optionally update time period