mt_metadata.timeseries ====================== .. py:module:: mt_metadata.timeseries .. autoapi-nested-parse:: ====================== time series metadata ====================== :mod:`mt_metadata.timeseries` is a package that contains classes for describing time series metadata. MetadataBase Objects -------------------- * Diagnostic - Diagnostic information for data quality and instrument performance * Battery - Battery voltage and power supply metadata * Electrode - Electrode specifications and contact information * TimingSystem - GPS and timing system metadata (synchronization, accuracy) * AppliedFilter - Filters applied to time series data during acquisition or processing * FilterBase - Base class for all filter types (analog, digital, frequency response) * DataLogger - Data logger/acquisition system specifications and settings * Channel - Base channel metadata common to all channel types * ChannelBase - Fundamental channel properties and attributes * Auxiliary - Auxiliary channel metadata (e.g., temperature, humidity, tilt) * Electric - Electric field channel metadata (dipole length, orientation, electrode info) * Magnetic - Magnetic field channel metadata (sensor type, calibration) * Run - Time series run metadata (collection of channels recorded together) * Station - Station-level metadata (location, orientation, equipment) * Survey - Survey-level metadata (project information, participants, objectives) * Experiment - Top-level experiment metadata encompassing multiple surveys Created on Sun Apr 24 20:50:41 2020 :copyright: Jared Peacock (jpeacock@usgs.gov) :license: MIT Submodules ---------- .. toctree:: :maxdepth: 1 /source/api/mt_metadata/timeseries/auxiliary/index /source/api/mt_metadata/timeseries/battery/index /source/api/mt_metadata/timeseries/channel/index /source/api/mt_metadata/timeseries/data_logger/index /source/api/mt_metadata/timeseries/diagnostic/index /source/api/mt_metadata/timeseries/electric/index /source/api/mt_metadata/timeseries/electrode/index /source/api/mt_metadata/timeseries/experiment/index /source/api/mt_metadata/timeseries/filtered/index /source/api/mt_metadata/timeseries/filters/index /source/api/mt_metadata/timeseries/magnetic/index /source/api/mt_metadata/timeseries/run/index /source/api/mt_metadata/timeseries/station/index /source/api/mt_metadata/timeseries/stationxml/index /source/api/mt_metadata/timeseries/survey/index /source/api/mt_metadata/timeseries/timing_system/index /source/api/mt_metadata/timeseries/tools/index Classes ------- .. autoapisummary:: mt_metadata.timeseries.Diagnostic mt_metadata.timeseries.Battery mt_metadata.timeseries.Electrode mt_metadata.timeseries.TimingSystem mt_metadata.timeseries.AppliedFilter mt_metadata.timeseries.FilterBase mt_metadata.timeseries.DataLogger mt_metadata.timeseries.Channel mt_metadata.timeseries.ChannelBase mt_metadata.timeseries.Auxiliary mt_metadata.timeseries.Electric mt_metadata.timeseries.Magnetic mt_metadata.timeseries.Run mt_metadata.timeseries.Station mt_metadata.timeseries.Experiment Package Contents ---------------- .. py:class:: Diagnostic(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: end :type: Annotated[float | None, Field(default=None, description='Ending value of a diagnostic measurement.', alias=None, json_schema_extra={'examples': '10', 'type': 'number', 'units': None, 'required': False})] :value: None .. py:attribute:: start :type: Annotated[float | None, Field(default=None, description='Starting value of a diagnostic measurement.', alias=None, json_schema_extra={'examples': '12.3', 'type': 'number', 'units': None, 'required': False})] :value: None .. py:class:: Battery(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: type :type: Annotated[str | None, Field(default=None, description='Description of battery type.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'pb-acid gel cell', 'type': 'string'})] :value: None .. py:attribute:: id :type: Annotated[str | None, Field(default=None, description='battery id', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'battery01', 'type': 'string'})] :value: None .. py:attribute:: voltage :type: Annotated[mt_metadata.common.StartEndRange, Field(default=StartEndRange(), description='Range of voltages.', alias=None, json_schema_extra={'units': 'volts', 'required': False, 'examples': 'Range(minimum=0.0, maximum=1.0)', 'type': 'object'})] .. py:attribute:: comments :type: Annotated[mt_metadata.common.Comment, Field(default_factory=Comment, description='Any comments about the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'ambient air temperature was chilly, ice on cables'})] .. py:method:: validate_comments(value, info) :classmethod: Validate that the value is a valid comment. .. py:class:: Electrode(**data) Bases: :py:obj:`mt_metadata.common.Location`, :py:obj:`mt_metadata.common.Instrument` Positional location of a geographic point .. py:class:: TimingSystem(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: comments :type: Annotated[mt_metadata.common.Comment, Field(default_factory=Comment, description='Any comment on the timing system.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'GPS locked with internal quartz clock'})] .. py:attribute:: drift :type: Annotated[float, Field(default=0.0, description='Estimated drift of the timing system.', alias=None, json_schema_extra={'units': 'seconds', 'required': True, 'examples': '0.001'})] .. py:attribute:: type :type: Annotated[str, Field(default='GPS', description='Type of timing system.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': 'GPS'})] .. py:attribute:: uncertainty :type: Annotated[float, Field(default=0.0, description='Estimated uncertainty of the timing system.', alias=None, json_schema_extra={'units': 'seconds', 'required': True, 'examples': '0.0002'})] .. py:attribute:: n_satellites :type: Annotated[int | None, Field(default=None, description='Number of satellites used for timing.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': '6'})] .. py:method:: validate_comments(value, info) :classmethod: Validate that the value is a valid comment. .. py:class:: AppliedFilter(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: name :type: Annotated[str, Field(default=None, description='Name of the filter.', json_schema_extra={'units': None, 'required': True, 'examples': ['low pass']})] .. py:attribute:: applied :type: Annotated[bool, Field(default=True, description='Whether the filter has been applied.', json_schema_extra={'units': None, 'required': True, 'examples': ['True']})] .. py:attribute:: stage :type: Annotated[int | None, Field(default=None, description='Stage of the filter in the processing chain.', json_schema_extra={'units': None, 'required': False, 'examples': [1]})] .. py:attribute:: comments :type: Annotated[mt_metadata.common.comment.Comment, Field(default_factory=lambda: Comment(), description='Any comments on filters.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['low pass is not calibrated']})] .. py:method:: validate_comments(value, info) :classmethod: Validate that the value is a valid comment. .. py:method:: validate_name(value, info) :classmethod: Validate that the name is not empty or whitespace only. Also, replace '/' with ' per ' and convert to lower case for consistency. .. py:class:: FilterBase(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: name :type: Annotated[str, Field(default='', description='Name of filter applied or to be applied. If more than one filter input as a comma separated list.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': '"lowpass_magnetic"'})] .. py:attribute:: comments :type: Annotated[mt_metadata.common.Comment, Field(default_factory=lambda: Comment(), description='Any comments about the filter.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'ambient air temperature'})] .. py:attribute:: type :type: Annotated[str, Field(default='base', description='Type of filter, must be one of the available filters.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': 'fap_table'})] .. py:attribute:: units_in :type: Annotated[str, Field(default='', description="Name of the input units to the filter. Should be all lowercase and separated with an underscore, use 'per' if units are divided and '-' if units are multiplied.", alias=None, json_schema_extra={'units': None, 'required': True, 'examples': 'count'})] .. py:attribute:: units_out :type: Annotated[str, Field(default='', description="Name of the output units. Should be all lowercase and separated with an underscore, use 'per' if units are divided and '-' if units are multiplied.", alias=None, json_schema_extra={'units': None, 'required': True, 'examples': 'millivolt'})] .. py:attribute:: calibration_date :type: Annotated[mt_metadata.common.mttime.MTime | str | float | int | numpy.datetime64 | pandas.Timestamp | None, Field(default_factory=lambda: MTime(time_stamp=None), description='Most recent date of filter calibration in ISO format of YYY-MM-DD.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': '2020-01-01'})] .. py:attribute:: gain :type: Annotated[float, Field(default=1.0, description='scalar gain of the filter across all frequencies, producted with any frequency depenendent terms', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': '1.0'})] .. py:attribute:: sequence_number :type: Annotated[int, Field(default=0, description='Sequence number of the filter in the processing chain.', alias=None, ge=0, json_schema_extra={'units': None, 'required': True, 'examples': 1})] .. py:method:: validate_calibration_date(field_value) :classmethod: .. py:method:: validate_comments(value, info) :classmethod: .. py:method:: validate_type(value, info) :classmethod: Validate that the type of filter is set to "fir" .. py:method:: validate_units(value, info) :classmethod: validate units base on input string will return the long name :param value: unit string separated by either '/' for division or ' ' for multiplication. Or 'per' and ' ', respectively :type value: units string :param info: _description_ :type info: ValidationInfo :returns: return the long descriptive name of the unit. For example 'kilometers'. :rtype: str .. py:property:: units_in_object :type: mt_metadata.common.units.Unit .. py:property:: units_out_object :type: mt_metadata.common.units.Unit .. py:method:: make_obspy_mapping() .. py:property:: obspy_mapping mapping to an obspy filter :rtype: dict :type: return .. py:property:: total_gain :type: float Total gain of the filter :rtype: float :type: return .. py:method:: get_filter_description() :return: predetermined filter description based on the type of filter :rtype: string .. py:method:: from_obspy_stage(stage, mapping = None) :classmethod: Expected to return a multiply operation function :param cls: a filter object :type cls: filter object :param stage: Obspy stage filter :type stage: :class:`obspy.inventory.response.ResponseStage` :param mapping: dictionary for mapping from an obspy stage, defaults to None :type mapping: dict, optional :raises TypeError: If stage is not a :class:`obspy.inventory.response.ResponseStage` :return: the appropriate mt_metadata.timeseries.filter object :rtype: mt_metadata.timeseries.filter object .. py:method:: complex_response(frqs) .. py:method:: pass_band(frequencies, window_len = 5, tol = 0.5, **kwargs) Fast passband estimation using decimation (10-100x faster than original). Caveat: This should work for most Fluxgate and feedback coil magnetometers, and basically most filters having a "low" number of poles and zeros. This method is not 100% robust to filters with a notch in them. Try to estimate pass band of the filter from the flattest spots in the amplitude. Instead of checking every frequency point, this decimates the frequency array and only checks a subset of windows. The pass band region is then interpolated across the full array. The flattest spot is determined by calculating a sliding window with length `window_len` and estimating normalized std. ..note:: This only works for simple filters with on flat pass band. :param frequencies: array of frequencies :type frequencies: np.ndarray :param window_len: length of sliding window in points :type window_len: integer :param tol: the ratio of the mean/std should be around 1 tol is the range around 1 to find the flat part of the curve. :type tol: float :return: pass band frequencies [f_start, f_end] :rtype: np.ndarray or None .. py:method:: generate_frequency_axis(sampling_rate, n_observations) .. py:method:: plot_response(frequencies, x_units='period', unwrap=True, pb_tol=0.1, interpolation_method='slinear') .. py:property:: decimation_active if decimation is prescribed :rtype: bool :type: return .. py:class:: DataLogger(**data) Bases: :py:obj:`mt_metadata.common.Instrument` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: timing_system :type: Annotated[mt_metadata.timeseries.TimingSystem, Field(default_factory=TimingSystem, description='Timing system of the data logger.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'TimingSystem()'})] .. py:attribute:: firmware :type: Annotated[mt_metadata.common.Software, Field(default_factory=Software, description='Firmware of the data logger.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'Software()'})] .. py:attribute:: power_source :type: Annotated[mt_metadata.timeseries.Battery, Field(default_factory=Battery, description='Power source of the data logger.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'Battery()'})] .. py:attribute:: data_storage :type: Annotated[mt_metadata.common.Instrument, Field(default_factory=Instrument, description='Data storage of the data logger.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'Instrument()'})] .. py:class:: Channel(**data) Bases: :py:obj:`ChannelBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: sensor :type: Annotated[mt_metadata.common.Instrument, Field(default_factory=Instrument, description='Sensor for the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': 'Instrument()'})] .. py:attribute:: location :type: Annotated[mt_metadata.common.BasicLocation, Field(default_factory=BasicLocation, description='Location information for the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['BasicLocation(latitude=0.0, longitude=0.0, elevation=0.0)']})] .. py:class:: ChannelBase(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: channel_number :type: Annotated[int, Field(default=0, description='Channel number on the data logger.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['1']})] .. py:attribute:: channel_id :type: Annotated[str | None, Field(default=None, description='channel id given by the user or data logger', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['1001.11']})] .. py:attribute:: comments :type: Annotated[mt_metadata.common.Comment, Field(default_factory=Comment, description='Any comments about the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['ambient air temperature was chilly, ice on cables']})] .. py:attribute:: component :type: Annotated[str, Field(default='auxiliary_default', description="Name of the component measured, can be uppercase and/or lowercase. For now electric channels should start with an 'e' and magnetic channels start with an 'h', followed by the component. If there are multiples of the same channel the name could include an integer. {type}{component}{number} --> Ex01.", alias=None, pattern='\\w+', json_schema_extra={'units': None, 'required': True, 'examples': ['ex']})] .. py:attribute:: measurement_azimuth :type: Annotated[float, Field(default=0.0, description='Horizontal azimuth of the channel in measurement coordinate system spcified in station.orientation.reference_frame. Default reference frame is a geographic right-handed coordinate system with north=0, east=90, vertical=+ downward.', validation_alias=AliasChoices('measurement_azimuth', 'azimuth'), json_schema_extra={'units': 'degrees', 'required': True, 'examples': [0.0]})] .. py:attribute:: measurement_tilt :type: Annotated[float, Field(default=0.0, description='Vertical tilt of the channel in measurement coordinate system specified in station.orientation.reference_frame. Default reference frame is a geographic right-handed coordinate system with north=0, east=90, vertical=+ downward.', validation_alias=AliasChoices('measurement_tilt', 'dip'), json_schema_extra={'units': 'degrees', 'required': True, 'examples': [0]})] .. py:attribute:: sample_rate :type: Annotated[float, Field(default=0.0, description='Digital sample rate', validation_alias=AliasChoices('sample_rate', 'sampling_rate'), json_schema_extra={'units': 'samples per second', 'required': True, 'examples': [8.0]})] .. py:attribute:: translated_azimuth :type: Annotated[float | None, Field(default=None, description='Horizontal azimuth of the channel in translated coordinate system, this should only be used for derived product. For instance if you collected your data in geomagnetic coordinates and then translated them to geographic coordinates you would set measurement_azimuth=0, translated_azimuth=-12.5 for a declination angle of N12.5E.', alias=None, json_schema_extra={'units': 'degrees', 'required': False, 'examples': [0.0]})] .. py:attribute:: translated_tilt :type: Annotated[float | None, Field(default=None, description='Tilt of channel in translated coordinate system, this should only be used for derived product. For instance if you collected your data using a tripod you would set measurement_tilt=45, translated_tilt=0 for a vertical component.', alias=None, json_schema_extra={'units': 'degrees', 'required': False, 'examples': [0.0]})] .. py:attribute:: type :type: Annotated[str, Field(default='base', description='Data type for the channel, should be a descriptive word that a user can understand.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['temperature']})] .. py:attribute:: units :type: Annotated[str, Field(default='', description="Units of the data, should be in SI units and represented as the full name of the unit all lowercase. If a complex unit use 'per' and '-'.", alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['celsius']})] .. py:attribute:: data_quality :type: Annotated[mt_metadata.common.DataQuality, Field(default_factory=DataQuality, description='Data quality for the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['DataQuality()']})] .. py:attribute:: filters :type: Annotated[list[mt_metadata.timeseries.AppliedFilter], Field(default_factory=list, description='Filter data for the channel.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ["AppliedFilter(name='filter_name', applied=True, stage=1)"]})] .. py:attribute:: time_period :type: Annotated[mt_metadata.common.TimePeriod, Field(default_factory=TimePeriod, description='Time period for the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ["TimePeriod(start='2020-01-01', end='2020-12-31')"]})] .. py:attribute:: fdsn :type: Annotated[mt_metadata.common.Fdsn, Field(default_factory=Fdsn, description='FDSN information for the channel.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Fdsn()']})] .. py:method:: validate_component(value) :classmethod: make sure the value is all lower case .. py:method:: validate_comments(value, info) :classmethod: Validate that the value is a valid comment. .. py:method:: validate_units(value, info) :classmethod: validate units base on input string will return the long name :param value: unit string separated by either '/' for division or ' ' for multiplication. Or 'per' and ' ', respectively :type value: units string :param info: _description_ :type info: ValidationInfo :returns: return the long descriptive name of the unit. For example 'kilometers'. :rtype: str .. py:method:: validate_type(value, info) :classmethod: Validate that the type channel .. py:method:: parse_filters_string(value) :classmethod: Parse string representation of filters into list of AppliedFilter objects .. py:method:: validate_filters(value, info) :classmethod: sort the filters by stage number and check for duplicates .. py:method:: add_filter(applied_filter = None, name = None, applied = True, stage = None, comments = None) Add a filter to the filter list. :param name: Name of the filter. :type name: str :param applied: Whether the filter has been applied, by default True. :type applied: bool, optional :param stage: Stage of the filter in the processing chain, by default None. :type stage: int | None, optional .. py:property:: filter_names :type: list[str] List of filter names applied to the channel. :returns: List of filter names. :rtype: list[str] .. py:method:: remove_filter(name, reset_stages = True) Remove a filter from the filter list. :param name: Name of the filter to remove. :type name: str :param reset_stages: Whether to reset the stages of the remaining filters, by default True. :type reset_stages: bool, optional .. py:method:: get_filter(name) Get a filter from the filter list by name. :param name: Name of the filter to get. :type name: str :returns: The filter with the given name, or None if not found. :rtype: AppliedFilter | None .. py:method:: channel_response(filters_dict) full channel response from a dictionary of filter objects .. py:property:: unit_object :type: mt_metadata.common.units.Unit Some channels have a unit object that is used to convert between units. This is a property that returns the unit object for the channel. The unit object is created using the units attribute of the channel. The unit object is used to convert between units and to get the unit :returns: BaseModel object with unit attributes :rtype: Unit .. py:method:: from_dict(meta_dict, skip_none = False) Fill attributes from a dictionary with backwards compatibility for legacy filter formats. :param meta_dict: Dictionary of attributes to set. :type meta_dict: dict :param skip_none: If True, skip attributes with None values, by default False. :type skip_none: bool, optional :raises MTSchemaError: If the input dictionary is not valid. .. rubric:: Notes Supports backwards compatibility for three filter formats: 1. **Legacy format** (oldest): - Keys: 'filter.applied', 'filter.name' - Values: Lists of booleans and strings 2. **Old format** (intermediate): - Keys: 'filtered.applied', 'filtered.name' - Values: Lists of booleans and strings 3. **New format** (current): - Key: 'filters' - Value: List of AppliedFilter objects or dictionaries All legacy formats are automatically converted to the new format using AppliedFilter objects. A warning is issued when legacy formats are detected. .. py:class:: Auxiliary(**data) Bases: :py:obj:`mt_metadata.timeseries.Channel` Auxiliary channel class for storing auxiliary channel information. .. py:attribute:: type :type: Annotated[str, Field(default='auxiliary', description='Data type for the channel, should be a descriptive word that a user can understand.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': 'auxiliary', 'type': 'string'})] .. py:class:: Electric(**data) Bases: :py:obj:`mt_metadata.timeseries.ChannelBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: component :type: Annotated[str, Field(default='e_default', description='Component of the electric field.', alias=None, pattern='^[eE][a-zA-Z0-9_]*$', json_schema_extra={'units': None, 'required': True, 'examples': ['Ex']})] .. py:attribute:: dipole_length :type: Annotated[float, Field(default=0.0, description='Length of the dipole as measured in a straight line from electrode to electrode.', alias=None, json_schema_extra={'units': 'meters', 'required': True, 'examples': ['55.25']})] .. py:attribute:: positive :type: Annotated[mt_metadata.timeseries.Electrode, Field(default_factory=Electrode, description='Positive electrode.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['Electrode()']})] .. py:attribute:: negative :type: Annotated[mt_metadata.timeseries.Electrode, Field(default_factory=Electrode, description='Negative electrode.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['Electrode()']})] .. py:attribute:: contact_resistance :type: Annotated[mt_metadata.common.StartEndRange, Field(default_factory=StartEndRange, description='Contact resistance start and end values.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['StartEndRange()']})] .. py:attribute:: ac :type: Annotated[mt_metadata.common.StartEndRange, Field(default_factory=StartEndRange, description='AC start and end values.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['StartEndRange()']})] .. py:attribute:: dc :type: Annotated[mt_metadata.common.StartEndRange, Field(default_factory=StartEndRange, description='DC start and end values.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['StartEndRange()']})] .. py:attribute:: type :type: Annotated[str, Field(default='electric', description='Data type for the channel, should be a descriptive word that a user can understand.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['electric']})] .. py:class:: Magnetic(**data) Bases: :py:obj:`mt_metadata.timeseries.Channel` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: component :type: Annotated[str, Field(default='h_default', description='Component of the magnetic field.', alias=None, pattern='^[hHbBrR][a-zA-Z0-9_]*$', json_schema_extra={'units': None, 'required': True, 'examples': ['hx']})] .. py:attribute:: h_field_min :type: Annotated[mt_metadata.common.StartEndRange, Field(default_factory=StartEndRange, description='minimum of field strength at the beginning and end', alias=None, json_schema_extra={'units': 'nanotesla', 'required': True, 'examples': ['StartEndRange(start=0.01, end=0.02)']})] .. py:attribute:: h_field_max :type: Annotated[mt_metadata.common.StartEndRange, Field(default_factory=StartEndRange, description='maximum of field strength at the beginning and end', alias=None, json_schema_extra={'units': 'nanotesla', 'required': True, 'examples': ['StartEndRange(start=0.1, end=2.0)']})] .. py:attribute:: type :type: Annotated[str, Field(default='magnetic', description='Data type for the channel, should be a descriptive word that a user can understand.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['magnetic']})] .. py:class:: Run(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: channels_recorded_auxiliary :type: Annotated[list[str], Field(default_factory=list, description='List of auxiliary channels recorded', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['[T]']})] .. py:attribute:: channels_recorded_electric :type: 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]']})] .. py:attribute:: channels_recorded_magnetic :type: 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]']})] .. py:method:: channels_recorded_all() List of all channels recorded in the run. .. py:attribute:: comments :type: 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']})] .. py:attribute:: data_type :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']})] .. py:attribute:: id :type: 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']})] .. py:attribute:: sample_rate :type: 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']})] .. py:attribute:: acquired_by :type: 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()']})] .. py:attribute:: metadata_by :type: 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()']})] .. py:attribute:: provenance :type: 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()']})] .. py:attribute:: time_period :type: 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')"]})] .. py:attribute:: data_logger :type: 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()']})] .. py:attribute:: fdsn :type: 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()']})] .. py:attribute:: channels :type: 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())']})] .. py:method:: validate_comments(value, info) :classmethod: Validate that the value is a valid comment. .. py:method:: validate_data_type(value, info) :classmethod: Validate that the data_type is a string. .. py:method:: validate_list_of_strings(value, info) :classmethod: Validate that the value is a list of strings. .. py:method:: validate_channels_recorded() Validate that the value is a list of strings. .. py:method:: validate_channels(value, info) :classmethod: .. py:method:: 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. :param other: Another Run object whose channels will be merged into this run. :type other: Run :param inplace: If True, update this run and update time period. If False, return a copy of the merged run (default is True). :type inplace: bool, optional :returns: If inplace is False, returns a copy of the merged Run. Otherwise None. :rtype: Run | None :raises TypeError: If other is not a Run object. .. rubric:: 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'] .. seealso:: :py:obj:`update` Update metadata from another run .. py:method:: 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. :param other: Another Run object to copy attributes from. :type other: Run :param match: 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). :type match: list[str] | None, optional :raises ValueError: If any attributes in match list don't have equal values. :raises TypeError: If other is not a compatible Run type. .. rubric:: 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! .. rubric:: 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' .. seealso:: :py:obj:`merge` Merge channels from another run .. py:method:: has_channel(component) Check if a channel with the given component exists in the run. :param component: Channel component name to search for (e.g., 'ex', 'hy'). :type component: str :returns: True if channel exists, False otherwise. :rtype: bool .. rubric:: Examples >>> run = Run(id='001') >>> run.add_channel(Electric(component='ex')) >>> print(run.has_channel('ex')) True >>> print(run.has_channel('ey')) False .. seealso:: :py:obj:`get_channel` Retrieve a channel object :py:obj:`channel_index` Get the index of a channel .. py:method:: channel_index(component) Get the index of a channel in the channels_recorded_all list. :param component: Channel component name to search for (e.g., 'ex', 'hy'). :type component: str :returns: Index of the channel if found, None otherwise. :rtype: int | None .. rubric:: 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 .. rubric:: Notes Channels are sorted alphabetically in channels_recorded_all. .. seealso:: :py:obj:`has_channel` Check if channel exists :py:obj:`get_channel` Retrieve channel object .. py:method:: get_channel(component) Retrieve a channel object by component name. :param component: Channel component name to retrieve (e.g., 'ex', 'hy'). :type component: str :returns: Channel object if found, None otherwise. Return type depends on the channel type. :rtype: Electric | Magnetic | Auxiliary | None .. rubric:: 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 .. seealso:: :py:obj:`has_channel` Check if channel exists :py:obj:`add_channel` Add a channel to the run .. py:method:: 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. :param channel_obj: 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 :type channel_obj: Electric | Magnetic | Auxiliary | dict | str :param update: 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). :type update: bool, optional .. rubric:: 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) .. rubric:: 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 .. seealso:: :py:obj:`remove_channel` Remove a channel from the run :py:obj:`get_channel` Retrieve a channel object :py:obj:`update_time_period` Manually update time period .. py:method:: remove_channel(channel_id) Remove a channel from the run. :param channel_id: Channel component name to remove (e.g., 'ex', 'hy'). :type channel_id: str .. rubric:: 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. .. rubric:: Notes Automatically updates the channels_recorded lists after removal. Logs a warning if the channel is not found. .. seealso:: :py:obj:`add_channel` Add a channel to the run :py:obj:`has_channel` Check if channel exists .. py:method:: 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. :rtype: dict[str, str] .. rubric:: 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'} .. rubric:: 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. .. seealso:: :py:obj:`add_channel` Add channels to the run :py:obj:`get_channel` Access channels by component .. py:property:: n_channels :type: int Number of channels in the run. :returns: Count of channels currently in the run. :rtype: int .. rubric:: Examples >>> run = Run(id='001') >>> print(run.n_channels) 0 >>> run.add_channel('ex') >>> run.add_channel('hy') >>> print(run.n_channels) 2 .. py:method:: 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'. .. rubric:: 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 .. rubric:: 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 .. seealso:: :py:obj:`add_channel` Add channel and optionally update time period .. py:class:: Station(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Base 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. .. attribute:: _skip_equals Private attribute listing fields to skip in equality comparisons :type: list[str] .. attribute:: _fields Private attribute caching field information :type: dict[str, Any] .. rubric:: 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 .. py:attribute:: channel_layout :type: Annotated[mt_metadata.common.ChannelLayoutEnum, Field(default=ChannelLayoutEnum.X, description='How the station channels were laid out.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['X']})] .. py:attribute:: channels_recorded :type: Annotated[list[str], Field(default_factory=list, description='List of components recorded by the station. Should be a summary of all channels recorded. Dropped channels will be recorded in Run metadata.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['"[ Ex, Ey, Hx, Hy, Hz, T]"']})] .. py:attribute:: comments :type: Annotated[mt_metadata.common.Comment, Field(default_factory=Comment, description='Any comments on the station.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['cows chewed cables']})] .. py:attribute:: data_type :type: Annotated[mt_metadata.common.DataTypeEnum, Field(default='BBMT', description='Type of data recorded. If multiple types input as a comma separated list.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['BBMT']})] .. py:attribute:: fdsn :type: Annotated[mt_metadata.common.Fdsn, Field(default_factory=Fdsn, description='FDSN information for the station.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Fdsn()']})] .. py:attribute:: geographic_name :type: Annotated[str, Field(default='', description='Closest geographic name to the station, usually a city, but could be another common geographic location.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['Whitehorse, YK']})] .. py:attribute:: id :type: Annotated[str, Field(default='', description='Station ID name. This should be an alpha numeric name that is typically 5-6 characters long. Commonly the project name in 2 or 3 letters and the station number.', alias=None, pattern='^[a-zA-Z0-9_-]*$', json_schema_extra={'units': None, 'required': True, 'examples': ['MT001']})] .. py:attribute:: run_list :type: Annotated[list[str], Field(default_factory=list, description='List of runs recorded by the station. Should be a summary of all runs recorded.', alias=None, json_schema_extra={'units': None, 'required': True, 'examples': ['[ mt001a, mt001b, mt001c ]']})] .. py:attribute:: location :type: Annotated[mt_metadata.common.StationLocation, Field(default_factory=StationLocation, description='Location of the station.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['StationLocation(latitude=60.0, longitude=-135.0)']})] .. py:attribute:: orientation :type: Annotated[mt_metadata.common.Orientation, Field(default_factory=Orientation, description='Orientation of the station.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Orientation(north=0, east=0, vertical=1)']})] .. py:attribute:: acquired_by :type: Annotated[mt_metadata.common.AuthorPerson, Field(default_factory=AuthorPerson, description='Group or person who acquired the data.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Person()']})] .. py:attribute:: provenance :type: Annotated[mt_metadata.common.Provenance, Field(default_factory=Provenance, description='Provenance of the data.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ['Provenance()']})] .. py:attribute:: time_period :type: Annotated[mt_metadata.common.TimePeriod, Field(default_factory=TimePeriod, description='Time period of the data.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ["TimePeriod(start='2020-01-01', end='2020-12-31')"]})] .. py:attribute:: runs :type: Annotated[mt_metadata.common.list_dict.ListDict | list | dict | collections.OrderedDict | tuple, Field(default_factory=ListDict, description='List of runs recorded by the station.', alias=None, json_schema_extra={'units': None, 'required': False, 'examples': ["[Run(id='mt001a'), Run(id='mt001b'), Run(id='mt001c')]"]})] .. py:method:: validate_comments(value, info) :classmethod: .. py:method:: validate_list_of_strings(value, info) :classmethod: Validate that the value is a list of strings. .. py:method:: validate_runs_and_channels_recorded() Validate that the value is a list of strings. .. py:method:: validate_station_id() Validate that the value is a list of strings. .. py:method:: validate_runs(value, info) :classmethod: .. py:method:: merge(other, inplace=False) .. py:property:: n_runs :type: int Return the number of runs in the station. :return: number of runs in the station :rtype: int .. py:method:: has_run(run_id) Check to see if the run id already exists :param run_id: run id verbatim :type run_id: string :return: Tru if exists, False if not :rtype: boolean .. py:method:: run_index(run_id) Get the index of the run_id :param run_id: run id verbatim :type run_id: string :return: index of the run :rtype: integer .. py:method:: update_channels_recorded() Update the channels recorded lists based on the channels in the run. .. py:method:: update_run_list() Update the run list based on the runs in the station. .. py:method:: update_time_period() update time period from run information .. py:method:: update_all() Update the time period, channels recorded and run list. .. py:method:: add_run(run_obj, update=True) Add a run, if one of the same name exists overwrite it. :param run_obj: run object to add :type run_obj: :class:`mt_metadata.timeseries.Run` .. py:method:: get_run(run_id) Get a :class:`mt_metadata.timeseries.Run` object from the given id :param run_id: run id verbatim :type run_id: string .. py:method:: remove_run(run_id, update=True) remove a run from the survey :param run_id: run id verbatim :type run_id: string .. py:method:: update_run_keys() Update the keys in the runs ListDict to match current run IDs. This is useful when run IDs have been modified after runs were added to the station, ensuring that runs can be accessed by their current ID values. :returns: mapping of old keys to new keys :rtype: dict .. rubric:: Example >>> station = Station() >>> run = Run() >>> run.id = "" # empty ID initially >>> station.add_run(run) >>> run.id = "001" # update the ID >>> key_mapping = station.update_run_keys() >>> print(key_mapping) # {'': '001'} >>> # Now run can be accessed as station.runs['001'] .. py:method:: sort_runs_by_time(inplace=True, ascending=True) return a list of runs sorted by start time in the order of ascending or descending. :param ascending: DESCRIPTION, defaults to True :type ascending: TYPE, optional :return: DESCRIPTION :rtype: TYPE .. py:class:: Experiment(**data) Bases: :py:obj:`mt_metadata.base.MetadataBase` Top level of the metadata .. py:attribute:: surveys :type: Annotated[mt_metadata.common.list_dict.ListDict | list | dict | collections.OrderedDict, Field(default_factory=ListDict, description='List of surveys in the experiment', title='List of Surveys', json_schema_extra={'required': False, 'units': None, 'examples': [{'id': 'survey_1'}, {'id': 'survey_2'}]})] .. py:method:: merge(other) Merge two Experiment objects .. py:property:: n_surveys :type: int .. py:method:: validate_surveys(value) :classmethod: set the survey list .. py:property:: survey_names :type: list[str] Return names of surveys in experiment .. py:method:: has_survey(survey_id) Has survey id :param survey_id: DESCRIPTION :type survey_id: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: survey_index(survey_id) Get survey index :param survey_id: DESCRIPTION :type survey_id: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: add_survey(survey_obj) Add a survey, if has the same name update that object. :param survey_obj: DESCRIPTION :type survey_obj: `:class:`mt_metadata.timeseries.Survey` :return: DESCRIPTION :rtype: TYPE .. py:method:: get_survey(survey_id) Get a survey from the survey id :param survey_id: DESCRIPTION :type survey_id: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: remove_survey(survey_id, update = True) Remove a survey from the experiment :param survey_id: DESCRIPTION :type survey_id: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: to_dict(nested = False, required = True) create a dictionary for the experiment object. :param nested: DESCRIPTION, defaults to False :type nested: TYPE, optional :param single: DESCRIPTION, defaults to False :type single: TYPE, optional :param required: DESCRIPTION, defaults to True :type required: TYPE, optional :return: DESCRIPTION :rtype: TYPE .. py:method:: from_dict(ex_dict, skip_none = True) fill from an input dictionary :param ex_dict: DESCRIPTION :type ex_dict: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: to_json(fn = None, nested = False, indent = ' ' * 4, required = True) Write a json string from a given object, taking into account other class objects contained within the given object. :param nested: make the returned json nested :type nested: [ True | False ] , default is False .. py:method:: from_json(json_str, skip_none = True) read in a json string and update attributes of an object :param json_str: json string or file path :type json_str: string or :class:`pathlib.Path` .. py:method:: to_xml(fn = None, required = True, sort = True) Write XML version of the experiment :param fn: DESCRIPTION :type fn: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: from_xml(fn = None, element = None, sort = True, skip_none = True) :param fn: DESCRIPTION, defaults to None :type fn: TYPE, optional :param element: DESCRIPTION, defaults to None :type element: TYPE, optional :return: DESCRIPTION :rtype: TYPE .. py:method:: to_pickle(fn = None) Write a pickle version of the experiment :param fn: DESCRIPTION :type fn: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: from_pickle(fn = None) Read pickle version of experiment :param fn: DESCRIPTION :type fn: TYPE :return: DESCRIPTION :rtype: TYPE .. py:method:: sort(inplace = True) sort surveys, stations, runs, channels alphabetically/numerically :param inplace: DESCRIPTION, defaults to True :type inplace: TYPE, optional :return: DESCRIPTION :rtype: TYPE