Skip to content

pysmo.classes

Concrete classes compatible with pysmo types.

The pysmo.classes module provides classes that implement one or more pysmo protocol types. These classes can be used directly with any pysmo function or tool that operates on pysmo types.

Classes:

Name Description
GeoCsvSeismogram

Import class for seismograms in the GeoCSV timeseries format.

SAC

Access and modify data stored in SAC files.

SacEvent

Helper class for SAC event attributes.

SacPZ

Import class for SAC PZ (pole-zero) files.

SacSeismogram

Helper class for SAC seismogram attributes.

SacStation

Helper class for SAC station attributes.

SacTimestamps

Helper class to access times stored in SAC headers as Timestamp objects.

StationXML

Import class for FDSN StationXML response metadata.

GeoCsvSeismogram

Bases: SeismogramEndtimeMixin

Import class for seismograms in the GeoCSV timeseries format.

Reads a waveform from the timeseries flavour of GeoCSV and exposes it as a Seismogram-compatible object.

This class is intended as a data-ingestion step. Once loaded, use clone_to_mini to convert the waveform to a MiniSeismogram, which can then be passed to copy_from_mini to populate another object such as a SAC instance. Instances can be modified in memory but there is no write-back to GeoCSV.

Examples:

>>> from pysmo import Seismogram
>>> from pysmo.classes import GeoCsvSeismogram
>>> text = '''\
... # dataset: GeoCSV 2.0
... # delimiter: ,
... # field_unit: UTC, Counts
... # field_type: datetime, INTEGER
... # SID: IU_ANMO_00_LHZ
... # sample_count: 3
... # sample_rate_hz: 1.0
... # start_time: 2010-02-27T06:30:00Z
... Time, Sample
... 2010-02-27T06:30:00Z, -47297
... 2010-02-27T06:30:01Z, -47298
... 2010-02-27T06:30:02Z, -47299'''
>>> seismogram = GeoCsvSeismogram.from_text(text)
>>> isinstance(seismogram, Seismogram)
True
>>> seismogram.sid
'IU_ANMO_00_LHZ'
>>> seismogram.data
array([-47297., -47298., -47299.])
>>> seismogram.end_time
Timestamp('2010-02-27 06:30:02+0000', tz='UTC')
>>>

Methods:

Name Description
fetch

Fetch and parse a seismogram from the EarthScope FDSN dataselect

from_text

Create a new instance from a GeoCSV text body.

Attributes:

Name Type Description
begin_time UtcTimestamp

Seismogram begin time.

data ndarray

Seismogram data.

delta PositiveTimedelta

Seismogram sampling interval.

sample_count int

Number of samples, always equal to len(data).

sid str

FDSN source identifier of the parsed GeoCSV data (e.g. IU_ANMO_00_LHZ).

Source code in src/pysmo/classes/_geocsv.py
@define(kw_only=True)
class GeoCsvSeismogram(SeismogramEndtimeMixin):
    """Import class for seismograms in the GeoCSV timeseries format.

    Reads a waveform from the timeseries flavour of
    [GeoCSV](https://ds.iris.edu/files/documents/GeoCSV.pdf) and exposes
    it as a [`Seismogram`][pysmo.Seismogram]-compatible object.

    This class is intended as a data-ingestion step. Once loaded, use
    [`clone_to_mini`][pysmo.functions.clone_to_mini] to convert the
    waveform to a [`MiniSeismogram`][pysmo.MiniSeismogram], which can then
    be passed to [`copy_from_mini`][pysmo.functions.copy_from_mini] to
    populate another object such as a [`SAC`][pysmo.classes.SAC] instance.
    Instances can be modified in memory but there is no write-back to
    GeoCSV.

    Examples:
        ```python
        >>> from pysmo import Seismogram
        >>> from pysmo.classes import GeoCsvSeismogram
        >>> text = '''\\
        ... # dataset: GeoCSV 2.0
        ... # delimiter: ,
        ... # field_unit: UTC, Counts
        ... # field_type: datetime, INTEGER
        ... # SID: IU_ANMO_00_LHZ
        ... # sample_count: 3
        ... # sample_rate_hz: 1.0
        ... # start_time: 2010-02-27T06:30:00Z
        ... Time, Sample
        ... 2010-02-27T06:30:00Z, -47297
        ... 2010-02-27T06:30:01Z, -47298
        ... 2010-02-27T06:30:02Z, -47299'''
        >>> seismogram = GeoCsvSeismogram.from_text(text)
        >>> isinstance(seismogram, Seismogram)
        True
        >>> seismogram.sid
        'IU_ANMO_00_LHZ'
        >>> seismogram.data
        array([-47297., -47298., -47299.])
        >>> seismogram.end_time
        Timestamp('2010-02-27 06:30:02+0000', tz='UTC')
        >>>
        ```
    """

    begin_time: UtcTimestamp = field(
        converter=convert_to_utc_timestamp,
        on_setattr=setters.convert,
    )
    """Seismogram begin time."""

    delta: PositiveTimedelta = field(
        converter=convert_to_timedelta,
        validator=[
            validators.instance_of(pd.Timedelta),
            validators.gt(pd.Timedelta(0)),
        ],
        on_setattr=setters.pipe(setters.convert, setters.validate),
    )
    """Seismogram sampling interval."""

    data: np.ndarray = field(
        converter=convert_to_ndarray,
        validator=validators.instance_of(np.ndarray),
        on_setattr=setters.pipe(setters.convert, setters.validate),
    )
    """Seismogram data."""

    sid: str = field(
        validator=validators.instance_of(str),
        on_setattr=setters.validate,
    )
    """FDSN source identifier of the parsed GeoCSV data (e.g. `IU_ANMO_00_LHZ`).

    This is parse-time metadata: it describes the GeoCSV data the instance
    was created from and is not updated when other attributes change.
    """

    @property
    def sample_count(self) -> int:
        """Number of samples, always equal to `len(data)`."""
        return len(self.data)

    @classmethod
    def from_text(cls, text: str) -> Self:
        """Create a new instance from a GeoCSV text body.

        The text may contain several timeseries datasets (the EarthScope
        dataselect service returns one dataset per contiguous segment);
        they are merged into a single continuous waveform.

        Args:
            text: GeoCSV text containing one or more timeseries datasets.

        Returns:
            A new GeoCsvSeismogram instance.

        Raises:
            ValueError: If the text contains no GeoCSV datasets, a dataset
                is not a valid timeseries, or the datasets cannot be merged
                into a continuous waveform (data gaps, differing channels
                or sample rates).
        """
        datasets = parse_geocsv(text)
        if not datasets:
            raise ValueError("No GeoCSV datasets found in text.")
        segment = merge_geocsv_timeseries(
            [extract_geocsv_timeseries(dataset) for dataset in datasets]
        )
        return cls(
            begin_time=segment.start_time,
            delta=pd.Timedelta(seconds=1.0 / segment.sample_rate_hz),
            data=segment.data,
            sid=segment.sid,
        )

    @classmethod
    def fetch(
        cls, *, station: Station, starttime: pd.Timestamp, endtime: pd.Timestamp
    ) -> Self:
        """Fetch and parse a seismogram from the EarthScope FDSN dataselect
        web service, for an absolute time window.

        For a window relative to a predicted phase arrival instead, compute
        the window yourself (e.g. with [`pysmo.tools.web.fetch_travel_times`][],
        which shows exactly this in its own Examples) and pass the
        resulting *starttime*/*endtime* here.

        Args:
            station: Any object satisfying the [`Station`][pysmo.Station]
                protocol. Provides the network, station code, location, and
                channel for the request.
            starttime: Start of the requested time window (UTC).
            endtime: End of the requested time window (UTC).

        Returns:
            A new GeoCsvSeismogram instance.

        Raises:
            ValueError: If no waveform data is returned for the given
                window, or the returned segments cannot be merged into a
                continuous trace (data gaps, differing channels or sample
                rates).
            urllib3.exceptions.ResponseError: If the dataselect web service
                returns an HTTP error.

        Examples:
            ```python
            >>> import pandas as pd
            >>> from pysmo import MiniStation
            >>> from pysmo.classes import GeoCsvSeismogram
            >>> station = MiniStation(
            ...     name="ANMO", network="IU", location="00", channel="LHZ",
            ...     latitude=34.945981, longitude=-106.457133,
            ... )
            >>> seismogram = GeoCsvSeismogram.fetch(
            ...     station=station,
            ...     starttime=pd.Timestamp("2010-02-27T06:44:00Z"),
            ...     endtime=pd.Timestamp("2010-02-27T06:54:00Z"),
            ... )  # doctest: +SKIP
            >>>
            ```
        """
        starttime = convert_to_utc_timestamp(starttime)
        endtime = convert_to_utc_timestamp(endtime)
        waveform_bytes = fetch_geocsvseismogram(
            station=station, starttime=starttime, endtime=endtime
        )
        if not waveform_bytes.strip():
            raise ValueError(
                f"No waveform data returned for "
                f"{station.network}.{station.name}.{station.location}."
                f"{station.channel} between {starttime} and {endtime}."
            )
        return cls.from_text(waveform_bytes.decode("utf-8"))

begin_time class-attribute instance-attribute

begin_time: UtcTimestamp = field(
    converter=convert_to_utc_timestamp,
    on_setattr=setters.convert,
)

Seismogram begin time.

data class-attribute instance-attribute

Seismogram data.

delta class-attribute instance-attribute

Seismogram sampling interval.

sample_count property

sample_count: int

Number of samples, always equal to len(data).

sid class-attribute instance-attribute

sid: str = field(
    validator=validators.instance_of(str),
    on_setattr=setters.validate,
)

FDSN source identifier of the parsed GeoCSV data (e.g. IU_ANMO_00_LHZ).

This is parse-time metadata: it describes the GeoCSV data the instance was created from and is not updated when other attributes change.

fetch classmethod

fetch(
    *,
    station: Station,
    starttime: Timestamp,
    endtime: Timestamp
) -> Self

Fetch and parse a seismogram from the EarthScope FDSN dataselect web service, for an absolute time window.

For a window relative to a predicted phase arrival instead, compute the window yourself (e.g. with pysmo.tools.web.fetch_travel_times, which shows exactly this in its own Examples) and pass the resulting starttime/endtime here.

Parameters:

Name Type Description Default
station Station

Any object satisfying the Station protocol. Provides the network, station code, location, and channel for the request.

required
starttime Timestamp

Start of the requested time window (UTC).

required
endtime Timestamp

End of the requested time window (UTC).

required

Returns:

Type Description
Self

A new GeoCsvSeismogram instance.

Raises:

Type Description
ValueError

If no waveform data is returned for the given window, or the returned segments cannot be merged into a continuous trace (data gaps, differing channels or sample rates).

ResponseError

If the dataselect web service returns an HTTP error.

Examples:

>>> import pandas as pd
>>> from pysmo import MiniStation
>>> from pysmo.classes import GeoCsvSeismogram
>>> station = MiniStation(
...     name="ANMO", network="IU", location="00", channel="LHZ",
...     latitude=34.945981, longitude=-106.457133,
... )
>>> seismogram = GeoCsvSeismogram.fetch(
...     station=station,
...     starttime=pd.Timestamp("2010-02-27T06:44:00Z"),
...     endtime=pd.Timestamp("2010-02-27T06:54:00Z"),
... )  # doctest: +SKIP
>>>
Source code in src/pysmo/classes/_geocsv.py
@classmethod
def fetch(
    cls, *, station: Station, starttime: pd.Timestamp, endtime: pd.Timestamp
) -> Self:
    """Fetch and parse a seismogram from the EarthScope FDSN dataselect
    web service, for an absolute time window.

    For a window relative to a predicted phase arrival instead, compute
    the window yourself (e.g. with [`pysmo.tools.web.fetch_travel_times`][],
    which shows exactly this in its own Examples) and pass the
    resulting *starttime*/*endtime* here.

    Args:
        station: Any object satisfying the [`Station`][pysmo.Station]
            protocol. Provides the network, station code, location, and
            channel for the request.
        starttime: Start of the requested time window (UTC).
        endtime: End of the requested time window (UTC).

    Returns:
        A new GeoCsvSeismogram instance.

    Raises:
        ValueError: If no waveform data is returned for the given
            window, or the returned segments cannot be merged into a
            continuous trace (data gaps, differing channels or sample
            rates).
        urllib3.exceptions.ResponseError: If the dataselect web service
            returns an HTTP error.

    Examples:
        ```python
        >>> import pandas as pd
        >>> from pysmo import MiniStation
        >>> from pysmo.classes import GeoCsvSeismogram
        >>> station = MiniStation(
        ...     name="ANMO", network="IU", location="00", channel="LHZ",
        ...     latitude=34.945981, longitude=-106.457133,
        ... )
        >>> seismogram = GeoCsvSeismogram.fetch(
        ...     station=station,
        ...     starttime=pd.Timestamp("2010-02-27T06:44:00Z"),
        ...     endtime=pd.Timestamp("2010-02-27T06:54:00Z"),
        ... )  # doctest: +SKIP
        >>>
        ```
    """
    starttime = convert_to_utc_timestamp(starttime)
    endtime = convert_to_utc_timestamp(endtime)
    waveform_bytes = fetch_geocsvseismogram(
        station=station, starttime=starttime, endtime=endtime
    )
    if not waveform_bytes.strip():
        raise ValueError(
            f"No waveform data returned for "
            f"{station.network}.{station.name}.{station.location}."
            f"{station.channel} between {starttime} and {endtime}."
        )
    return cls.from_text(waveform_bytes.decode("utf-8"))

from_text classmethod

from_text(text: str) -> Self

Create a new instance from a GeoCSV text body.

The text may contain several timeseries datasets (the EarthScope dataselect service returns one dataset per contiguous segment); they are merged into a single continuous waveform.

Parameters:

Name Type Description Default
text str

GeoCSV text containing one or more timeseries datasets.

required

Returns:

Type Description
Self

A new GeoCsvSeismogram instance.

Raises:

Type Description
ValueError

If the text contains no GeoCSV datasets, a dataset is not a valid timeseries, or the datasets cannot be merged into a continuous waveform (data gaps, differing channels or sample rates).

Source code in src/pysmo/classes/_geocsv.py
@classmethod
def from_text(cls, text: str) -> Self:
    """Create a new instance from a GeoCSV text body.

    The text may contain several timeseries datasets (the EarthScope
    dataselect service returns one dataset per contiguous segment);
    they are merged into a single continuous waveform.

    Args:
        text: GeoCSV text containing one or more timeseries datasets.

    Returns:
        A new GeoCsvSeismogram instance.

    Raises:
        ValueError: If the text contains no GeoCSV datasets, a dataset
            is not a valid timeseries, or the datasets cannot be merged
            into a continuous waveform (data gaps, differing channels
            or sample rates).
    """
    datasets = parse_geocsv(text)
    if not datasets:
        raise ValueError("No GeoCSV datasets found in text.")
    segment = merge_geocsv_timeseries(
        [extract_geocsv_timeseries(dataset) for dataset in datasets]
    )
    return cls(
        begin_time=segment.start_time,
        delta=pd.Timedelta(seconds=1.0 / segment.sample_rate_hz),
        data=segment.data,
        sid=segment.sid,
    )

SAC

Bases: SacIO

Access and modify data stored in SAC files.

The SAC class inherits all attributes and methods of the SacIO class, and extends it with attributes that allow using pysmo types. The extra attributes are themselves instances of "helper" classes that shouldn't be instantiated directly.

Examples:

SAC instances are typically created by reading a SAC file. Users familiar with the SAC file format can access header and data using the names they are used to:

>>> from pysmo.classes import SAC
>>> sac = SAC.from_file("example.sac")
>>> sac.delta
0.019999999552965164
>>> sac.data
array([2302., 2313., 2345., ..., 2836., 2772., 2723.], shape=(180000,))
>>> sac.evla
-31.465999603271484
>>>

Presenting the data in the above way is not compatible with pysmo types. For example, event coordinates are stored in the evla and evlo attributes, which do not match the pysmo Location type. Renaming or aliasing evla to latitude and evlo to longitude would solve the problem for the event coordinates, but since the SAC format also specifies station coordinates (stla, stlo) we still run into compatibility issues.

In order to map these incompatible attributes to ones that can be used with pysmo types, we use helper classes as a way to access the attributes under different names that are compatible with pysmo types:

>>> # Import the Seismogram type to check if the nested class is compatible:
>>> from pysmo import Seismogram
>>>
>>> # First verify that a SAC instance is not a pysmo Seismogram:
>>> isinstance(sac, Seismogram)
False
>>> # The sac.seismogram object is, however:
>>> isinstance(sac.seismogram, Seismogram)
True
>>>

Because the SAC file format defines a large amount of header fields for metadata, it needs to allow for many of these to be optional. Since the helper classes are more specific (and intended to be used with pysmo types), their attributes typically may not be None:

>>> # No error: a SAC file doesn't have to contain event information:
>>> sac.evla = None
>>>
Tip

The SAC class directly inherits from the SacIO class. This gives access to all SAC headers, ability to load from a file, download data, and so on. Using SAC is therefore almost always preferred over using SacIO.

Attributes:

Name Type Description
event SacEvent

Access data stored in the SAC object compatible with the Event type.

seismogram SacSeismogram

Access data stored in the SAC object compatible with the Seismogram type.

station SacStation

Access data stored in the SAC object compatible with the Station type.

timestamps SacTimestamps

Maps a SAC times such as B, E, O, T0-T9 to Timestamp objects.

Source code in src/pysmo/classes/_sac.py
@define(kw_only=True)
class SAC(SacIO):
    """Access and modify data stored in SAC files.

    The [`SAC`][pysmo.classes.SAC] class inherits all attributes and methods
    of the [`SacIO`][pysmo.lib.io.SacIO] class, and extends it with attributes
    that allow using pysmo types. The extra attributes are themselves instances
    of "helper" classes that shouldn't be instantiated directly.

    Examples:
        SAC instances are typically created by reading a SAC file. Users
        familiar with the SAC file format can access header and data using
        the names they are used to:

        ```python
        >>> from pysmo.classes import SAC
        >>> sac = SAC.from_file("example.sac")
        >>> sac.delta
        0.019999999552965164
        >>> sac.data
        array([2302., 2313., 2345., ..., 2836., 2772., 2723.], shape=(180000,))
        >>> sac.evla
        -31.465999603271484
        >>>
        ```

        Presenting the data in the above way is *not* compatible with pysmo
        types. For example, event coordinates are stored in the
        [`evla`][pysmo.lib.io.SacIO.evla] and [`evlo`][pysmo.lib.io.SacIO.evlo]
        attributes, which do not match the pysmo [`Location`][pysmo.Location]
        type. Renaming or aliasing `evla` to `latitude` and `evlo` to
        `longitude` would solve the problem for the event coordinates, but
        since the SAC format also specifies station coordinates
        ([`stla`][pysmo.lib.io.SacIO.stla], [`stlo`][pysmo.lib.io.SacIO.stlo])
        we still run into compatibility issues.

        In order to map these incompatible attributes to ones that can be
        used with pysmo types, we use helper classes as a way to access the
        attributes under different names that *are* compatible with pysmo
        types:

        ```python
        >>> # Import the Seismogram type to check if the nested class is compatible:
        >>> from pysmo import Seismogram
        >>>
        >>> # First verify that a SAC instance is not a pysmo Seismogram:
        >>> isinstance(sac, Seismogram)
        False
        >>> # The sac.seismogram object is, however:
        >>> isinstance(sac.seismogram, Seismogram)
        True
        >>>
        ```

        Because the SAC file format defines a large amount of header fields for
        metadata, it needs to allow for many of these to be optional. Since the
        helper classes are more specific (and intended to be used with pysmo
        types), their attributes typically may *not* be [`None`][]:

        ```python
        >>> # No error: a SAC file doesn't have to contain event information:
        >>> sac.evla = None
        >>>
        ```

    Tip:
        The [`SAC`][pysmo.classes.SAC] class directly inherits from the
        [`SacIO`][pysmo.lib.io.SacIO] class. This gives access to all
        SAC headers, ability to load from a file, download data, and so on.
        Using [`SAC`][pysmo.classes.SAC] is therefore almost always
        preferred over using [`SacIO`][pysmo.lib.io.SacIO].
    """

    seismogram: SacSeismogram = field(init=False)
    """Access data stored in the SAC object compatible with the [`Seismogram`][pysmo.Seismogram] type."""

    station: SacStation = field(init=False)
    """Access data stored in the SAC object compatible with the [`Station`][pysmo.Station] type."""

    event: SacEvent = field(init=False)
    """Access data stored in the SAC object compatible with the [`Event`][pysmo.Event] type."""

    timestamps: SacTimestamps = field(init=False)
    """Maps a SAC times such as B, E, O, T0-T9 to Timestamp objects."""

    def __attrs_post_init__(self) -> None:
        self.seismogram = SacSeismogram(parent=self)
        self.station = SacStation(parent=self)
        self.event = SacEvent(parent=self)
        self.timestamps = SacTimestamps(parent=self)

event class-attribute instance-attribute

event: SacEvent = field(init=False)

Access data stored in the SAC object compatible with the Event type.

seismogram class-attribute instance-attribute

seismogram: SacSeismogram = field(init=False)

Access data stored in the SAC object compatible with the Seismogram type.

station class-attribute instance-attribute

station: SacStation = field(init=False)

Access data stored in the SAC object compatible with the Station type.

timestamps class-attribute instance-attribute

timestamps: SacTimestamps = field(init=False)

Maps a SAC times such as B, E, O, T0-T9 to Timestamp objects.

SacEvent

Bases: _SacNested

Helper class for SAC event attributes.

The SacEvent class is used to map SAC attributes in a way that matches pysmo types. An instance of this class is created for each new (parent) SAC instance to enable pysmo types compatibility.

Examples:

Checking if a SacEvent matches the pysmo Event type:

>>> from pysmo.classes import SAC
>>> from pysmo import Event
>>> sac = SAC.from_file("example.sac")
>>> isinstance(sac.event, Event)
True
>>>
Note

Not all SAC files contain event information.

Attributes:

Name Type Description
depth int | float

Event depth in metres (positive downward from the surface).

latitude int | float

Event Latitude.

longitude int | float

Event Longitude.

time UtcTimestamp

Event origin time (UTC).

Source code in src/pysmo/classes/_sac.py
@define(kw_only=True)
class SacEvent(_SacNested):
    """Helper class for SAC event attributes.

    The `SacEvent` class is used to map SAC attributes in a way that
    matches pysmo types. An instance of this class is created for each
    new (parent) [`SAC`][pysmo.classes.SAC] instance to enable pysmo
    types compatibility.

    Examples:
        Checking if a SacEvent matches the pysmo
        [`Event`][pysmo.Event] type:

        ```python
        >>> from pysmo.classes import SAC
        >>> from pysmo import Event
        >>> sac = SAC.from_file("example.sac")
        >>> isinstance(sac.event, Event)
        True
        >>>
        ```

    Note:
        Not all SAC files contain event information.
    """

    @property
    def latitude(self) -> int | float:
        """Event Latitude."""

        if self._parent.evla is None:
            raise TypeError("SAC object event latitude 'evla' is None.")
        return self._parent.evla

    @latitude.setter
    def latitude(self, value: int | float) -> None:
        setattr(self._parent, "evla", value)

    @property
    def longitude(self) -> int | float:
        """Event Longitude."""

        if self._parent.evlo is None:
            raise TypeError("SAC object event longitude 'evlo' is None.")
        return self._parent.evlo

    @longitude.setter
    def longitude(self, value: int | float) -> None:
        setattr(self._parent, "evlo", value)

    @property
    def depth(self) -> int | float:
        """Event depth in metres (positive downward from the surface)."""

        if self._parent.evdp is None:
            raise TypeError("Sac object event depth 'evdp' is None.")
        return self._parent.evdp * 1000

    @depth.setter
    def depth(self, value: int | float) -> None:
        setattr(self._parent, "evdp", value / 1000)

    @property
    def time(self) -> UtcTimestamp:
        """Event origin time (UTC).

        Important:
            This property uses the [`SacIO.o`][pysmo.lib.io.SacIO.o] time
            header. If [`SacIO.iztype`][pysmo.lib.io.SacIO.iztype] is set to
            `"o"`, then this is also the "Reference time equivalance" and
            [`SacIO.o`][pysmo.lib.io.SacIO.o] cannot be changed (it is always
            0). Changing the [`time`][pysmo.classes.SacEvent.time] directly
            is not possible if this is the case.
        """

        event_time = self._get_timestamp_from_sac(SAC_OPTIONAL_TIME_HEADERS.o)
        if event_time is None:
            raise TypeError("SAC object event time 'o' is None.")
        return event_time

    @time.setter
    def time(self, value: pd.Timestamp) -> None:
        self._set_sac_from_timestamp(SAC_OPTIONAL_TIME_HEADERS.o, value)

depth property writable

depth: int | float

Event depth in metres (positive downward from the surface).

latitude property writable

latitude: int | float

Event Latitude.

longitude property writable

longitude: int | float

Event Longitude.

time property writable

Event origin time (UTC).

Important

This property uses the SacIO.o time header. If SacIO.iztype is set to "o", then this is also the "Reference time equivalance" and SacIO.o cannot be changed (it is always 0). Changing the time directly is not possible if this is the case.

SacPZ

Import class for SAC PZ (pole-zero) files.

Reads an analog instrument response from a SAC PZ file (as produced by e.g. the EarthScope SACPZ web service) and exposes it as a Response-compatible object. SacPZ only ever satisfies Response, never StagedResponse — the SAC PZ format has no digital-stage fields to parse.

Examples:

>>> from pysmo import Response
>>> from pysmo.classes import SacPZ
>>> text = '''\
... * NETWORK   (KNETWK): IU
... * STATION    (KSTNM): ANMO
... * LOCATION   (KHOLE): 00
... * CHANNEL   (KCMPNM): BHZ
... * START             : 2018-07-09T20:45:00
... * END               :
... * INPUT UNIT        : M
... ZEROS 2
... \t+0.000000e+00\t+0.000000e+00
... \t+0.000000e+00\t+0.000000e+00
... POLES 1
... \t-1.000000e-02\t+0.000000e+00
... CONSTANT 1.0e+09
... '''
>>> response = SacPZ.from_text(text)
>>> isinstance(response, Response)
True
>>> response.network, response.station
('IU', 'ANMO')
>>>

Methods:

Name Description
all_from_text

Create one instance per record in a bulk/concatenated SAC PZ text body.

fetch

Fetch and parse an instrument response from the EarthScope SACPZ

from_text

Create a new instance from a single-record SAC PZ text body.

Attributes:

Name Type Description
channel str

Channel code parsed from the SAC PZ file's comment header.

end_date Timestamp | None

End of the epoch this response applies to, or None if still open.

input_units str

Physical units produced by removing this response via full spectral

location str

Location code parsed from the SAC PZ file's comment header.

network str

Network code parsed from the SAC PZ file's comment header.

overall_sensitivity NonZeroNumber

Total system sensitivity (the SAC PZ file's CONSTANT).

poles list[complex]

Response poles.

reference_sensitivity NonZeroNumber | None

Total system sensitivity at the reference frequency, A0 excluded

start_date Timestamp

Start of the epoch this response applies to.

station str

Station code parsed from the SAC PZ file's comment header.

zeros list[complex]

Response zeros.

Source code in src/pysmo/classes/_sacpz.py
@define(kw_only=True, slots=True)
class SacPZ:
    """Import class for SAC PZ (pole-zero) files.

    Reads an analog instrument response from a
    [SAC PZ](https://ds.iris.edu/files/sac-manual/commands/transfer.html)
    file (as produced by e.g. the EarthScope SACPZ web service) and exposes
    it as a [`Response`][pysmo.Response]-compatible object. `SacPZ` only ever
    satisfies [`Response`][pysmo.Response], never
    [`StagedResponse`][pysmo.StagedResponse] — the SAC PZ format has no
    digital-stage fields to parse.

    Examples:
        ```python
        >>> from pysmo import Response
        >>> from pysmo.classes import SacPZ
        >>> text = '''\\
        ... * NETWORK   (KNETWK): IU
        ... * STATION    (KSTNM): ANMO
        ... * LOCATION   (KHOLE): 00
        ... * CHANNEL   (KCMPNM): BHZ
        ... * START             : 2018-07-09T20:45:00
        ... * END               :
        ... * INPUT UNIT        : M
        ... ZEROS 2
        ... \\t+0.000000e+00\\t+0.000000e+00
        ... \\t+0.000000e+00\\t+0.000000e+00
        ... POLES 1
        ... \\t-1.000000e-02\\t+0.000000e+00
        ... CONSTANT 1.0e+09
        ... '''
        >>> response = SacPZ.from_text(text)
        >>> isinstance(response, Response)
        True
        >>> response.network, response.station
        ('IU', 'ANMO')
        >>>
        ```
    """

    poles: list[complex] = field()
    """Response poles.

    See [`Response.poles`][pysmo.Response.poles] for more details.
    """

    zeros: list[complex] = field()
    """Response zeros.

    See [`Response.zeros`][pysmo.Response.zeros] for more details.
    """

    overall_sensitivity: NonZeroNumber = field(
        converter=float, validator=validate_nonzero
    )
    """Total system sensitivity (the SAC PZ file's `CONSTANT`).

    See [`Response.overall_sensitivity`][pysmo.Response.overall_sensitivity]
    for more details.
    """

    reference_sensitivity: NonZeroNumber | None = field(
        default=None,
        converter=_convert_optional_float,
        validator=validators.optional(validate_nonzero),
    )
    """Total system sensitivity at the reference frequency, `A0` excluded
    (the SAC PZ file's `SENSITIVITY` header, if present).

    See
    [`Response.reference_sensitivity`][pysmo.Response.reference_sensitivity]
    for more details.
    """

    input_units: str = field(validator=validators.instance_of(str))
    """Physical units produced by removing this response via full spectral
    deconvolution — not necessarily via the sensitivity-only path, see
    [`remove_response`][pysmo.tools.signal.remove_response] for why.

    See [`Response.input_units`][pysmo.Response.input_units] for more details.
    """

    network: str = field(validator=validators.instance_of(str))
    """Network code parsed from the SAC PZ file's comment header."""

    station: str = field(validator=validators.instance_of(str))
    """Station code parsed from the SAC PZ file's comment header."""

    location: str = field(validator=validators.instance_of(str))
    """Location code parsed from the SAC PZ file's comment header."""

    channel: str = field(validator=validators.instance_of(str))
    """Channel code parsed from the SAC PZ file's comment header."""

    start_date: pd.Timestamp = field()
    """Start of the epoch this response applies to."""

    end_date: pd.Timestamp | None = field(default=None)
    """End of the epoch this response applies to, or `None` if still open."""

    @classmethod
    def from_text(cls, text: str) -> Self:
        """Create a new instance from a single-record SAC PZ text body.

        Args:
            text: SAC PZ text containing exactly one record (the common
                sidecar-file case, e.g. one `.pz`/`SACPZ.NET.STA.LOC.CHA`
                file matched to one channel epoch by filename convention).

        Returns:
            A new SacPZ instance.

        Raises:
            ValueError: If the text contains zero or more than one SAC PZ
                record.

        Tip: See Also
            [`SacPZ.all_from_text`][pysmo.classes.SacPZ.all_from_text]: Parse
            a bulk/concatenated multi-record text body.

        Examples:
            Reading a SAC PZ file already saved to disk — the common case for
            archived/legacy data, e.g. extracted from an old SEED volume with
            `rdseed -p`, rather than fetched live from EarthScope:

            ```python
            >>> from pathlib import Path
            >>> from pysmo import Response
            >>> from pysmo.classes import SacPZ
            >>> text = Path("SACPZ.IU.ANMO.00.BHZ").read_text()
            >>> response = SacPZ.from_text(text)
            >>> isinstance(response, Response)
            True
            >>> response.network, response.station
            ('IU', 'ANMO')
            >>>
            ```
        """
        records = parse_sacpz(text)
        if len(records) != 1:
            raise ValueError(
                f"Expected exactly one SAC PZ record in text, found {len(records)}."
            )
        return cls._from_raw(records[0])

    @classmethod
    def fetch(cls, *, station: Station, time: pd.Timestamp | None = None) -> Self:
        """Fetch and parse an instrument response from the EarthScope SACPZ
        web service, selecting one epoch.

        Unlike [`StationXML.fetch`][pysmo.classes.StationXML.fetch], epoch
        selection happens server-side: the SACPZ web service's own `time`
        parameter is passed through, so exactly one record is returned
        (the epoch active at *time* if given, otherwise the one currently
        open) without needing to fetch the full response history first.

        Args:
            station: Any object satisfying the [`Station`][pysmo.Station]
                protocol. Provides the network, station code, location, and
                channel for the request.
            time: Timestamp used to select the response epoch. If `None`,
                the currently-open epoch is selected.

        Returns:
            A new SacPZ instance for the response epoch active at *time*
            (or currently open, if `time` is `None`).

        Raises:
            ValueError: If the web service's response does not contain
                exactly one SAC PZ record.
            urllib3.exceptions.ResponseError: If the web service returns an
                HTTP error.

        Tip:
            When fetching live from EarthScope rather than reading an
            existing SAC PZ file, prefer
            [`StationXML.fetch`][pysmo.classes.StationXML.fetch]: the
            StationXML response also captures digital FIR/IIR stages, so it
            always satisfies [`StagedResponse`][pysmo.StagedResponse], unlike
            `SacPZ`.

        Examples:
            ```python
            >>> from pysmo import MiniStation
            >>> from pysmo.classes import SacPZ
            >>> station = MiniStation(
            ...     name="ANMO", network="IU", location="00", channel="BHZ",
            ...     latitude=34.945981, longitude=-106.457133,
            ... )
            >>> response = SacPZ.fetch(station=station)  # doctest: +SKIP
            >>>
            ```
        """
        text = fetch_sacpz(station=station, time=time)
        return cls.from_text(text)

    @classmethod
    def all_from_text(cls, text: str) -> list[Self]:
        """Create one instance per record in a bulk/concatenated SAC PZ text body.

        Unlike [`from_text`][pysmo.classes.SacPZ.from_text], this does not
        require (or merge to) a single record — a SACPZ retrieval that is
        not pinned to a single channel epoch returns multiple concatenated
        records, each with its own `network`/`station`/`location`/`channel`/
        `start_date`/`end_date` provenance, which callers can filter
        themselves.

        Args:
            text: SAC PZ text containing one or more records.

        Returns:
            One SacPZ instance per record found, in order of appearance.
        """
        return [cls._from_raw(record) for record in parse_sacpz(text)]

    @classmethod
    def _from_raw(cls, record: _RawSacPzResponse) -> Self:
        return cls(
            poles=record.poles,
            zeros=record.zeros,
            overall_sensitivity=record.overall_sensitivity,
            reference_sensitivity=record.reference_sensitivity,
            input_units=record.input_units,
            network=record.network,
            station=record.station,
            location=record.location,
            channel=record.channel,
            start_date=record.start_date,
            end_date=record.end_date,
        )

channel class-attribute instance-attribute

channel: str = field(validator=validators.instance_of(str))

Channel code parsed from the SAC PZ file's comment header.

end_date class-attribute instance-attribute

end_date: Timestamp | None = field(default=None)

End of the epoch this response applies to, or None if still open.

input_units class-attribute instance-attribute

input_units: str = field(
    validator=validators.instance_of(str)
)

Physical units produced by removing this response via full spectral deconvolution — not necessarily via the sensitivity-only path, see remove_response for why.

See Response.input_units for more details.

location class-attribute instance-attribute

location: str = field(validator=validators.instance_of(str))

Location code parsed from the SAC PZ file's comment header.

network class-attribute instance-attribute

network: str = field(validator=validators.instance_of(str))

Network code parsed from the SAC PZ file's comment header.

overall_sensitivity class-attribute instance-attribute

overall_sensitivity: NonZeroNumber = field(
    converter=float, validator=validate_nonzero
)

Total system sensitivity (the SAC PZ file's CONSTANT).

See Response.overall_sensitivity for more details.

poles class-attribute instance-attribute

poles: list[complex] = field()

Response poles.

See Response.poles for more details.

reference_sensitivity class-attribute instance-attribute

reference_sensitivity: NonZeroNumber | None = field(
    default=None,
    converter=_convert_optional_float,
    validator=validators.optional(validate_nonzero),
)

Total system sensitivity at the reference frequency, A0 excluded (the SAC PZ file's SENSITIVITY header, if present).

See Response.reference_sensitivity for more details.

start_date class-attribute instance-attribute

start_date: Timestamp = field()

Start of the epoch this response applies to.

station class-attribute instance-attribute

station: str = field(validator=validators.instance_of(str))

Station code parsed from the SAC PZ file's comment header.

zeros class-attribute instance-attribute

zeros: list[complex] = field()

Response zeros.

See Response.zeros for more details.

all_from_text classmethod

all_from_text(text: str) -> list[Self]

Create one instance per record in a bulk/concatenated SAC PZ text body.

Unlike from_text, this does not require (or merge to) a single record — a SACPZ retrieval that is not pinned to a single channel epoch returns multiple concatenated records, each with its own network/station/location/channel/ start_date/end_date provenance, which callers can filter themselves.

Parameters:

Name Type Description Default
text str

SAC PZ text containing one or more records.

required

Returns:

Type Description
list[Self]

One SacPZ instance per record found, in order of appearance.

Source code in src/pysmo/classes/_sacpz.py
@classmethod
def all_from_text(cls, text: str) -> list[Self]:
    """Create one instance per record in a bulk/concatenated SAC PZ text body.

    Unlike [`from_text`][pysmo.classes.SacPZ.from_text], this does not
    require (or merge to) a single record — a SACPZ retrieval that is
    not pinned to a single channel epoch returns multiple concatenated
    records, each with its own `network`/`station`/`location`/`channel`/
    `start_date`/`end_date` provenance, which callers can filter
    themselves.

    Args:
        text: SAC PZ text containing one or more records.

    Returns:
        One SacPZ instance per record found, in order of appearance.
    """
    return [cls._from_raw(record) for record in parse_sacpz(text)]

fetch classmethod

fetch(
    *, station: Station, time: Timestamp | None = None
) -> Self

Fetch and parse an instrument response from the EarthScope SACPZ web service, selecting one epoch.

Unlike StationXML.fetch, epoch selection happens server-side: the SACPZ web service's own time parameter is passed through, so exactly one record is returned (the epoch active at time if given, otherwise the one currently open) without needing to fetch the full response history first.

Parameters:

Name Type Description Default
station Station

Any object satisfying the Station protocol. Provides the network, station code, location, and channel for the request.

required
time Timestamp | None

Timestamp used to select the response epoch. If None, the currently-open epoch is selected.

None

Returns:

Type Description
Self

A new SacPZ instance for the response epoch active at time

Self

(or currently open, if time is None).

Raises:

Type Description
ValueError

If the web service's response does not contain exactly one SAC PZ record.

ResponseError

If the web service returns an HTTP error.

Tip

When fetching live from EarthScope rather than reading an existing SAC PZ file, prefer StationXML.fetch: the StationXML response also captures digital FIR/IIR stages, so it always satisfies StagedResponse, unlike SacPZ.

Examples:

>>> from pysmo import MiniStation
>>> from pysmo.classes import SacPZ
>>> station = MiniStation(
...     name="ANMO", network="IU", location="00", channel="BHZ",
...     latitude=34.945981, longitude=-106.457133,
... )
>>> response = SacPZ.fetch(station=station)  # doctest: +SKIP
>>>
Source code in src/pysmo/classes/_sacpz.py
@classmethod
def fetch(cls, *, station: Station, time: pd.Timestamp | None = None) -> Self:
    """Fetch and parse an instrument response from the EarthScope SACPZ
    web service, selecting one epoch.

    Unlike [`StationXML.fetch`][pysmo.classes.StationXML.fetch], epoch
    selection happens server-side: the SACPZ web service's own `time`
    parameter is passed through, so exactly one record is returned
    (the epoch active at *time* if given, otherwise the one currently
    open) without needing to fetch the full response history first.

    Args:
        station: Any object satisfying the [`Station`][pysmo.Station]
            protocol. Provides the network, station code, location, and
            channel for the request.
        time: Timestamp used to select the response epoch. If `None`,
            the currently-open epoch is selected.

    Returns:
        A new SacPZ instance for the response epoch active at *time*
        (or currently open, if `time` is `None`).

    Raises:
        ValueError: If the web service's response does not contain
            exactly one SAC PZ record.
        urllib3.exceptions.ResponseError: If the web service returns an
            HTTP error.

    Tip:
        When fetching live from EarthScope rather than reading an
        existing SAC PZ file, prefer
        [`StationXML.fetch`][pysmo.classes.StationXML.fetch]: the
        StationXML response also captures digital FIR/IIR stages, so it
        always satisfies [`StagedResponse`][pysmo.StagedResponse], unlike
        `SacPZ`.

    Examples:
        ```python
        >>> from pysmo import MiniStation
        >>> from pysmo.classes import SacPZ
        >>> station = MiniStation(
        ...     name="ANMO", network="IU", location="00", channel="BHZ",
        ...     latitude=34.945981, longitude=-106.457133,
        ... )
        >>> response = SacPZ.fetch(station=station)  # doctest: +SKIP
        >>>
        ```
    """
    text = fetch_sacpz(station=station, time=time)
    return cls.from_text(text)

from_text classmethod

from_text(text: str) -> Self

Create a new instance from a single-record SAC PZ text body.

Parameters:

Name Type Description Default
text str

SAC PZ text containing exactly one record (the common sidecar-file case, e.g. one .pz/SACPZ.NET.STA.LOC.CHA file matched to one channel epoch by filename convention).

required

Returns:

Type Description
Self

A new SacPZ instance.

Raises:

Type Description
ValueError

If the text contains zero or more than one SAC PZ record.

See Also

SacPZ.all_from_text: Parse a bulk/concatenated multi-record text body.

Examples:

Reading a SAC PZ file already saved to disk — the common case for archived/legacy data, e.g. extracted from an old SEED volume with rdseed -p, rather than fetched live from EarthScope:

>>> from pathlib import Path
>>> from pysmo import Response
>>> from pysmo.classes import SacPZ
>>> text = Path("SACPZ.IU.ANMO.00.BHZ").read_text()
>>> response = SacPZ.from_text(text)
>>> isinstance(response, Response)
True
>>> response.network, response.station
('IU', 'ANMO')
>>>
Source code in src/pysmo/classes/_sacpz.py
@classmethod
def from_text(cls, text: str) -> Self:
    """Create a new instance from a single-record SAC PZ text body.

    Args:
        text: SAC PZ text containing exactly one record (the common
            sidecar-file case, e.g. one `.pz`/`SACPZ.NET.STA.LOC.CHA`
            file matched to one channel epoch by filename convention).

    Returns:
        A new SacPZ instance.

    Raises:
        ValueError: If the text contains zero or more than one SAC PZ
            record.

    Tip: See Also
        [`SacPZ.all_from_text`][pysmo.classes.SacPZ.all_from_text]: Parse
        a bulk/concatenated multi-record text body.

    Examples:
        Reading a SAC PZ file already saved to disk — the common case for
        archived/legacy data, e.g. extracted from an old SEED volume with
        `rdseed -p`, rather than fetched live from EarthScope:

        ```python
        >>> from pathlib import Path
        >>> from pysmo import Response
        >>> from pysmo.classes import SacPZ
        >>> text = Path("SACPZ.IU.ANMO.00.BHZ").read_text()
        >>> response = SacPZ.from_text(text)
        >>> isinstance(response, Response)
        True
        >>> response.network, response.station
        ('IU', 'ANMO')
        >>>
        ```
    """
    records = parse_sacpz(text)
    if len(records) != 1:
        raise ValueError(
            f"Expected exactly one SAC PZ record in text, found {len(records)}."
        )
    return cls._from_raw(records[0])

SacSeismogram

Bases: _SacNested, SeismogramEndtimeMixin

Helper class for SAC seismogram attributes.

The SacSeismogram class is used to map SAC attributes in a way that matches pysmo types. An instance of this class is created for each new (parent) SAC instance to enable pysmo types compatibility.

Examples:

Checking if a SacSeismogram matches the pysmo Seismogram type:

>>> from pysmo import Seismogram
>>> from pysmo.classes import SAC
>>> sac = SAC.from_file("example.sac")
>>> isinstance(sac.seismogram, Seismogram)
True
>>>

Timing operations in a SAC file use a reference time, and all times (begin time, event origin time, picks, etc.) are relative to this reference time. In pysmo only absolute times are used. The example below shows the begin_time is the absolute time (in UTC) of the first data point:

>>> sac.seismogram.begin_time
Timestamp('2005-03-01 07:23:02.159999848+0000', tz='UTC')
>>>

Attributes:

Name Type Description
begin_time UtcTimestamp

Seismogram begin time.

data ndarray

Seismogram data.

delta Timedelta

Sampling interval.

Source code in src/pysmo/classes/_sac.py
@define(kw_only=True)
class SacSeismogram(_SacNested, SeismogramEndtimeMixin):
    """Helper class for SAC seismogram attributes.

    The `SacSeismogram` class is used to map SAC attributes in a way that
    matches pysmo types. An instance of this class is created for each new
    (parent) [`SAC`][pysmo.classes.SAC] instance to enable pysmo types
    compatibility.

    Examples:
        Checking if a SacSeismogram matches the pysmo
        [`Seismogram`][pysmo.Seismogram] type:

        ```python
        >>> from pysmo import Seismogram
        >>> from pysmo.classes import SAC
        >>> sac = SAC.from_file("example.sac")
        >>> isinstance(sac.seismogram, Seismogram)
        True
        >>>
        ```

        Timing operations in a SAC file use a reference time, and all times
        (begin time, event origin time, picks, etc.) are relative to this
        reference time. In pysmo only absolute times are used. The example
        below shows the `begin_time` is the absolute time (in UTC) of the first
        data point:

        ```python
        >>> sac.seismogram.begin_time
        Timestamp('2005-03-01 07:23:02.159999848+0000', tz='UTC')
        >>>
        ```
    """

    if TYPE_CHECKING:
        data: np.ndarray = field(init=False)
        delta: PositiveTimedelta = field(init=False)
        begin_time: UtcTimestamp = field(init=False)

    else:

        @property
        def data(self) -> np.ndarray:
            """Seismogram data."""

            return self._parent.data

        @data.setter
        def data(self, value: np.ndarray) -> None:
            self._parent.data = value

        @property
        def delta(self) -> pd.Timedelta:
            """Sampling interval."""
            return pd.Timedelta(seconds=self._parent.delta)

        @delta.setter
        def delta(self, value: pd.Timedelta) -> None:
            self._parent.delta = value.total_seconds()

        @property
        def begin_time(self) -> UtcTimestamp:
            """Seismogram begin time."""

            return self._get_timestamp_from_sac(SAC_REQUIRED_TIME_HEADERS.b)

        @begin_time.setter
        def begin_time(self, value: pd.Timestamp) -> None:
            self._set_sac_from_timestamp(SAC_REQUIRED_TIME_HEADERS.b, value)

begin_time property writable

begin_time: UtcTimestamp

Seismogram begin time.

data property writable

data: ndarray

Seismogram data.

delta property writable

delta: Timedelta

Sampling interval.

SacStation

Bases: _SacNested

Helper class for SAC station attributes.

The SacStation class is used to map SAC attributes in a way that matches pysmo types. An instance of this class is created for each new (parent) SAC instance to enable pysmo types compatibility.

Examples:

Checking if a SacStation matches the pysmo Station type:

>>> from pysmo.classes import SAC
>>> from pysmo import Station
>>> sac = SAC.from_file("example.sac")
>>> isinstance(sac.station, Station)
True
>>>

Attributes:

Name Type Description
channel str

Channel code.

elevation int | float | None

Station elevation in metres.

latitude int | float

Station latitude.

location str

Location code.

longitude int | float

Station longitude.

name str

Station name or code.

network str

Network name or code.

Source code in src/pysmo/classes/_sac.py
@define(kw_only=True)
class SacStation(_SacNested):
    """Helper class for SAC station attributes.

    The `SacStation` class is used to map SAC attributes in a way that
    matches pysmo types. An instance of this class is created for each
    new (parent) [`SAC`][pysmo.classes.SAC] instance to enable pysmo
    types compatibility.

    Examples:
        Checking if a SacStation matches the pysmo
        [`Station`][pysmo.Station] type:

        ```python
        >>> from pysmo.classes import SAC
        >>> from pysmo import Station
        >>> sac = SAC.from_file("example.sac")
        >>> isinstance(sac.station, Station)
        True
        >>>
        ```
    """

    @property
    def name(self) -> str:
        """Station name or code."""

        if self._parent.kstnm is None:
            raise TypeError("SAC object station name 'kstnm' is None.")
        return self._parent.kstnm

    @name.setter
    def name(self, value: str) -> None:
        setattr(self._parent, "kstnm", value)

    @property
    def network(self) -> str:
        """Network name or code."""

        if self._parent.knetwk is None:
            raise TypeError("SAC object network name 'knetwk' is None.")

        return self._parent.knetwk

    @network.setter
    def network(self, value: str) -> None:
        setattr(self._parent, "knetwk", value)

    @property
    def location(self) -> str:
        """Location code."""

        if self._parent.khole is None:
            raise TypeError("SAC object location code 'khole' is None.")
        return self._parent.khole

    @location.setter
    def location(self, value: str) -> None:
        setattr(self._parent, "khole", value)

    @property
    def channel(self) -> str:
        """Channel code."""

        if self._parent.kcmpnm is None:
            raise TypeError("SAC object channel code 'kcmpnm' is None.")
        return self._parent.kcmpnm

    @channel.setter
    def channel(self, value: str) -> None:
        setattr(self._parent, "kcmpnm", value)

    @property
    def latitude(self) -> int | float:
        """Station latitude."""

        if self._parent.stla is None:
            raise TypeError("SAC object station latitude 'stla' is None.")
        return self._parent.stla

    @latitude.setter
    def latitude(self, value: int | float) -> None:
        setattr(self._parent, "stla", value)

    @property
    def longitude(self) -> int | float:
        """Station longitude."""

        if self._parent.stlo is None:
            raise TypeError("SAC object station longitude 'stlo' is None.")
        return self._parent.stlo

    @longitude.setter
    def longitude(self, value: int | float) -> None:
        setattr(self._parent, "stlo", value)

    @property
    def elevation(self) -> int | float | None:
        """Station elevation in metres."""

        return self._parent.stel

    @elevation.setter
    def elevation(self, value: int | float | None) -> None:
        setattr(self._parent, "stel", value)

channel property writable

channel: str

Channel code.

elevation property writable

elevation: int | float | None

Station elevation in metres.

latitude property writable

latitude: int | float

Station latitude.

location property writable

location: str

Location code.

longitude property writable

longitude: int | float

Station longitude.

name property writable

name: str

Station name or code.

network property writable

network: str

Network name or code.

SacTimestamps

Bases: _SacNested

Helper class to access times stored in SAC headers as Timestamp objects.

The SacTimestamps class is used to map SAC attributes in a way that matches pysmo types. An instance of this class is created for each new (parent) SAC instance to enable pysmo types compatibility.

Examples:

Relative seismogram begin time as a float vs absolute begin time as a Timestamp object.

>>> from pysmo.classes import SAC
>>> sac = SAC.from_file("example.sac")
>>>
>>> # SAC header "B" as stored in a SAC file
>>> sac.b
-63.34000015258789
>>>
>>> # the output above is the number of seconds relative
>>> # to the reference time and date:
>>> sac.kzdate , sac.kztime
('2005-03-01', '07:24:05.500')
>>>
>>> # Accessing the same SAC header via a `SacTimestamps` object
>>> # yields a corresponding Timestamp object with the absolute time:
>>> sac.timestamps.b
Timestamp('2005-03-01 07:23:02.159999848+0000', tz='UTC')
>>>

Changing timestamp values:

>>> import pandas as pd
>>> sac = SAC.from_file("example.sac")
>>>
>>> # Original value of the "B" SAC header:
>>> sac.b
-63.34000015258789
>>>
>>> # Add 30 seconds to the absolute time:
>>> sac.timestamps.b += pd.Timedelta(seconds=30)
>>>
>>> # The relative time also changes by the same amount:
>>> sac.b
-33.34
>>>
>>> # Changing b to None is not allowed (it is a required time header):
>>> sac.timestamps.b = None
Traceback (most recent call last):
...
TypeError: ...
>>>

Attributes:

Name Type Description
a OptionalSacTimestamp

First arrival time.

b RequiredSacTimestamp

Beginning time of the independent variable.

e RequiredSacTimestamp

Ending time of the independent variable (read-only).

f OptionalSacTimestamp

Fini or end of event time.

o OptionalSacTimestamp

Event origin time.

t0 OptionalSacTimestamp

User defined time pick or marker 0.

t1 OptionalSacTimestamp

User defined time pick or marker 1.

t2 OptionalSacTimestamp

User defined time pick or marker 2.

t3 OptionalSacTimestamp

User defined time pick or marker 3.

t4 OptionalSacTimestamp

User defined time pick or marker 4.

t5 OptionalSacTimestamp

User defined time pick or marker 5.

t6 OptionalSacTimestamp

User defined time pick or marker 6.

t7 OptionalSacTimestamp

User defined time pick or marker 7.

t8 OptionalSacTimestamp

User defined time pick or marker 8.

t9 OptionalSacTimestamp

User defined time pick or marker 9.

Source code in src/pysmo/classes/_sac.py
class SacTimestamps(_SacNested):
    """Helper class to access times stored in SAC headers as [`Timestamp`][pandas.Timestamp] objects.

    The `SacTimestamps` class is used to map SAC attributes in a way that
    matches pysmo types. An instance of this class is created for each
    new (parent) [`SAC`][pysmo.classes.SAC] instance to enable pysmo
    types compatibility.


    Examples:
        Relative seismogram begin time as a float vs absolute begin time
        as a [`Timestamp`][pandas.Timestamp] object.

        ```python
        >>> from pysmo.classes import SAC
        >>> sac = SAC.from_file("example.sac")
        >>>
        >>> # SAC header "B" as stored in a SAC file
        >>> sac.b
        -63.34000015258789
        >>>
        >>> # the output above is the number of seconds relative
        >>> # to the reference time and date:
        >>> sac.kzdate , sac.kztime
        ('2005-03-01', '07:24:05.500')
        >>>
        >>> # Accessing the same SAC header via a `SacTimestamps` object
        >>> # yields a corresponding Timestamp object with the absolute time:
        >>> sac.timestamps.b
        Timestamp('2005-03-01 07:23:02.159999848+0000', tz='UTC')
        >>>
        ```

        Changing timestamp values:

        ```python
        >>> import pandas as pd
        >>> sac = SAC.from_file("example.sac")
        >>>
        >>> # Original value of the "B" SAC header:
        >>> sac.b
        -63.34000015258789
        >>>
        >>> # Add 30 seconds to the absolute time:
        >>> sac.timestamps.b += pd.Timedelta(seconds=30)
        >>>
        >>> # The relative time also changes by the same amount:
        >>> sac.b
        -33.34
        >>>
        >>> # Changing b to None is not allowed (it is a required time header):
        >>> sac.timestamps.b = None
        Traceback (most recent call last):
        ...
        TypeError: ...
        >>>
        ```
    """

    b: RequiredSacTimestamp = RequiredSacTimestamp()
    """Beginning time of the independent variable."""

    e: RequiredSacTimestamp = RequiredSacTimestamp(readonly=True)
    """Ending time of the independent variable (read-only)."""

    o: OptionalSacTimestamp = OptionalSacTimestamp()
    """Event origin time."""

    a: OptionalSacTimestamp = OptionalSacTimestamp()
    """First arrival time."""

    f: OptionalSacTimestamp = OptionalSacTimestamp()
    """Fini or end of event time."""

    t0: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 0."""

    t1: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 1."""

    t2: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 2."""

    t3: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 3."""

    t4: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 4."""

    t5: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 5."""

    t6: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 6."""

    t7: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 7."""

    t8: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 8."""

    t9: OptionalSacTimestamp = OptionalSacTimestamp()
    """User defined time pick or marker 9."""

a class-attribute instance-attribute

a: OptionalSacTimestamp = OptionalSacTimestamp()

First arrival time.

b class-attribute instance-attribute

b: RequiredSacTimestamp = RequiredSacTimestamp()

Beginning time of the independent variable.

e class-attribute instance-attribute

e: RequiredSacTimestamp = RequiredSacTimestamp(
    readonly=True
)

Ending time of the independent variable (read-only).

f class-attribute instance-attribute

f: OptionalSacTimestamp = OptionalSacTimestamp()

Fini or end of event time.

o class-attribute instance-attribute

o: OptionalSacTimestamp = OptionalSacTimestamp()

Event origin time.

t0 class-attribute instance-attribute

t0: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 0.

t1 class-attribute instance-attribute

t1: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 1.

t2 class-attribute instance-attribute

t2: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 2.

t3 class-attribute instance-attribute

t3: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 3.

t4 class-attribute instance-attribute

t4: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 4.

t5 class-attribute instance-attribute

t5: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 5.

t6 class-attribute instance-attribute

t6: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 6.

t7 class-attribute instance-attribute

t7: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 7.

t8 class-attribute instance-attribute

t8: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 8.

t9 class-attribute instance-attribute

t9: OptionalSacTimestamp = OptionalSacTimestamp()

User defined time pick or marker 9.

StationXML

Import class for FDSN StationXML response metadata.

Reads an instrument response from a FDSN StationXML document (as returned by e.g. the EarthScope station web service with level=response) and exposes it as a Response-compatible object. Unlike SacPZ, StationXML always satisfies StagedResponse too — stages is simply empty if the document has no digital FIR/IIR decimation stages.

A StationXML document commonly covers a channel's full instrument history, i.e. several response epochs (e.g. after a sensor swap). from_bytes narrows this to a single epoch (matching a given time, or the currently-open one); all_from_bytes returns every epoch found, for callers who want to do their own selection.

Examples:

>>> from pysmo import Response, StagedResponse
>>> from pysmo.classes import StationXML
>>> xml = b'''\
... <?xml version="1.0"?>
... <FDSNStationXML xmlns="http://www.fdsn.org/xml/station/1">
...   <Network code="IU">
...     <Station code="ANMO">
...       <Channel code="BHZ" locationCode="00"
...                startDate="2018-07-09T20:45:00.0000">
...         <Response>
...           <InstrumentSensitivity>
...             <Value>1.98475E9</Value>
...             <Frequency>0.02</Frequency>
...             <InputUnits><Name>m/s</Name></InputUnits>
...             <OutputUnits><Name>counts</Name></OutputUnits>
...           </InstrumentSensitivity>
...           <Stage number="1">
...             <PolesZeros>
...               <InputUnits><Name>m/s</Name></InputUnits>
...               <OutputUnits><Name>V</Name></OutputUnits>
...               <PzTransferFunctionType>LAPLACE (RADIANS/SECOND)</PzTransferFunctionType>
...               <NormalizationFactor>5.03773E14</NormalizationFactor>
...               <NormalizationFrequency>0.02</NormalizationFrequency>
...               <Zero number="0"><Real>0.0</Real><Imaginary>0.0</Imaginary></Zero>
...               <Pole number="0"><Real>-0.037</Real><Imaginary>0.037</Imaginary></Pole>
...             </PolesZeros>
...             <Decimation>
...               <InputSampleRate>40.0</InputSampleRate>
...               <Factor>1</Factor>
...             </Decimation>
...             <StageGain><Value>1183.0</Value><Frequency>0.02</Frequency></StageGain>
...           </Stage>
...         </Response>
...       </Channel>
...     </Station>
...   </Network>
... </FDSNStationXML>'''
>>> response = StationXML.from_bytes(xml)
>>> isinstance(response, Response)
True
>>> isinstance(response, StagedResponse)
True
>>> response.network, response.station
('IU', 'ANMO')
>>>

Methods:

Name Description
all_from_bytes

Create one instance per response epoch in a StationXML document.

fetch

Fetch and parse an instrument response from the EarthScope FDSN

from_bytes

Create a new instance from a StationXML document, selecting one epoch.

Attributes:

Name Type Description
channel str

Channel code parsed from the StationXML document.

end_date Timestamp | None

End of the epoch this response applies to, or None if still open.

input_units str

Physical units produced by removing this response.

location str

Location code parsed from the StationXML document.

network str

Network code parsed from the StationXML document.

overall_sensitivity NonZeroNumber

Scale factor combined with poles/zeros to reconstruct H(f)

poles list[complex]

Response poles.

reference_sensitivity NonZeroNumber | None

Total system sensitivity at the reference frequency, A0 excluded

stages list[ResponseStage]

Digital decimation stages, in signal order. Empty if the document has

start_date Timestamp

Start of the epoch this response applies to.

station str

Station code parsed from the StationXML document.

zeros list[complex]

Response zeros.

Source code in src/pysmo/classes/_stationxml.py
@define(kw_only=True, slots=True)
class StationXML:
    """Import class for FDSN StationXML response metadata.

    Reads an instrument response from a
    [FDSN StationXML](http://www.fdsn.org/xml/station/) document (as
    returned by e.g. the EarthScope station web service with
    `level=response`) and exposes it as a
    [`Response`][pysmo.Response]-compatible object. Unlike
    [`SacPZ`][pysmo.classes.SacPZ], `StationXML` always satisfies
    [`StagedResponse`][pysmo.StagedResponse] too — `stages` is simply empty
    if the document has no digital FIR/IIR decimation stages.

    A StationXML document commonly covers a channel's full instrument
    history, i.e. several response epochs (e.g. after a sensor swap).
    [`from_bytes`][pysmo.classes.StationXML.from_bytes] narrows this to a
    single epoch (matching a given time, or the currently-open one);
    [`all_from_bytes`][pysmo.classes.StationXML.all_from_bytes] returns
    every epoch found, for callers who want to do their own selection.

    Examples:
        ```python
        >>> from pysmo import Response, StagedResponse
        >>> from pysmo.classes import StationXML
        >>> xml = b'''\\
        ... <?xml version="1.0"?>
        ... <FDSNStationXML xmlns="http://www.fdsn.org/xml/station/1">
        ...   <Network code="IU">
        ...     <Station code="ANMO">
        ...       <Channel code="BHZ" locationCode="00"
        ...                startDate="2018-07-09T20:45:00.0000">
        ...         <Response>
        ...           <InstrumentSensitivity>
        ...             <Value>1.98475E9</Value>
        ...             <Frequency>0.02</Frequency>
        ...             <InputUnits><Name>m/s</Name></InputUnits>
        ...             <OutputUnits><Name>counts</Name></OutputUnits>
        ...           </InstrumentSensitivity>
        ...           <Stage number="1">
        ...             <PolesZeros>
        ...               <InputUnits><Name>m/s</Name></InputUnits>
        ...               <OutputUnits><Name>V</Name></OutputUnits>
        ...               <PzTransferFunctionType>LAPLACE (RADIANS/SECOND)</PzTransferFunctionType>
        ...               <NormalizationFactor>5.03773E14</NormalizationFactor>
        ...               <NormalizationFrequency>0.02</NormalizationFrequency>
        ...               <Zero number="0"><Real>0.0</Real><Imaginary>0.0</Imaginary></Zero>
        ...               <Pole number="0"><Real>-0.037</Real><Imaginary>0.037</Imaginary></Pole>
        ...             </PolesZeros>
        ...             <Decimation>
        ...               <InputSampleRate>40.0</InputSampleRate>
        ...               <Factor>1</Factor>
        ...             </Decimation>
        ...             <StageGain><Value>1183.0</Value><Frequency>0.02</Frequency></StageGain>
        ...           </Stage>
        ...         </Response>
        ...       </Channel>
        ...     </Station>
        ...   </Network>
        ... </FDSNStationXML>'''
        >>> response = StationXML.from_bytes(xml)
        >>> isinstance(response, Response)
        True
        >>> isinstance(response, StagedResponse)
        True
        >>> response.network, response.station
        ('IU', 'ANMO')
        >>>
        ```
    """

    poles: list[complex] = field()
    """Response poles.

    See [`Response.poles`][pysmo.Response.poles] for more details.
    """

    zeros: list[complex] = field()
    """Response zeros.

    See [`Response.zeros`][pysmo.Response.zeros] for more details.
    """

    overall_sensitivity: NonZeroNumber = field(
        converter=float, validator=validate_nonzero
    )
    """Scale factor combined with `poles`/`zeros` to reconstruct `H(f)`
    (`NormalizationFactor * InstrumentSensitivity`).

    See [`Response.overall_sensitivity`][pysmo.Response.overall_sensitivity]
    for more details.
    """

    reference_sensitivity: NonZeroNumber | None = field(
        default=None,
        converter=_convert_optional_float,
        validator=validators.optional(validate_nonzero),
    )
    """Total system sensitivity at the reference frequency, `A0` excluded
    (StationXML's `InstrumentSensitivity/Value`).

    See
    [`Response.reference_sensitivity`][pysmo.Response.reference_sensitivity]
    for more details.
    """

    input_units: str = field(validator=validators.instance_of(str))
    """Physical units produced by removing this response.

    See [`Response.input_units`][pysmo.Response.input_units] for more details.
    """

    stages: list[ResponseStage] = field(factory=list)
    """Digital decimation stages, in signal order. Empty if the document has
    no digital stages.

    See [`StagedResponse.stages`][pysmo.StagedResponse.stages] for more details.
    """

    network: str = field(validator=validators.instance_of(str))
    """Network code parsed from the StationXML document."""

    station: str = field(validator=validators.instance_of(str))
    """Station code parsed from the StationXML document."""

    location: str = field(validator=validators.instance_of(str))
    """Location code parsed from the StationXML document."""

    channel: str = field(validator=validators.instance_of(str))
    """Channel code parsed from the StationXML document."""

    start_date: pd.Timestamp = field()
    """Start of the epoch this response applies to."""

    end_date: pd.Timestamp | None = field(default=None)
    """End of the epoch this response applies to, or `None` if still open."""

    @classmethod
    def from_bytes(
        cls,
        xml: bytes,
        *,
        time: pd.Timestamp | None = None,
        location: str | None = None,
        channel: str | None = None,
    ) -> Self:
        """Create a new instance from a StationXML document, selecting one epoch.

        A document is not guaranteed to cover a single channel — a
        station-level query (or one saved for later, offline use) commonly
        returns every location/channel combination on record, each with its
        own epoch history. `location`/`channel` narrow to one before `time`
        is applied; without them, a multi-channel document raises the same
        "more than one epoch" error as an ambiguous *time*.

        Args:
            xml: Raw StationXML document bytes (as returned by the FDSN
                station web service with `level=response`).
            time: Timestamp used to select the response epoch. If `None`,
                the currently-open epoch (no end date) is selected.
            location: Location code to narrow to, if `xml` covers more than
                one location. If `None`, location is not filtered.
            channel: Channel code to narrow to, if `xml` covers more than
                one channel. If `None`, channel is not filtered.

        Returns:
            A new StationXML instance for the response epoch active at
            *time* (or currently open, if `time` is `None`).

        Raises:
            ValueError: If, after narrowing by `location`/`channel`, zero or
                more than one response epoch matches *time* (or "currently
                open", if `time` is `None`).

        Tip: See Also
            [`StationXML.all_from_bytes`][pysmo.classes.StationXML.all_from_bytes]:
            Parse every epoch in the document without narrowing to one.
        """
        epochs = parse_stationxml(xml)
        matches = _matching_epochs(epochs, time, location=location, channel=channel)
        if len(matches) != 1:
            raise ValueError(
                f"Expected exactly one response epoch in the given "
                f"StationXML at "
                f"{'the currently open epoch' if time is None else time}"
                f"{f', location {location!r}' if location is not None else ''}"
                f"{f', channel {channel!r}' if channel is not None else ''}, "
                f"found {len(matches)}."
            )
        return cls._from_raw(matches[0])

    @classmethod
    def fetch(cls, *, station: Station, time: pd.Timestamp | None = None) -> Self:
        """Fetch and parse an instrument response from the EarthScope FDSN
        station web service, selecting one epoch.

        A channel's instrument response usually has several epochs (e.g.
        after a sensor swap), so the request is narrowed to a single one:
        the epoch active at *time* if given, otherwise the one currently
        open (no `endDate`). Fetches the full response history in one
        request and narrows client-side (like
        [`from_bytes`][pysmo.classes.StationXML.from_bytes]); to fetch once
        and interpret later (e.g. offline, or without repeating the network
        request), use [`pysmo.tools.web.fetch_stationxml`][] and
        [`from_bytes`][pysmo.classes.StationXML.from_bytes] /
        [`all_from_bytes`][pysmo.classes.StationXML.all_from_bytes] directly
        instead.

        Args:
            station: Any object satisfying the [`Station`][pysmo.Station]
                protocol. Provides the network, station code, location, and
                channel for the request.
            time: Timestamp used to select the response epoch. If `None`,
                the currently-open epoch (no end date) is selected.

        Returns:
            A new StationXML instance for the response epoch active at
            *time* (or currently open, if `time` is `None`).

        Raises:
            ValueError: If zero or more than one response epoch matches
                *time* (or "currently open", if `time` is `None`).
            urllib3.exceptions.ResponseError: If the station web service
                returns an HTTP error.

        Examples:
            ```python
            >>> from pysmo import MiniStation
            >>> from pysmo.classes import StationXML
            >>> station = MiniStation(
            ...     name="ANMO", network="IU", location="00", channel="BHZ",
            ...     latitude=34.945981, longitude=-106.457133,
            ... )
            >>> response = StationXML.fetch(station=station)  # doctest: +SKIP
            >>>
            ```
        """
        xml = fetch_stationxml(station=station)
        return cls.from_bytes(xml, time=time)

    @classmethod
    def all_from_bytes(cls, xml: bytes) -> list[Self]:
        """Create one instance per response epoch in a StationXML document.

        Unlike [`from_bytes`][pysmo.classes.StationXML.from_bytes], this
        does not narrow to a single epoch — a document covering a channel's
        full instrument history returns several, each with its own
        `network`/`station`/`location`/`channel`/`start_date`/`end_date`
        provenance, which callers can filter themselves.

        Args:
            xml: Raw StationXML document bytes.

        Returns:
            One StationXML instance per response epoch found, in document
            order.
        """
        return [cls._from_raw(raw) for raw in parse_stationxml(xml)]

    @classmethod
    def _from_raw(cls, raw: _RawResponse) -> Self:
        return cls(
            poles=raw.poles,
            zeros=raw.zeros,
            overall_sensitivity=raw.normalization_factor * raw.sensitivity_value,
            reference_sensitivity=raw.sensitivity_value,
            input_units=raw.sensitivity_input_units,
            stages=[
                MiniResponseStage(
                    input_sample_rate=stage.input_sample_rate,
                    decimation_factor=stage.decimation_factor,
                    numerator=stage.numerator,
                    denominator=stage.denominator,
                    correction=stage.correction,
                )
                for stage in raw.digital_stages
            ],
            network=raw.network,
            station=raw.station,
            location=raw.location,
            channel=raw.channel,
            start_date=raw.start_date,
            end_date=raw.end_date,
        )

channel class-attribute instance-attribute

channel: str = field(validator=validators.instance_of(str))

Channel code parsed from the StationXML document.

end_date class-attribute instance-attribute

end_date: Timestamp | None = field(default=None)

End of the epoch this response applies to, or None if still open.

input_units class-attribute instance-attribute

input_units: str = field(
    validator=validators.instance_of(str)
)

Physical units produced by removing this response.

See Response.input_units for more details.

location class-attribute instance-attribute

location: str = field(validator=validators.instance_of(str))

Location code parsed from the StationXML document.

network class-attribute instance-attribute

network: str = field(validator=validators.instance_of(str))

Network code parsed from the StationXML document.

overall_sensitivity class-attribute instance-attribute

overall_sensitivity: NonZeroNumber = field(
    converter=float, validator=validate_nonzero
)

Scale factor combined with poles/zeros to reconstruct H(f) (NormalizationFactor * InstrumentSensitivity).

See Response.overall_sensitivity for more details.

poles class-attribute instance-attribute

poles: list[complex] = field()

Response poles.

See Response.poles for more details.

reference_sensitivity class-attribute instance-attribute

reference_sensitivity: NonZeroNumber | None = field(
    default=None,
    converter=_convert_optional_float,
    validator=validators.optional(validate_nonzero),
)

Total system sensitivity at the reference frequency, A0 excluded (StationXML's InstrumentSensitivity/Value).

See Response.reference_sensitivity for more details.

stages class-attribute instance-attribute

stages: list[ResponseStage] = field(factory=list)

Digital decimation stages, in signal order. Empty if the document has no digital stages.

See StagedResponse.stages for more details.

start_date class-attribute instance-attribute

start_date: Timestamp = field()

Start of the epoch this response applies to.

station class-attribute instance-attribute

station: str = field(validator=validators.instance_of(str))

Station code parsed from the StationXML document.

zeros class-attribute instance-attribute

zeros: list[complex] = field()

Response zeros.

See Response.zeros for more details.

all_from_bytes classmethod

all_from_bytes(xml: bytes) -> list[Self]

Create one instance per response epoch in a StationXML document.

Unlike from_bytes, this does not narrow to a single epoch — a document covering a channel's full instrument history returns several, each with its own network/station/location/channel/start_date/end_date provenance, which callers can filter themselves.

Parameters:

Name Type Description Default
xml bytes

Raw StationXML document bytes.

required

Returns:

Type Description
list[Self]

One StationXML instance per response epoch found, in document

list[Self]

order.

Source code in src/pysmo/classes/_stationxml.py
@classmethod
def all_from_bytes(cls, xml: bytes) -> list[Self]:
    """Create one instance per response epoch in a StationXML document.

    Unlike [`from_bytes`][pysmo.classes.StationXML.from_bytes], this
    does not narrow to a single epoch — a document covering a channel's
    full instrument history returns several, each with its own
    `network`/`station`/`location`/`channel`/`start_date`/`end_date`
    provenance, which callers can filter themselves.

    Args:
        xml: Raw StationXML document bytes.

    Returns:
        One StationXML instance per response epoch found, in document
        order.
    """
    return [cls._from_raw(raw) for raw in parse_stationxml(xml)]

fetch classmethod

fetch(
    *, station: Station, time: Timestamp | None = None
) -> Self

Fetch and parse an instrument response from the EarthScope FDSN station web service, selecting one epoch.

A channel's instrument response usually has several epochs (e.g. after a sensor swap), so the request is narrowed to a single one: the epoch active at time if given, otherwise the one currently open (no endDate). Fetches the full response history in one request and narrows client-side (like from_bytes); to fetch once and interpret later (e.g. offline, or without repeating the network request), use pysmo.tools.web.fetch_stationxml and from_bytes / all_from_bytes directly instead.

Parameters:

Name Type Description Default
station Station

Any object satisfying the Station protocol. Provides the network, station code, location, and channel for the request.

required
time Timestamp | None

Timestamp used to select the response epoch. If None, the currently-open epoch (no end date) is selected.

None

Returns:

Type Description
Self

A new StationXML instance for the response epoch active at

Self

time (or currently open, if time is None).

Raises:

Type Description
ValueError

If zero or more than one response epoch matches time (or "currently open", if time is None).

ResponseError

If the station web service returns an HTTP error.

Examples:

>>> from pysmo import MiniStation
>>> from pysmo.classes import StationXML
>>> station = MiniStation(
...     name="ANMO", network="IU", location="00", channel="BHZ",
...     latitude=34.945981, longitude=-106.457133,
... )
>>> response = StationXML.fetch(station=station)  # doctest: +SKIP
>>>
Source code in src/pysmo/classes/_stationxml.py
@classmethod
def fetch(cls, *, station: Station, time: pd.Timestamp | None = None) -> Self:
    """Fetch and parse an instrument response from the EarthScope FDSN
    station web service, selecting one epoch.

    A channel's instrument response usually has several epochs (e.g.
    after a sensor swap), so the request is narrowed to a single one:
    the epoch active at *time* if given, otherwise the one currently
    open (no `endDate`). Fetches the full response history in one
    request and narrows client-side (like
    [`from_bytes`][pysmo.classes.StationXML.from_bytes]); to fetch once
    and interpret later (e.g. offline, or without repeating the network
    request), use [`pysmo.tools.web.fetch_stationxml`][] and
    [`from_bytes`][pysmo.classes.StationXML.from_bytes] /
    [`all_from_bytes`][pysmo.classes.StationXML.all_from_bytes] directly
    instead.

    Args:
        station: Any object satisfying the [`Station`][pysmo.Station]
            protocol. Provides the network, station code, location, and
            channel for the request.
        time: Timestamp used to select the response epoch. If `None`,
            the currently-open epoch (no end date) is selected.

    Returns:
        A new StationXML instance for the response epoch active at
        *time* (or currently open, if `time` is `None`).

    Raises:
        ValueError: If zero or more than one response epoch matches
            *time* (or "currently open", if `time` is `None`).
        urllib3.exceptions.ResponseError: If the station web service
            returns an HTTP error.

    Examples:
        ```python
        >>> from pysmo import MiniStation
        >>> from pysmo.classes import StationXML
        >>> station = MiniStation(
        ...     name="ANMO", network="IU", location="00", channel="BHZ",
        ...     latitude=34.945981, longitude=-106.457133,
        ... )
        >>> response = StationXML.fetch(station=station)  # doctest: +SKIP
        >>>
        ```
    """
    xml = fetch_stationxml(station=station)
    return cls.from_bytes(xml, time=time)

from_bytes classmethod

from_bytes(
    xml: bytes,
    *,
    time: Timestamp | None = None,
    location: str | None = None,
    channel: str | None = None
) -> Self

Create a new instance from a StationXML document, selecting one epoch.

A document is not guaranteed to cover a single channel — a station-level query (or one saved for later, offline use) commonly returns every location/channel combination on record, each with its own epoch history. location/channel narrow to one before time is applied; without them, a multi-channel document raises the same "more than one epoch" error as an ambiguous time.

Parameters:

Name Type Description Default
xml bytes

Raw StationXML document bytes (as returned by the FDSN station web service with level=response).

required
time Timestamp | None

Timestamp used to select the response epoch. If None, the currently-open epoch (no end date) is selected.

None
location str | None

Location code to narrow to, if xml covers more than one location. If None, location is not filtered.

None
channel str | None

Channel code to narrow to, if xml covers more than one channel. If None, channel is not filtered.

None

Returns:

Type Description
Self

A new StationXML instance for the response epoch active at

Self

time (or currently open, if time is None).

Raises:

Type Description
ValueError

If, after narrowing by location/channel, zero or more than one response epoch matches time (or "currently open", if time is None).

See Also

StationXML.all_from_bytes: Parse every epoch in the document without narrowing to one.

Source code in src/pysmo/classes/_stationxml.py
@classmethod
def from_bytes(
    cls,
    xml: bytes,
    *,
    time: pd.Timestamp | None = None,
    location: str | None = None,
    channel: str | None = None,
) -> Self:
    """Create a new instance from a StationXML document, selecting one epoch.

    A document is not guaranteed to cover a single channel — a
    station-level query (or one saved for later, offline use) commonly
    returns every location/channel combination on record, each with its
    own epoch history. `location`/`channel` narrow to one before `time`
    is applied; without them, a multi-channel document raises the same
    "more than one epoch" error as an ambiguous *time*.

    Args:
        xml: Raw StationXML document bytes (as returned by the FDSN
            station web service with `level=response`).
        time: Timestamp used to select the response epoch. If `None`,
            the currently-open epoch (no end date) is selected.
        location: Location code to narrow to, if `xml` covers more than
            one location. If `None`, location is not filtered.
        channel: Channel code to narrow to, if `xml` covers more than
            one channel. If `None`, channel is not filtered.

    Returns:
        A new StationXML instance for the response epoch active at
        *time* (or currently open, if `time` is `None`).

    Raises:
        ValueError: If, after narrowing by `location`/`channel`, zero or
            more than one response epoch matches *time* (or "currently
            open", if `time` is `None`).

    Tip: See Also
        [`StationXML.all_from_bytes`][pysmo.classes.StationXML.all_from_bytes]:
        Parse every epoch in the document without narrowing to one.
    """
    epochs = parse_stationxml(xml)
    matches = _matching_epochs(epochs, time, location=location, channel=channel)
    if len(matches) != 1:
        raise ValueError(
            f"Expected exactly one response epoch in the given "
            f"StationXML at "
            f"{'the currently open epoch' if time is None else time}"
            f"{f', location {location!r}' if location is not None else ''}"
            f"{f', channel {channel!r}' if channel is not None else ''}, "
            f"found {len(matches)}."
        )
    return cls._from_raw(matches[0])