Skip to content

The SAC Class#

SAC (Seismic Analysis Code) is a commonly used program that uses its own file format. Pysmo provides a Python class (SacIO) to access data stored in these SAC files. The way SAC names things is different from how the equivalent types are named in pysmo (e.g. the seismogram begin_time is b in SAC). As the attributes in the SacIO class are named accordingly, it too is not compatible with pysmo types. However, all is not lost: in order to make use of the SacIO class, we simply need to map the SacIO attributes to the names used in pysmo. This mapping happens in a new class, called SAC, which provides access to all the SacIO attributes and pysmo compatible attributes. This means there is little reason to ever use the SacIO class directly, and you are encouraged to always use the SAC class instead when working with SAC files.

SAC #

Bases: SacIO

Class for accessing data in SAC files.

Tip

This class inherits the numerous attributes and methods from the SacIO class. This gives access to all SAC headers, ability to load from a file, download data, and so on.

Examples:

The SAC class provides a data structure that mirrors header fields and data as presented by the sac fileformat. SAC instances are typically created by reading sac file. Data and header fields can be accessed as attributes:

>>> from pysmo import SAC
>>> my_sac = SAC.from_file('testfile.sac')
>>> print(my_sac.delta)
0.019999999552965164
>>> print(my_sac.data)
array([2302., 2313., 2345., ..., 2836., 2772., 2723.])
>>> my_sac.evla = 23.14

Presenting the data in this 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 would 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(my_sac, Seismogram)
False
>>> # The my_sac.seismogram object is, however:
>>> isinstance(my_sac.seismogram, Seismogram)
True

Note that my_sac.seismogram is an instance of the SacSeismogram helper class. It is created when a new SAC instance is instantiated. For convenience we can access it via a new variable:

>>> my_seismogram = my_sac.seismogram
>>> isinstance(my_seismogram, Seismogram)
True
>>> # Verify the helper object is indeed just providing access to the same thing
>>> # under a different name:
>>> my_sac.delta is my_seismogram.delta
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:

>>> my_sac.evla = None
>>> # No error: a SAC file doesn't have to contain event information.
>>> my_sac.event.latitude = None
TypeError: SacEvent.latitude may not be of None type.
>>> # Error: the my_sac.event object may not have attributes set to `None`.

Attributes:

Name Type Description
seismogram SacSeismogram

Maps pysmo compatible attributes to the internal SAC attributes.

station SacStation

Maps pysmo compatible attributes to the internal SAC attributes.

event SacEvent

Maps pysmo compatible attributes to the internal SAC attributes.

Source code in pysmo/classes/sac.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
@define(kw_only=True)
class SAC(SacIO):
    """Class for accessing data in SAC files.

    Tip:
        This class inherits the numerous attributes and methods from the
        [SacIO][pysmo.lib.io.sacio.SacIO] class. This gives access to all
        SAC headers, ability to load from a file, download data, and so on.

    Examples:
        The `SAC` class provides a data structure that mirrors header fields and
        data as presented by the sac fileformat. SAC instances are typically
        created by reading sac file. Data and header fields can be accessed as
        attributes:

        >>> from pysmo import SAC
        >>> my_sac = SAC.from_file('testfile.sac')
        >>> print(my_sac.delta)
        0.019999999552965164
        >>> print(my_sac.data)
        array([2302., 2313., 2345., ..., 2836., 2772., 2723.])
        >>> my_sac.evla = 23.14

        Presenting the data in this 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`][pysmo.types.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 would 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(my_sac, Seismogram)
        False
        >>> # The my_sac.seismogram object is, however:
        >>> isinstance(my_sac.seismogram, Seismogram)
        True

        Note that `my_sac.seismogram` is an instance of the
        [`SacSeismogram`][pysmo.classes.sac.SAC.SacSeismogram] helper class. It is
        created when a new `SAC` instance is instantiated. For convenience we can
        access it via a new variable:

        >>> my_seismogram = my_sac.seismogram
        >>> isinstance(my_seismogram, Seismogram)
        True
        >>> # Verify the helper object is indeed just providing access to the same thing
        >>> # under a different name:
        >>> my_sac.delta is my_seismogram.delta
        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`][None]:

        >>> my_sac.evla = None
        >>> # No error: a SAC file doesn't have to contain event information.
        >>> my_sac.event.latitude = None
        TypeError: SacEvent.latitude may not be of None type.
        >>> # Error: the my_sac.event object may not have attributes set to `None`.


    Attributes:
        seismogram: Maps pysmo compatible attributes to the internal SAC attributes.
        station: Maps pysmo compatible attributes to the internal SAC attributes.
        event: Maps pysmo compatible attributes to the internal SAC attributes.
    """

    @define(kw_only=True)
    class _SacNested:
        """Base class for nested SAC classes"""

        _parent: SacIO

        @property
        def _ref_datetime(self) -> datetime:
            """
            Returns:
                Reference time date in a SAC file.
            """
            # The SAC header variable "b" is set to a default value, so
            # it should never b "None" in a SacIO object. We use that
            # to define the reference time in case it is missing in
            # the parent SacIO object.
            # TODO: maybe it needs setting in the SacIO object too?
            if self._parent.kztime is None or self._parent.kzdate is None:
                return SEISMOGRAM_DEFAULTS.begin_time - timedelta(
                    seconds=self._parent.b
                )
            ref_time = time.fromisoformat(self._parent.kztime)
            ref_date = date.fromisoformat(self._parent.kzdate)
            return datetime.combine(ref_date, ref_time)

    @define(kw_only=True)
    class SacSeismogram(_SacNested):
        """Helper class for 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.SAC]instance to enable pysmo types
        compatibility.

        Attributes:
            begin_time: Seismogram begin timedate.
            end_time: Seismogram end time.
            delta: Seismogram sampling interval.
            data: Seismogram data.

        Note:
            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.
            Here, the `begin_time` is the actual time (in UTC) of the first data point.

            As other time fields in the SAC specification depend on the reference time,
            changing the `begin_time` will adjust only the `b` attribute in the parent
            [`SAC`][pysmo.classes.sac.SAC] instance.
        """

        def __len__(self) -> int:
            return np.size(self.data)

        @property
        def data(self) -> np.ndarray:
            return self._parent.data

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

        @property
        def delta(self) -> float:
            return self._parent.delta

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

        @property
        def begin_time(self) -> datetime:
            # "b" header is event origin time relative to the reference time.
            return self._ref_datetime + timedelta(seconds=self._parent.b)

        @begin_time.setter
        @value_not_none
        def begin_time(self, value: datetime) -> None:
            self._parent.b = (value - self._ref_datetime).total_seconds()

        @property
        def end_time(self) -> datetime:
            if len(self) == 0:
                return self.begin_time
            return self.begin_time + timedelta(seconds=self.delta * (len(self) - 1))

    @define(kw_only=True)
    class SacEvent(_SacNested):
        """Helper class for 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.SAC] instance to enable pysmo
        types compatibility.

        Note:
            Not all SAC files contain event information.

        Attributes:
            latitude: Event Latitude.
            longitude: Event Longitude.
            depth: Event depth in meters.
            time: Event origin time (UTC).
        """

        @property
        def latitude(self) -> float:
            if self._parent.evla is None:
                raise SacHeaderUndefined(header="evla")
            return self._parent.evla

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

        @property
        def longitude(self) -> float:
            if self._parent.evlo is None:
                raise SacHeaderUndefined(header="evlo")
            return self._parent.evlo

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

        @property
        def depth(self) -> float:
            if self._parent.evdp is None:
                raise SacHeaderUndefined(header="evdp")
            return self._parent.evdp * 1000

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

        @property
        def time(self) -> datetime:
            # "o" header is event origin time relative to the reference time.
            if self._parent.o is None:
                raise SacHeaderUndefined(header="o")
            return self._ref_datetime + timedelta(seconds=self._parent.o)

        @time.setter
        @value_not_none
        def time(self, value: datetime) -> None:
            self._parent.o = (value - self._ref_datetime).total_seconds()

    @define(kw_only=True)
    class SacStation(_SacNested):
        """Helper class for SAC event 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.SAC]instance to enable pysmo
        types compatibility.

        Attributes:
            name: Station name or code.
            network: Network name or code.
            latitude: Station latitude.
            longitude: Station longitude.
            elevation: Station elevation in meters.
        """

        @property
        def name(self) -> str:
            if self._parent.kstnm is None:
                raise SacHeaderUndefined(header="kstnm")
            return self._parent.kstnm

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

        @property
        def network(self) -> str | None:
            return self._parent.knetwk

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

        @property
        def latitude(self) -> float:
            if self._parent.stla is None:
                raise SacHeaderUndefined(header="stla")
            return self._parent.stla

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

        @property
        def longitude(self) -> float:
            if self._parent.stlo is None:
                raise SacHeaderUndefined(header="stlo")
            return self._parent.stlo

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

        @property
        def elevation(self) -> float | None:
            return self._parent.stel

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

    seismogram: SacSeismogram = field(init=False)
    station: SacStation = field(init=False)
    event: SacEvent = field(init=False)

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

SacEvent #

Bases: _SacNested

Helper class for 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.

Note

Not all SAC files contain event information.

Attributes:

Name Type Description
latitude float

Event Latitude.

longitude float

Event Longitude.

depth float

Event depth in meters.

time datetime

Event origin time (UTC).

Source code in pysmo/classes/sac.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
@define(kw_only=True)
class SacEvent(_SacNested):
    """Helper class for 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.SAC] instance to enable pysmo
    types compatibility.

    Note:
        Not all SAC files contain event information.

    Attributes:
        latitude: Event Latitude.
        longitude: Event Longitude.
        depth: Event depth in meters.
        time: Event origin time (UTC).
    """

    @property
    def latitude(self) -> float:
        if self._parent.evla is None:
            raise SacHeaderUndefined(header="evla")
        return self._parent.evla

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

    @property
    def longitude(self) -> float:
        if self._parent.evlo is None:
            raise SacHeaderUndefined(header="evlo")
        return self._parent.evlo

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

    @property
    def depth(self) -> float:
        if self._parent.evdp is None:
            raise SacHeaderUndefined(header="evdp")
        return self._parent.evdp * 1000

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

    @property
    def time(self) -> datetime:
        # "o" header is event origin time relative to the reference time.
        if self._parent.o is None:
            raise SacHeaderUndefined(header="o")
        return self._ref_datetime + timedelta(seconds=self._parent.o)

    @time.setter
    @value_not_none
    def time(self, value: datetime) -> None:
        self._parent.o = (value - self._ref_datetime).total_seconds()

SacSeismogram #

Bases: _SacNested

Helper class for 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) SACinstance to enable pysmo types compatibility.

Attributes:

Name Type Description
begin_time datetime

Seismogram begin timedate.

end_time datetime

Seismogram end time.

delta float

Seismogram sampling interval.

data ndarray

Seismogram data.

Note

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. Here, the begin_time is the actual time (in UTC) of the first data point.

As other time fields in the SAC specification depend on the reference time, changing the begin_time will adjust only the b attribute in the parent SAC instance.

Source code in pysmo/classes/sac.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
@define(kw_only=True)
class SacSeismogram(_SacNested):
    """Helper class for 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.SAC]instance to enable pysmo types
    compatibility.

    Attributes:
        begin_time: Seismogram begin timedate.
        end_time: Seismogram end time.
        delta: Seismogram sampling interval.
        data: Seismogram data.

    Note:
        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.
        Here, the `begin_time` is the actual time (in UTC) of the first data point.

        As other time fields in the SAC specification depend on the reference time,
        changing the `begin_time` will adjust only the `b` attribute in the parent
        [`SAC`][pysmo.classes.sac.SAC] instance.
    """

    def __len__(self) -> int:
        return np.size(self.data)

    @property
    def data(self) -> np.ndarray:
        return self._parent.data

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

    @property
    def delta(self) -> float:
        return self._parent.delta

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

    @property
    def begin_time(self) -> datetime:
        # "b" header is event origin time relative to the reference time.
        return self._ref_datetime + timedelta(seconds=self._parent.b)

    @begin_time.setter
    @value_not_none
    def begin_time(self, value: datetime) -> None:
        self._parent.b = (value - self._ref_datetime).total_seconds()

    @property
    def end_time(self) -> datetime:
        if len(self) == 0:
            return self.begin_time
        return self.begin_time + timedelta(seconds=self.delta * (len(self) - 1))

SacStation #

Bases: _SacNested

Helper class for SAC event 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) SACinstance to enable pysmo types compatibility.

Attributes:

Name Type Description
name str

Station name or code.

network str | None

Network name or code.

latitude float

Station latitude.

longitude float

Station longitude.

elevation float | None

Station elevation in meters.

Source code in pysmo/classes/sac.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
@define(kw_only=True)
class SacStation(_SacNested):
    """Helper class for SAC event 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.SAC]instance to enable pysmo
    types compatibility.

    Attributes:
        name: Station name or code.
        network: Network name or code.
        latitude: Station latitude.
        longitude: Station longitude.
        elevation: Station elevation in meters.
    """

    @property
    def name(self) -> str:
        if self._parent.kstnm is None:
            raise SacHeaderUndefined(header="kstnm")
        return self._parent.kstnm

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

    @property
    def network(self) -> str | None:
        return self._parent.knetwk

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

    @property
    def latitude(self) -> float:
        if self._parent.stla is None:
            raise SacHeaderUndefined(header="stla")
        return self._parent.stla

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

    @property
    def longitude(self) -> float:
        if self._parent.stlo is None:
            raise SacHeaderUndefined(header="stlo")
        return self._parent.stlo

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

    @property
    def elevation(self) -> float | None:
        return self._parent.stel

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

SacIO #

The SacIO class reads and writes data and header values to and from a SAC file. Instances of SacIO provide attributes named identially to header names in the SAC file format. Additonal attributes may be set, but are not written to a SAC file (because there is no space reserved for them there). Class attributes with corresponding header fields in a SAC file (for example the begin time b) are checked for a valid format before being saved in the SacIO instance.

Warning

This class should typically never be used directly. Instead please use the SAC class, which inherits all attributes and methods from here.

Examples:

Create a new instance from a file and print seismogram data:

>>> from pysmo.lib.io import SacIO
>>> my_sac = SacIO.from_file('testfile.sac')
>>> data = my_sac.data
>>> data
array([-1616.0, -1609.0, -1568.0, -1606.0, -1615.0, -1565.0, ...

Read the sampling rate:

>>> delta = my_sac.delta
>>> delta
0.019999999552965164

Change the sampling rate:

>>> newdelta = 0.05
>>> my_sac.delta = newdelta
>>> my_sac.delta
0.05

Create a new instance from IRIS services:

>>> from pysmo.lib.io import SacIO
>>> my_sac = SacIO.from_iris(
>>>             net="C1",
>>>             sta="VA01",
>>>             cha="BHZ",
>>>             loc="--",
>>>             start="2021-03-22T13:00:00",
>>>             duration=1 * 60 * 60,
>>>             scale="AUTO",
>>>             demean="true",
>>>             force_single_result=True)
>>> my_sac.npts
144001

For each SAC(file) header field there is a corresponding attribute in this class. There are a lot of header fields in a SAC file, which are all called the same way when using SAC.

Attributes:

Name Type Description
delta float

Increment between evenly spaced samples (nominal value).

depmin float | None

Minimum value of dependent variable.

depmax float | None

Maximum value of dependent variable.

odelta float | None

Observed increment if different from nominal value.

b float

Beginning value of the independent variable.

e float

Ending value of the independent variable.

o float | None

Event origin time (seconds relative to reference time).

a float | None

First arrival time (seconds relative to reference time).

t0 float | None

User defined time pick or marker 0 (seconds relative to reference time).

t1 float | None

User defined time pick or marker 1 (seconds relative to reference time).

t2 float | None

User defined time pick or marker 2 (seconds relative to reference time).

t3 float | None

User defined time pick or marker 3 (seconds relative to reference time).

t4 float | None

User defined time pick or marker 4 (seconds relative to reference time).

t5 float | None

User defined time pick or marker 5 (seconds relative to reference time).

t6 float | None

User defined time pick or marker 6 (seconds relative to reference time).

t7 float | None

User defined time pick or marker 7 (seconds relative to reference time).

t8 float | None

User defined time pick or marker 8 (seconds relative to reference time).

t9 float | None

User defined time pick or marker 9 (seconds relative to reference time).

f float | None

Fini or end of event time (seconds relative to reference time).

resp0 float | None

Instrument response parameter 0 (not currently used).

resp1 float | None

Instrument response parameter 1 (not currently used).

resp2 float | None

Instrument response parameter 2 (not currently used).

resp3 float | None

Instrument response parameter 3 (not currently used).

resp4 float | None

Instrument response parameter 4 (not currently used).

resp5 float | None

Instrument response parameter 5 (not currently used).

resp6 float | None

Instrument response parameter 6 (not currently used).

resp7 float | None

Instrument response parameter 7 (not currently used).

resp8 float | None

Instrument response parameter 8 (not currently used).

resp9 float | None

Instrument response parameter 9 (not currently used).

stla float | None

Station latitude (degrees, north positive).

stlo float | None

Station longitude (degrees, east positive).

stel float | None

Station elevation above sea level (meters).

stdp float | None

Station depth below surface (meters).

evla float | None

Event latitude (degrees, north positive).

evlo float | None

Event longitude (degrees, east positive).

evel float | None

Event elevation (meters).

evdp float | None

Event depth below surface (kilometers -- previously meters).

mag float | None

Event magnitude.

user0 float | None

User defined variable storage area.

user1 float | None

User defined variable storage area.

user2 float | None

User defined variable storage area.

user3 float | None

User defined variable storage area.

user4 float | None

User defined variable storage area.

user5 float | None

User defined variable storage area.

user6 float | None

User defined variable storage area.

user7 float | None

User defined variable storage area.

user8 float | None

User defined variable storage area.

user9 float | None

User defined variable storage area.

dist float

Station to event distance (km).

az float

Event to station azimuth (degrees).

baz float

Station to event azimuth (degrees).

gcarc float

Station to event great circle arc length (degrees).

depmen float | None

Mean value of dependent variable.

cmpaz float | None

Component azimuth (degrees clockwise from north).

cmpinc float | None

Component incident angle (degrees from upward vertical; SEED/MINISEED uses dip: degrees from horizontal down).

xminimum float | None

Minimum value of X (Spectral files only).

xmaximum float | None

Maximum value of X (Spectral files only).

yminimum float | None

Minimum value of Y (Spectral files only).

ymaximum float | None

Maximum value of Y (Spectral files only).

nzyear int | None

GMT year corresponding to reference (zero) time in file.

nzjday int | None

GMT julian day.

nzhour int | None

GMT hour.

nzmin int | None

GMT minute.

nzsec int | None

GMT second.

nzmsec int | None

GMT millisecond.

nvhdr int

Header version number.

norid int | None

Origin ID (CSS 3.0).

nevid int | None

Event ID (CSS 3.0).

npts int

Number of points per data component.

nwfid int | None

Waveform ID (CSS 3.0).

nxsize float | None

Spectral Length (Spectral files only).

nysize float | None

Spectral Length (Spectral files only).

iftype str

Type of file.

idep str

Type of dependent variable.

iztype str

Reference time equivalence.

iinst str | None

Type of recording instrument.

istreg str | None

Station geographic region.

ievreg str | None

Event geographic region.

ievtyp str

Type of event.

iqual str | None

Quality of data.

isynth str | None

Synthetic data flag.

imagtyp str | None

Magnitude type.

imagsrc str | None

Source of magnitude information.

ibody str | None

Body / Spheroid definition used in Distance Calculations.

leven bool

TRUE if data is evenly spaced.

lpspol bool | None

TRUE if station components have a positive polarity (left-hand rule).

lovrok bool | None

TRUE if it is okay to overwrite this file on disk.

lcalda bool

TRUE if DIST, AZ, BAZ, and GCARC are to be calculated from station and event coordinates.

kstnm str | None

Station name.

kevnm str | None

Event name.

khole str | None

Nuclear: hole identifier; Other: location identifier (LOCID).

ko str | None

Event origin time identification.

ka str | None

First arrival time identification.

kt0 str | None

User defined time pick identification.

kt1 str | None

User defined time pick identification.

kt2 str | None

User defined time pick identification.

kt3 str | None

User defined time pick identification.

kt4 str | None

User defined time pick identification.

kt5 str | None

User defined time pick identification.

kt6 str | None

User defined time pick identification.

kt7 str | None

User defined time pick identification.

kt8 str | None

User defined time pick identification.

kt9 str | None

User defined time pick identification.

kf str | None

Fini identification.

kuser0 str | None

User defined variable storage area.

kuser1 str | None

User defined variable storage area.

kuser2 str | None

User defined variable storage area.

kcmpnm str | None

Channel name. SEED volumes use three character names, and the third is the component/orientation. For horizontals, the current trend is to use 1 and 2 instead of N and E.

knetwk str | None

Name of seismic network.

kdatrd str | None

Date data was read onto computer.

kinst str | None

Generic name of recording instrument.

Source code in pysmo/lib/io/sacio.py
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
@define(kw_only=True)
class SacIO:
    """
    The `SacIO` class reads and writes data and header values to and from a
    SAC file. Instances of `SacIO` provide attributes named identially to
    header names in the SAC file format. Additonal attributes may be set, but are
    not written to a SAC file (because there is no space reserved for them there).
    Class attributes with corresponding header fields in a SAC file (for example the
    begin time `b`) are checked for a valid format before being saved in the
    `SacIO` instance.

    Warning:
        This class should typically never be used directly. Instead please
        use the [SAC][pysmo.classes.sac.SAC] class, which inherits all
        attributes and methods from here.

    Examples:
        Create a new instance from a file and print seismogram data:

        >>> from pysmo.lib.io import SacIO
        >>> my_sac = SacIO.from_file('testfile.sac')
        >>> data = my_sac.data
        >>> data
        array([-1616.0, -1609.0, -1568.0, -1606.0, -1615.0, -1565.0, ...

        Read the sampling rate:

        >>> delta = my_sac.delta
        >>> delta
        0.019999999552965164

        Change the sampling rate:

        >>> newdelta = 0.05
        >>> my_sac.delta = newdelta
        >>> my_sac.delta
        0.05

        Create a new instance from IRIS services:

        >>> from pysmo.lib.io import SacIO
        >>> my_sac = SacIO.from_iris(
        >>>             net="C1",
        >>>             sta="VA01",
        >>>             cha="BHZ",
        >>>             loc="--",
        >>>             start="2021-03-22T13:00:00",
        >>>             duration=1 * 60 * 60,
        >>>             scale="AUTO",
        >>>             demean="true",
        >>>             force_single_result=True)
        >>> my_sac.npts
        144001

    For each SAC(file) header field there is a corresponding attribute in this class.
    There are a lot of header fields in a SAC file, which are all called the
    same way when using `SAC`.

    Attributes:
        delta:
            Increment between evenly spaced samples (nominal value).
        depmin:
            Minimum value of dependent variable.
        depmax:
            Maximum value of dependent variable.
        odelta:
            Observed increment if different from nominal value.
        b:
            Beginning value of the independent variable.
        e:
            Ending value of the independent variable.
        o:
            Event origin time (seconds relative to reference time).
        a:
            First arrival time (seconds relative to reference time).
        t0:
            User defined time pick or marker 0 (seconds relative to reference
            time).
        t1:
            User defined time pick or marker 1 (seconds relative to reference
            time).
        t2:
            User defined time pick or marker 2 (seconds relative to reference
            time).
        t3:
            User defined time pick or marker 3 (seconds relative to reference
            time).
        t4:
            User defined time pick or marker 4 (seconds relative to reference
            time).
        t5:
            User defined time pick or marker 5 (seconds relative to reference
            time).
        t6:
            User defined time pick or marker 6 (seconds relative to reference
            time).
        t7:
            User defined time pick or marker 7 (seconds relative to reference
            time).
        t8:
            User defined time pick or marker 8 (seconds relative to reference
            time).
        t9:
            User defined time pick or marker 9 (seconds relative to reference
            time).
        f:
            Fini or end of event time (seconds relative to reference time).
        resp0:
            Instrument response parameter 0 (not currently used).
        resp1:
            Instrument response parameter 1 (not currently used).
        resp2:
            Instrument response parameter 2 (not currently used).
        resp3:
            Instrument response parameter 3 (not currently used).
        resp4:
            Instrument response parameter 4 (not currently used).
        resp5:
            Instrument response parameter 5 (not currently used).
        resp6:
            Instrument response parameter 6 (not currently used).
        resp7:
            Instrument response parameter 7 (not currently used).
        resp8:
            Instrument response parameter 8 (not currently used).
        resp9:
            Instrument response parameter 9 (not currently used).
        stla:
            Station latitude (degrees, north positive).
        stlo:
            Station longitude (degrees, east positive).
        stel:
            Station elevation above sea level (meters).
        stdp:
            Station depth below surface (meters).
        evla:
            Event latitude (degrees, north positive).
        evlo:
            Event longitude (degrees, east positive).
        evel:
            Event elevation (meters).
        evdp:
            Event depth below surface (kilometers -- previously meters).
        mag:
            Event magnitude.
        user0:
            User defined variable storage area.
        user1:
            User defined variable storage area.
        user2:
            User defined variable storage area.
        user3:
            User defined variable storage area.
        user4:
            User defined variable storage area.
        user5:
            User defined variable storage area.
        user6:
            User defined variable storage area.
        user7:
            User defined variable storage area.
        user8:
            User defined variable storage area.
        user9:
            User defined variable storage area.
        dist:
            Station to event distance (km).
        az:
            Event to station azimuth (degrees).
        baz:
            Station to event azimuth (degrees).
        gcarc:
            Station to event great circle arc length (degrees).
        depmen:
            Mean value of dependent variable.
        cmpaz:
            Component azimuth (degrees clockwise from north).
        cmpinc:
            Component incident angle (degrees from upward vertical; SEED/MINISEED
            uses dip: degrees from horizontal down).
        xminimum:
            Minimum value of X (Spectral files only).
        xmaximum:
            Maximum value of X (Spectral files only).
        yminimum:
            Minimum value of Y (Spectral files only).
        ymaximum:
            Maximum value of Y (Spectral files only).
        nzyear:
            GMT year corresponding to reference (zero) time in file.
        nzjday:
            GMT julian day.
        nzhour:
            GMT hour.
        nzmin:
            GMT minute.
        nzsec:
            GMT second.
        nzmsec:
            GMT millisecond.
        nvhdr:
            Header version number.
        norid:
            Origin ID (CSS 3.0).
        nevid:
            Event ID (CSS 3.0).
        npts:
            Number of points per data component.
        nwfid:
            Waveform ID (CSS 3.0).
        nxsize:
            Spectral Length (Spectral files only).
        nysize:
            Spectral Length (Spectral files only).
        iftype:
            Type of file.
        idep:
            Type of dependent variable.
        iztype:
            Reference time equivalence.
        iinst:
            Type of recording instrument.
        istreg:
            Station geographic region.
        ievreg:
            Event geographic region.
        ievtyp:
            Type of event.
        iqual:
            Quality of data.
        isynth:
            Synthetic data flag.
        imagtyp:
            Magnitude type.
        imagsrc:
            Source of magnitude information.
        ibody:
            Body / Spheroid definition used in Distance Calculations.
        leven:
            TRUE if data is evenly spaced.
        lpspol:
            TRUE if station components have a positive polarity (left-hand rule).
        lovrok:
            TRUE if it is okay to overwrite this file on disk.
        lcalda:
            TRUE if DIST, AZ, BAZ, and GCARC are to be calculated from station and
            event coordinates.
        kstnm:
            Station name.
        kevnm:
            Event name.
        khole:
            Nuclear: hole identifier; Other: location identifier (LOCID).
        ko:
            Event origin time identification.
        ka:
            First arrival time identification.
        kt0:
            User defined time pick identification.
        kt1:
            User defined time pick identification.
        kt2:
            User defined time pick identification.
        kt3:
            User defined time pick identification.
        kt4:
            User defined time pick identification.
        kt5:
            User defined time pick identification.
        kt6:
            User defined time pick identification.
        kt7:
            User defined time pick identification.
        kt8:
            User defined time pick identification.
        kt9:
            User defined time pick identification.
        kf:
            Fini identification.
        kuser0:
            User defined variable storage area.
        kuser1:
            User defined variable storage area.
        kuser2:
            User defined variable storage area.
        kcmpnm:
            Channel name. SEED volumes use three character names, and the third is
            the component/orientation. For horizontals, the current trend is to
            use 1 and 2 instead of N and E.
        knetwk:
            Name of seismic network.
        kdatrd:
            Date data was read onto computer.
        kinst:
            Generic name of recording instrument.
    """

    delta: float = field(
        default=SACIO_DEFAULTS.delta, converter=float, validator=[type_validator()]
    )
    odelta: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    b: float = field(
        default=SACIO_DEFAULTS.b, converter=float, validator=[type_validator()]
    )
    o: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    a: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t0: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t1: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t2: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t3: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t4: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t5: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t6: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t7: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t8: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    t9: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    f: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp0: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp1: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp2: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp3: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp4: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp5: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp6: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp7: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp8: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    resp9: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    stla: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional(
            [type_validator(), validators.ge(-90), validators.le(90)]
        ),
    )
    stlo: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional(
            [type_validator(), validators.gt(-180), validators.le(180)]
        ),
    )
    stel: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    stdp: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    evla: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional(
            [type_validator(), validators.ge(-90), validators.le(90)]
        ),
    )
    evlo: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional(
            [type_validator(), validators.gt(-180), validators.le(180)]
        ),
    )
    evel: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    evdp: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    mag: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user0: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user1: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user2: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user3: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user4: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user5: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user6: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user7: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user8: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    user9: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    cmpaz: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    cmpinc: float | None = field(
        default=None,
        converter=converters.optional(float),
        validator=validators.optional([type_validator()]),
    )
    nzyear: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nzjday: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nzhour: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nzmin: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nzsec: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nzmsec: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nvhdr: int = field(default=SACIO_DEFAULTS.nvhdr, validator=[type_validator()])
    norid: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nevid: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    nwfid: int | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    iftype: str = field(default=SACIO_DEFAULTS.iftype, validator=validate_sacenum)
    idep: str = field(default=SACIO_DEFAULTS.idep, validator=validate_sacenum)
    iztype: str = field(default=SACIO_DEFAULTS.iztype, validator=validate_sacenum)
    iinst: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(4)]),
    )
    istreg: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(4)]),
    )
    ievreg: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(4)]),
    )
    ievtyp: str = field(default=SACIO_DEFAULTS.ievtyp, validator=validate_sacenum)
    iqual: str | None = field(
        default=None, validator=validators.optional(validate_sacenum)
    )
    isynth: str | None = field(
        default=None, validator=validators.optional(validate_sacenum)
    )
    imagtyp: str | None = field(
        default=None, validator=validators.optional(validate_sacenum)
    )
    imagsrc: str | None = field(
        default=None, validator=validators.optional(validate_sacenum)
    )
    ibody: str | None = field(
        default=None, validator=validators.optional(validate_sacenum)
    )
    leven: bool = field(default=SACIO_DEFAULTS.leven, validator=[type_validator()])
    lpspol: bool | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    lovrok: bool | None = field(
        default=None, validator=validators.optional([type_validator()])
    )
    kstnm: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kevnm: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(16)]),
    )
    khole: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    ko: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    ka: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt0: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt1: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt2: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt3: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt4: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt5: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt6: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt7: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt8: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kt9: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kf: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kuser0: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kuser1: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kuser2: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kcmpnm: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    knetwk: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kdatrd: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    kinst: str | None = field(
        default=None,
        validator=validators.optional([type_validator(), validators.max_len(8)]),
    )
    data: np.ndarray = field(factory=lambda: np.array([]), validator=type_validator())
    x: np.ndarray = field(factory=lambda: np.array([]), validator=type_validator())
    y: np.ndarray = field(factory=lambda: np.array([]), validator=type_validator())

    @property
    def depmin(self) -> float | None:
        if self.npts == 0:
            return None
        return np.min(self.data)

    @property
    def depmax(self) -> float | None:
        if self.npts == 0:
            return None
        return np.max(self.data)

    @property
    def depmen(self) -> float | None:
        if self.npts == 0:
            return None
        return np.mean(self.data)

    @property
    def e(self) -> float:
        if self.npts == 0:
            return self.b
        return self.b + (self.npts - 1) * self.delta

    @property
    def dist(self) -> float:
        if self.stla and self.stlo and self.evla and self.evlo:
            return (
                _azdist(lat1=self.stla, lon1=self.stlo, lat2=self.evla, lon2=self.evlo)[
                    2
                ]
                / 1000
            )
        raise SacHeaderUndefined("One or more coordinates are None.")

    @property
    def az(self) -> float:
        if self.stla and self.stlo and self.evla and self.evlo:
            return _azdist(
                lat1=self.stla, lon1=self.stlo, lat2=self.evla, lon2=self.evlo
            )[0]
        raise SacHeaderUndefined("One or more coordinates are None.")

    @property
    def baz(self) -> float:
        if self.stla and self.stlo and self.evla and self.evlo:
            return _azdist(
                lat1=self.stla, lon1=self.stlo, lat2=self.evla, lon2=self.evlo
            )[1]
        raise SacHeaderUndefined("One or more coordinates are None.")

    @property
    def gcarc(self) -> float:
        if self.stla and self.stlo and self.evla and self.evlo:
            lat1, lon1 = np.deg2rad(self.stla), np.deg2rad(self.stlo)
            lat2, lon2 = np.deg2rad(self.evla), np.deg2rad(self.evlo)
            return np.rad2deg(
                np.arccos(
                    np.sin(lat1) * np.sin(lat2)
                    + np.cos(lat1) * np.cos(lat2) * np.cos(np.abs(lon1 - lon2))
                )
            )
        raise SacHeaderUndefined("One or more coordinates are None.")

    @property
    def xminimum(self) -> float | None:
        if self.nxsize == 0 or not self.nxsize:
            return None
        return float(np.min(self.x))

    @property
    def xmaximum(self) -> float | None:
        if self.nxsize == 0 or not self.nxsize:
            return None
        return np.max(self.x)

    @property
    def yminimum(self) -> float | None:
        if self.nysize == 0 or not self.nysize:
            return None
        return np.min(self.y)

    @property
    def ymaximum(self) -> float | None:
        if self.nysize == 0 or not self.nysize:
            return None
        return np.max(self.y)

    @property
    def npts(self) -> int:
        return np.size(self.data)

    @property
    def nxsize(self) -> float | None:
        if np.size(self.x) == 0:
            return None
        return np.size(self.x)

    @property
    def nysize(self) -> float | None:
        if np.size(self.y) == 0:
            return None
        return np.size(self.y)

    @property
    def lcalda(self) -> bool:
        # all distances and bearings are always calculated...
        return True

    @property
    def kzdate(self) -> str | None:
        """
        Returns:
            ISO 8601 format of GMT reference date.
        """
        if self.nzyear is None or self.nzjday is None:
            return None
        _kzdate = datetime.date(self.nzyear, 1, 1) + datetime.timedelta(self.nzjday - 1)
        return _kzdate.isoformat()

    @property
    def kztime(self) -> str | None:
        """
        Returns:
            Alphanumeric form of GMT reference time.
        """
        if (
            self.nzhour is None
            or self.nzmin is None
            or self.nzsec is None
            or self.nzmsec is None
        ):
            return None
        _kztime = datetime.time(self.nzhour, self.nzmin, self.nzsec, self.nzmsec * 1000)
        return _kztime.isoformat(timespec="milliseconds")

    def read(self, filename: str) -> None:
        """Read data and headers from a SAC file into an existing SAC instance.

        Parameters:
            filename: Name of the sac file to read.
        """

        with open(filename, "rb") as file_handle:
            self.read_buffer(file_handle.read())

    def read_buffer(self, buffer: bytes) -> None:
        """Read data and headers from a SAC byte buffer into an existing SAC instance.

        Parameters:
            buffer: Buffer containing SAC file content.
        """

        if len(buffer) < 632:
            raise EOFError()

        # Guess the file endianness first using the unused12 header field.
        # It is located at position 276 and its value should be -12345.0.
        # Try reading with little endianness
        if struct.unpack("<f", buffer[276:280])[-1] == -12345.0:
            file_byteorder = "<"
        # otherwise assume big endianness.
        else:
            file_byteorder = ">"

        # Loop over all header fields and store them in the SAC object under their
        # respective private names.
        npts = 0
        for header, header_metadata in SAC_HEADERS.items():
            header_type = header_metadata.type
            header_required = header_metadata.required
            header_undefined = HEADER_TYPES[header_type].undefined
            start = header_metadata.start
            length = header_metadata.length
            end = start + length
            if end >= len(buffer):
                continue
            content = buffer[start:end]
            value = struct.unpack(file_byteorder + header_metadata.format, content)[0]
            if isinstance(value, bytes):
                # strip spaces and "\x00" chars
                value = value.decode().rstrip(" \x00")

            # npts is read only property in this class, but is needed for reading data
            if header == "npts":
                npts = int(value)

            # raise error if header is undefined AND required
            if value == header_undefined and header_required:
                raise RuntimeError(
                    f"Required {header=} is undefined - invalid SAC file!"
                )

            # skip if undefined (value == -12345...) and not required
            if value == header_undefined and not header_required:
                continue

            # convert enumerated header to string and format others
            if header_type == "i":
                value = SacEnum(value).name

            # SAC file has headers fields which are read only attributes in this
            # class. We skip them with this try/except.
            # TODO: This is a bit crude, should maybe be a bit more specific.
            try:
                setattr(self, header, value)
            except AttributeError as e:
                if "object has no setter" in str(e):
                    pass

        # Only accept IFTYPE = ITIME SAC files. Other IFTYPE use two data blocks,
        # which is something we don't support for now.
        if self.iftype.lower() != "time":
            raise NotImplementedError(
                f"Reading SAC files with IFTYPE=(I){self.iftype.upper()} is not supported."  # noqa: E501
            )

        # Read first data block
        start = 632
        length = npts * 4
        data_end = start + length
        self.data = np.array([])
        if length > 0:
            data_end = start + length
            data_format = file_byteorder + str(npts) + "f"
            if data_end > len(buffer):
                raise EOFError()
            content = buffer[start:data_end]
            data = struct.unpack(data_format, content)
            self.data = np.array(data)

        if self.nvhdr == 7:
            for footer, footer_metadata in SAC_FOOTERS.items():
                undefined = -12345.0
                length = 8
                start = footer_metadata.start + data_end
                end = start + length

                if end > len(buffer):
                    raise EOFError()
                content = buffer[start:end]

                value = struct.unpack(file_byteorder + "d", content)[0]

                # skip if undefined (value == -12345...)
                if value == undefined:
                    continue

                # SAC file has headers fields which are read only attributes in this
                # class. We skip them with this try/except.
                # TODO: This is a bit crude, should maybe be a bit more specific.
                try:
                    setattr(self, footer, value)
                except AttributeError as e:
                    if "object has no setter" in str(e):
                        pass

    @classmethod
    def from_file(cls, filename: str) -> Self:
        """Create a new SAC instance from a SAC file.

        Parameters:
            filename: Name of the SAC file to read.

        Returns:
            A new SacIO instance.
        """
        newinstance = cls()
        newinstance.read(filename)
        return newinstance

    @classmethod
    def from_buffer(cls, buffer: bytes) -> Self:
        """Create a new SAC instance from a SAC data buffer.

        Parameters:
            buffer: Buffer containing SAC file content.

        Returns:
            A new SacIO instance.
        """
        newinstance = cls()
        newinstance.read_buffer(buffer)
        return newinstance

    @classmethod
    def from_iris(
        cls,
        net: str,
        sta: str,
        cha: str,
        loc: str,
        force_single_result: bool = False,
        **kwargs: Any,
    ) -> Self | dict[str, Self] | None:
        """Create a list of SAC instances from a single IRIS
        request using the output format as "sac.zip".

        Parameters:
            force_single_result: If true, the function will return a single SAC
                                 object or None if the requests returns nothing.

        Returns:
            A new SacIO instance.
        """
        kwargs["net"] = net
        kwargs["sta"] = sta
        kwargs["cha"] = cha
        kwargs["loc"] = loc
        kwargs["output"] = "sac.zip"

        if isinstance(kwargs["start"], datetime.datetime):
            kwargs["start"] = kwargs["start"].isoformat()

        end = kwargs.get("end", None)
        if end is not None and isinstance(end, datetime.datetime):
            kwargs["end"] = end.isoformat()

        base = "https://service.iris.edu/irisws/timeseries/1/query"
        params = urllib.parse.urlencode(kwargs, doseq=False)
        url = f"{base}?{params}"
        response = requests.get(url)
        if not response:
            raise ValueError(response.content.decode("utf-8"))
        zip = zipfile.ZipFile(io.BytesIO(response.content))
        result = {}
        for name in zip.namelist():
            buffer = zip.read(name)
            sac = cls.from_buffer(buffer)
            if force_single_result:
                return sac
            result[name] = sac
        return None if force_single_result else result

    def write(self, filename: str) -> None:
        """Writes data and header values to a SAC file.

        Parameters:
            filename: Name of the sacfile to write to.
        """
        with open(filename, "wb") as file_handle:
            # loop over all valid header fields and write them to the file
            for header, header_metadata in SAC_HEADERS.items():
                header_type = header_metadata.type
                header_format = header_metadata.format
                start = header_metadata.start
                header_undefined = HEADER_TYPES[header_type].undefined

                value = None
                try:
                    if hasattr(self, header):
                        value = getattr(self, header)
                except SacHeaderUndefined:
                    value = None

                # convert enumerated header to integer if it is not None
                if header_type == "i" and value is not None:
                    value = SacEnum[value].value

                # set None to -12345
                if value is None:
                    value = header_undefined

                # Encode strings to bytes
                if isinstance(value, str):
                    value = value.encode()

                # write to file
                file_handle.seek(start)
                file_handle.write(struct.pack(header_format, value))

            # write data (if npts > 0)
            data_1_start = 632
            data_1_end = data_1_start + self.npts * 4
            file_handle.truncate(data_1_start)
            if self.npts > 0:
                file_handle.seek(data_1_start)
                for x in self.data:
                    file_handle.write(struct.pack("f", x))

            if self.nvhdr == 7:
                for footer, footer_metadata in SAC_FOOTERS.items():
                    undefined = -12345.0
                    start = footer_metadata.start + data_1_end
                    value = None
                    try:
                        if hasattr(self, footer):
                            value = getattr(self, footer)
                    except SacHeaderUndefined:
                        value = None

                    # set None to -12345
                    if value is None:
                        value = undefined

                    # write to file
                    file_handle.seek(start)
                    file_handle.write(struct.pack("d", value))

kzdate: str | None property #

Returns:

Type Description
str | None

ISO 8601 format of GMT reference date.

kztime: str | None property #

Returns:

Type Description
str | None

Alphanumeric form of GMT reference time.

from_buffer(buffer) classmethod #

Create a new SAC instance from a SAC data buffer.

Parameters:

Name Type Description Default
buffer bytes

Buffer containing SAC file content.

required

Returns:

Type Description
Self

A new SacIO instance.

Source code in pysmo/lib/io/sacio.py
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
@classmethod
def from_buffer(cls, buffer: bytes) -> Self:
    """Create a new SAC instance from a SAC data buffer.

    Parameters:
        buffer: Buffer containing SAC file content.

    Returns:
        A new SacIO instance.
    """
    newinstance = cls()
    newinstance.read_buffer(buffer)
    return newinstance

from_file(filename) classmethod #

Create a new SAC instance from a SAC file.

Parameters:

Name Type Description Default
filename str

Name of the SAC file to read.

required

Returns:

Type Description
Self

A new SacIO instance.

Source code in pysmo/lib/io/sacio.py
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
@classmethod
def from_file(cls, filename: str) -> Self:
    """Create a new SAC instance from a SAC file.

    Parameters:
        filename: Name of the SAC file to read.

    Returns:
        A new SacIO instance.
    """
    newinstance = cls()
    newinstance.read(filename)
    return newinstance

from_iris(net, sta, cha, loc, force_single_result=False, **kwargs) classmethod #

Create a list of SAC instances from a single IRIS request using the output format as "sac.zip".

Parameters:

Name Type Description Default
force_single_result bool

If true, the function will return a single SAC object or None if the requests returns nothing.

False

Returns:

Type Description
Self | dict[str, Self] | None

A new SacIO instance.

Source code in pysmo/lib/io/sacio.py
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
@classmethod
def from_iris(
    cls,
    net: str,
    sta: str,
    cha: str,
    loc: str,
    force_single_result: bool = False,
    **kwargs: Any,
) -> Self | dict[str, Self] | None:
    """Create a list of SAC instances from a single IRIS
    request using the output format as "sac.zip".

    Parameters:
        force_single_result: If true, the function will return a single SAC
                             object or None if the requests returns nothing.

    Returns:
        A new SacIO instance.
    """
    kwargs["net"] = net
    kwargs["sta"] = sta
    kwargs["cha"] = cha
    kwargs["loc"] = loc
    kwargs["output"] = "sac.zip"

    if isinstance(kwargs["start"], datetime.datetime):
        kwargs["start"] = kwargs["start"].isoformat()

    end = kwargs.get("end", None)
    if end is not None and isinstance(end, datetime.datetime):
        kwargs["end"] = end.isoformat()

    base = "https://service.iris.edu/irisws/timeseries/1/query"
    params = urllib.parse.urlencode(kwargs, doseq=False)
    url = f"{base}?{params}"
    response = requests.get(url)
    if not response:
        raise ValueError(response.content.decode("utf-8"))
    zip = zipfile.ZipFile(io.BytesIO(response.content))
    result = {}
    for name in zip.namelist():
        buffer = zip.read(name)
        sac = cls.from_buffer(buffer)
        if force_single_result:
            return sac
        result[name] = sac
    return None if force_single_result else result

read(filename) #

Read data and headers from a SAC file into an existing SAC instance.

Parameters:

Name Type Description Default
filename str

Name of the sac file to read.

required
Source code in pysmo/lib/io/sacio.py
1277
1278
1279
1280
1281
1282
1283
1284
1285
def read(self, filename: str) -> None:
    """Read data and headers from a SAC file into an existing SAC instance.

    Parameters:
        filename: Name of the sac file to read.
    """

    with open(filename, "rb") as file_handle:
        self.read_buffer(file_handle.read())

read_buffer(buffer) #

Read data and headers from a SAC byte buffer into an existing SAC instance.

Parameters:

Name Type Description Default
buffer bytes

Buffer containing SAC file content.

required
Source code in pysmo/lib/io/sacio.py
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
def read_buffer(self, buffer: bytes) -> None:
    """Read data and headers from a SAC byte buffer into an existing SAC instance.

    Parameters:
        buffer: Buffer containing SAC file content.
    """

    if len(buffer) < 632:
        raise EOFError()

    # Guess the file endianness first using the unused12 header field.
    # It is located at position 276 and its value should be -12345.0.
    # Try reading with little endianness
    if struct.unpack("<f", buffer[276:280])[-1] == -12345.0:
        file_byteorder = "<"
    # otherwise assume big endianness.
    else:
        file_byteorder = ">"

    # Loop over all header fields and store them in the SAC object under their
    # respective private names.
    npts = 0
    for header, header_metadata in SAC_HEADERS.items():
        header_type = header_metadata.type
        header_required = header_metadata.required
        header_undefined = HEADER_TYPES[header_type].undefined
        start = header_metadata.start
        length = header_metadata.length
        end = start + length
        if end >= len(buffer):
            continue
        content = buffer[start:end]
        value = struct.unpack(file_byteorder + header_metadata.format, content)[0]
        if isinstance(value, bytes):
            # strip spaces and "\x00" chars
            value = value.decode().rstrip(" \x00")

        # npts is read only property in this class, but is needed for reading data
        if header == "npts":
            npts = int(value)

        # raise error if header is undefined AND required
        if value == header_undefined and header_required:
            raise RuntimeError(
                f"Required {header=} is undefined - invalid SAC file!"
            )

        # skip if undefined (value == -12345...) and not required
        if value == header_undefined and not header_required:
            continue

        # convert enumerated header to string and format others
        if header_type == "i":
            value = SacEnum(value).name

        # SAC file has headers fields which are read only attributes in this
        # class. We skip them with this try/except.
        # TODO: This is a bit crude, should maybe be a bit more specific.
        try:
            setattr(self, header, value)
        except AttributeError as e:
            if "object has no setter" in str(e):
                pass

    # Only accept IFTYPE = ITIME SAC files. Other IFTYPE use two data blocks,
    # which is something we don't support for now.
    if self.iftype.lower() != "time":
        raise NotImplementedError(
            f"Reading SAC files with IFTYPE=(I){self.iftype.upper()} is not supported."  # noqa: E501
        )

    # Read first data block
    start = 632
    length = npts * 4
    data_end = start + length
    self.data = np.array([])
    if length > 0:
        data_end = start + length
        data_format = file_byteorder + str(npts) + "f"
        if data_end > len(buffer):
            raise EOFError()
        content = buffer[start:data_end]
        data = struct.unpack(data_format, content)
        self.data = np.array(data)

    if self.nvhdr == 7:
        for footer, footer_metadata in SAC_FOOTERS.items():
            undefined = -12345.0
            length = 8
            start = footer_metadata.start + data_end
            end = start + length

            if end > len(buffer):
                raise EOFError()
            content = buffer[start:end]

            value = struct.unpack(file_byteorder + "d", content)[0]

            # skip if undefined (value == -12345...)
            if value == undefined:
                continue

            # SAC file has headers fields which are read only attributes in this
            # class. We skip them with this try/except.
            # TODO: This is a bit crude, should maybe be a bit more specific.
            try:
                setattr(self, footer, value)
            except AttributeError as e:
                if "object has no setter" in str(e):
                    pass

write(filename) #

Writes data and header values to a SAC file.

Parameters:

Name Type Description Default
filename str

Name of the sacfile to write to.

required
Source code in pysmo/lib/io/sacio.py
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
def write(self, filename: str) -> None:
    """Writes data and header values to a SAC file.

    Parameters:
        filename: Name of the sacfile to write to.
    """
    with open(filename, "wb") as file_handle:
        # loop over all valid header fields and write them to the file
        for header, header_metadata in SAC_HEADERS.items():
            header_type = header_metadata.type
            header_format = header_metadata.format
            start = header_metadata.start
            header_undefined = HEADER_TYPES[header_type].undefined

            value = None
            try:
                if hasattr(self, header):
                    value = getattr(self, header)
            except SacHeaderUndefined:
                value = None

            # convert enumerated header to integer if it is not None
            if header_type == "i" and value is not None:
                value = SacEnum[value].value

            # set None to -12345
            if value is None:
                value = header_undefined

            # Encode strings to bytes
            if isinstance(value, str):
                value = value.encode()

            # write to file
            file_handle.seek(start)
            file_handle.write(struct.pack(header_format, value))

        # write data (if npts > 0)
        data_1_start = 632
        data_1_end = data_1_start + self.npts * 4
        file_handle.truncate(data_1_start)
        if self.npts > 0:
            file_handle.seek(data_1_start)
            for x in self.data:
                file_handle.write(struct.pack("f", x))

        if self.nvhdr == 7:
            for footer, footer_metadata in SAC_FOOTERS.items():
                undefined = -12345.0
                start = footer_metadata.start + data_1_end
                value = None
                try:
                    if hasattr(self, footer):
                        value = getattr(self, footer)
                except SacHeaderUndefined:
                    value = None

                # set None to -12345
                if value is None:
                    value = undefined

                # write to file
                file_handle.seek(start)
                file_handle.write(struct.pack("d", value))