From 1a202faed6189089d1a0500f4d2af90878bd72bd Mon Sep 17 00:00:00 2001 From: he xingchen Date: Mon, 24 Aug 2026 12:53:13 +0800 Subject: [PATCH 01/18] add base parameters and tests for sac module --- doc/api/index.rst | 1 + pygmt/figure.py | 2 + pygmt/src/sac.py | 81 ++++++++++++++++++ pygmt/tests/baseline/test_sac.png | 3 + .../baseline/test_sac_multiple_traces.png | 3 + pygmt/tests/data/seis.sac | Bin 0 -> 4632 bytes pygmt/tests/test_sac.py | 54 ++++++++++++ 7 files changed, 144 insertions(+) create mode 100644 pygmt/src/sac.py create mode 100644 pygmt/tests/baseline/test_sac.png create mode 100644 pygmt/tests/baseline/test_sac_multiple_traces.png create mode 100644 pygmt/tests/data/seis.sac create mode 100644 pygmt/tests/test_sac.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 75dedbf737d..197362503c7 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -55,6 +55,7 @@ Plotting tabular data Figure.plot Figure.plot3d Figure.rose + Figure.sac Figure.ternary Figure.velo Figure.wiggle diff --git a/pygmt/figure.py b/pygmt/figure.py index 34c345bb707..598157cd019 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -33,6 +33,7 @@ from pygmt.src.psconvert import psconvert as _psconvert from pygmt.src.pygmtlogo import pygmtlogo as _pygmtlogo from pygmt.src.rose import rose as _rose +from pygmt.src.sac import sac as _sac from pygmt.src.scalebar import scalebar as _scalebar from pygmt.src.shift_origin import shift_origin as _shift_origin from pygmt.src.solar import solar as _solar @@ -469,6 +470,7 @@ def _repr_html_(self) -> str: psconvert = _psconvert pygmtlogo = _pygmtlogo rose = _rose + sac = _sac scalebar = _scalebar set_panel = _set_panel shift_origin = _shift_origin diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py new file mode 100644 index 00000000000..d3147212793 --- /dev/null +++ b/pygmt/src/sac.py @@ -0,0 +1,81 @@ +""" +sac - Plot seismograms in SAC format. +""" + +from collections.abc import Sequence +from typing import Literal + +from pygmt._typing import PathLike +from pygmt.alias import Alias, AliasSystem +from pygmt.clib import Session +from pygmt.helpers import build_arg_list, fmt_docstring +from pygmt.params import Axis, Frame + + +@fmt_docstring +def sac( + self, + spec: PathLike | str | Sequence[PathLike | str], + pen: str | None = None, + projection: str | None = None, + region: Sequence[float | str] | str | None = None, + frame: Frame | Axis | Literal["none"] | str | Sequence[str] | bool = False, + verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] + | bool = False, + panel: int | Sequence[int] | bool = False, + perspective: float | Sequence[float] | str | bool = False, + transparency: float | None = None, + **kwargs, +): + r""" + Plot seismograms in SAC format. + + Reads SAC waveform files and plots seismic traces. Only evenly spaced SAC + data is supported. + + Full GMT docs at :gmt-docs:`supplements/seis/sac.html`. + + **Aliases:** + + .. hlist:: + :columns: 3 + + - B = frame + - J = projection + - R = region + - V = verbose + - W = pen + - c = panel + - p = perspective + - t = transparency + + Parameters + ---------- + spec : str, pathlib.Path, or a sequence of them + The SAC waveform file(s) to plot. + pen : str + Set pen attributes for all traces [Default is ``"0.25p,black,solid"``]. + $projection + $region + $frame + $verbose + $panel + $perspective + $transparency + """ + aliasdict = AliasSystem( + W=Alias(pen, name="pen"), + ).add_common( + B=frame, + J=projection, + R=region, + V=verbose, + c=panel, + p=perspective, + t=transparency, + ) + aliasdict.merge(kwargs) + + self._activate_figure() + with Session() as lib: + lib.call_module(module="sac", args=build_arg_list(aliasdict, infile=spec)) diff --git a/pygmt/tests/baseline/test_sac.png b/pygmt/tests/baseline/test_sac.png new file mode 100644 index 00000000000..bca144a6e15 --- /dev/null +++ b/pygmt/tests/baseline/test_sac.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14e0cd2cb6c55008a3cf76d68ee86ef9ad1e361752ac2673fab5cc17f21e3fd4 +size 24619 diff --git a/pygmt/tests/baseline/test_sac_multiple_traces.png b/pygmt/tests/baseline/test_sac_multiple_traces.png new file mode 100644 index 00000000000..8425503c4aa --- /dev/null +++ b/pygmt/tests/baseline/test_sac_multiple_traces.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9784469c87512a1713676e493689988753b63f8fee5287baea673aa87f08e7c0 +size 19847 diff --git a/pygmt/tests/data/seis.sac b/pygmt/tests/data/seis.sac new file mode 100644 index 0000000000000000000000000000000000000000..2ed16d049f8d01b75a1bdaede583c2692eecaa3b GIT binary patch literal 4632 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.7.0): Remove the xfail marker when the minimum GMT version is 6.7.0. +XFAIL_GMT_LE_6_6 = pytest.mark.xfail( + condition=Version(__gmt_version__) <= Version("6.6.0"), + reason="The sac module was added in GMT 6.7.0.", +) + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac(): + """ + Plot a single SAC waveform on a linear time plot. + """ + fig = Figure() + fig.sac( + spec=SAC_DATA, + region=[9, 20, -2, 2], + projection="X15c/5c", + frame=["xaf+lTime (s)", "yaf+lAmplitude", "WSen"], + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_multiple_traces(): + """ + Plot multiple SAC waveforms on a trace number profile. + """ + fig = Figure() + fig.sac( + spec=[SAC_DATA, SAC_DATA], + # Plot two traces on a trace number profile, i.e., the y positions of the + # two traces are 0 and 1, respectively. The data amplitude is ~±1.6, so + # the region is set with some margins. + region=[9, 20, -0.5, 3], + projection="X15c/5c", + frame=True, + ) + return fig From f4eb9e79224111e4c6ba40ac469c12115f0b3469 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 24 Aug 2026 13:31:21 +0800 Subject: [PATCH 02/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index d3147212793..0d1345bbf7d 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -15,7 +15,7 @@ @fmt_docstring def sac( self, - spec: PathLike | str | Sequence[PathLike | str], + data: PathLike | str | Sequence[PathLike | str], pen: str | None = None, projection: str | None = None, region: Sequence[float | str] | str | None = None, From 0a9d38d83853449ef4f2097897396c658056b374 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 24 Aug 2026 13:31:27 +0800 Subject: [PATCH 03/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 0d1345bbf7d..2e9563b7580 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -27,7 +27,7 @@ def sac( transparency: float | None = None, **kwargs, ): - r""" + """ Plot seismograms in SAC format. Reads SAC waveform files and plots seismic traces. Only evenly spaced SAC From 4b8b429e35ed95a9685b3016e16e5736197ca0aa Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 24 Aug 2026 13:31:33 +0800 Subject: [PATCH 04/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 2e9563b7580..1f0dbe843a3 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -51,7 +51,7 @@ def sac( Parameters ---------- - spec : str, pathlib.Path, or a sequence of them + data The SAC waveform file(s) to plot. pen : str Set pen attributes for all traces [Default is ``"0.25p,black,solid"``]. From 55df660d8093fe1e7122ab6e5ad631d53dfdde12 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 24 Aug 2026 13:31:39 +0800 Subject: [PATCH 05/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 1f0dbe843a3..bacc0e5b130 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -53,7 +53,7 @@ def sac( ---------- data The SAC waveform file(s) to plot. - pen : str + pen Set pen attributes for all traces [Default is ``"0.25p,black,solid"``]. $projection $region From 05814c583764d59530e65b5ec72aca89038a6996 Mon Sep 17 00:00:00 2001 From: he xingchen Date: Mon, 24 Aug 2026 13:39:44 +0800 Subject: [PATCH 06/18] replace spec with data --- pygmt/src/sac.py | 2 +- pygmt/tests/test_sac.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index bacc0e5b130..7a01556d235 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -78,4 +78,4 @@ def sac( self._activate_figure() with Session() as lib: - lib.call_module(module="sac", args=build_arg_list(aliasdict, infile=spec)) + lib.call_module(module="sac", args=build_arg_list(aliasdict, infile=data)) diff --git a/pygmt/tests/test_sac.py b/pygmt/tests/test_sac.py index e1a49bf92f1..5816fc234e0 100644 --- a/pygmt/tests/test_sac.py +++ b/pygmt/tests/test_sac.py @@ -26,7 +26,7 @@ def test_sac(): """ fig = Figure() fig.sac( - spec=SAC_DATA, + data=SAC_DATA, region=[9, 20, -2, 2], projection="X15c/5c", frame=["xaf+lTime (s)", "yaf+lAmplitude", "WSen"], @@ -43,7 +43,7 @@ def test_sac_multiple_traces(): """ fig = Figure() fig.sac( - spec=[SAC_DATA, SAC_DATA], + data=[SAC_DATA, SAC_DATA], # Plot two traces on a trace number profile, i.e., the y positions of the # two traces are 0 and 1, respectively. The data amplitude is ~±1.6, so # the region is set with some margins. From a2b7889145a1d87ccca614da6c832d7e4b52a184 Mon Sep 17 00:00:00 2001 From: he xingchen Date: Mon, 24 Aug 2026 14:10:59 +0800 Subject: [PATCH 07/18] add C,D,G,M parameters --- pygmt/src/sac.py | 32 +++++++- .../baseline/test_sac_amplitude_scale.png | 3 + pygmt/tests/baseline/test_sac_fill.png | 3 + pygmt/tests/baseline/test_sac_offset.png | 3 + pygmt/tests/baseline/test_sac_time_window.png | 3 + pygmt/tests/test_sac.py | 75 +++++++++++++++++++ 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 pygmt/tests/baseline/test_sac_amplitude_scale.png create mode 100644 pygmt/tests/baseline/test_sac_fill.png create mode 100644 pygmt/tests/baseline/test_sac_offset.png create mode 100644 pygmt/tests/baseline/test_sac_time_window.png diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 7a01556d235..dc5e7547200 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -15,8 +15,12 @@ @fmt_docstring def sac( self, - data: PathLike | str | Sequence[PathLike | str], + data: PathLike | Sequence[PathLike], pen: str | None = None, + time_window: Sequence[float] | None = None, + offset: float | Sequence[float] | None = None, + fill: str | None = None, + amplitude_scale: float | str | None = None, projection: str | None = None, region: Sequence[float | str] | str | None = None, frame: Frame | Axis | Literal["none"] | str | Sequence[str] | bool = False, @@ -41,7 +45,11 @@ def sac( :columns: 3 - B = frame + - C = time_window + - D = offset + - G = fill - J = projection + - M = amplitude_scale - R = region - V = verbose - W = pen @@ -55,6 +63,24 @@ def sac( The SAC waveform file(s) to plot. pen Set pen attributes for all traces [Default is ``"0.25p,black,solid"``]. + time_window + Read and plot seismograms in the time window between *t0* and *t1* only, + where *t0* and *t1* are relative to the reference time (see + ``time_reference``). If no reference time is set, the reference time in + the SAC header is used. + offset + Offset the seismogram positions by the given amounts *dx*[/ *dy*] + [Default is no offset]. If *dy* is not given, it is set equal to *dx*. + fill + Paint the positive or negative portion of the traces. Use ``p``/``n`` to + paint the positive/negative portion [Default paints the positive + portion], ``+g`` *fill* to set the fill color [Default is ``"black"``], + ``+z`` *zero* to define the zero line, and ``+t`` *t0*/*t1* to paint + a time window only. + amplitude_scale + Set the vertical scaling of the traces. If a unit is appended, all + traces are scaled to the given height on the map; otherwise all traces + are multiplied by the value. $projection $region $frame @@ -64,6 +90,10 @@ def sac( $transparency """ aliasdict = AliasSystem( + C=Alias(time_window, name="time_window", sep="/", size=2), + D=Alias(offset, name="offset", sep="/", size=(1, 2)), + G=Alias(fill, name="fill"), + M=Alias(amplitude_scale, name="amplitude_scale"), W=Alias(pen, name="pen"), ).add_common( B=frame, diff --git a/pygmt/tests/baseline/test_sac_amplitude_scale.png b/pygmt/tests/baseline/test_sac_amplitude_scale.png new file mode 100644 index 00000000000..52c797fddfa --- /dev/null +++ b/pygmt/tests/baseline/test_sac_amplitude_scale.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae374193202586cf79c3ec1bbb1bab0c18810aa8149d6a0a394275ef701e8e0b +size 17418 diff --git a/pygmt/tests/baseline/test_sac_fill.png b/pygmt/tests/baseline/test_sac_fill.png new file mode 100644 index 00000000000..bc6a2119143 --- /dev/null +++ b/pygmt/tests/baseline/test_sac_fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d32a7ec7e5be1f5eefe4232b764f706297f04914a26301790beb55758290954 +size 21182 diff --git a/pygmt/tests/baseline/test_sac_offset.png b/pygmt/tests/baseline/test_sac_offset.png new file mode 100644 index 00000000000..62f311af33a --- /dev/null +++ b/pygmt/tests/baseline/test_sac_offset.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71322522d7394b7989fa193e0aa853f0568aec95a0303d2c48a4ef9c62d58c5c +size 19019 diff --git a/pygmt/tests/baseline/test_sac_time_window.png b/pygmt/tests/baseline/test_sac_time_window.png new file mode 100644 index 00000000000..87d517d8a24 --- /dev/null +++ b/pygmt/tests/baseline/test_sac_time_window.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27d8b96799814a9cd7897443059344b5144071107ab05bc178e4cda17d1b2bf6 +size 19587 diff --git a/pygmt/tests/test_sac.py b/pygmt/tests/test_sac.py index 5816fc234e0..8ce0d7ac1d3 100644 --- a/pygmt/tests/test_sac.py +++ b/pygmt/tests/test_sac.py @@ -52,3 +52,78 @@ def test_sac_multiple_traces(): frame=True, ) return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_time_window(): + """ + Plot a SAC waveform in a given time window. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + time_window=[10, 18], + region=[9, 20, -2, 2], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_offset(): + """ + Plot SAC waveforms with an offset. + """ + fig = Figure() + fig.sac( + data=[SAC_DATA, SAC_DATA], + # Offset the two traces (at y positions 0 and 1) by dy=1, so the y range + # of the traces becomes 1 and 2, respectively. The data amplitude is + # ~±1.6, so the region is set with some margins. + offset=[0, 1], + region=[9, 20, -1, 4], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_fill(): + """ + Plot a SAC waveform with the positive portion filled. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + fill="+ggray", + region=[9, 20, -2, 2], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_amplitude_scale(): + """ + Plot a SAC waveform with vertical scaling. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + amplitude_scale="1.5c", + region=[9, 20, -4, 4], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig From f7de8fd9004a4fbc80ccd8d3aa8d7bf543554865 Mon Sep 17 00:00:00 2001 From: he xingchen Date: Mon, 24 Aug 2026 17:18:21 +0800 Subject: [PATCH 08/18] add E,F,Q,S,T parameters --- pygmt/src/sac.py | 117 ++++++++++++++-- .../test_sac_fill_positive_negative.png | 3 + pygmt/tests/baseline/test_sac_preprocess.png | 3 + pygmt/tests/baseline/test_sac_profile.png | 3 + .../tests/baseline/test_sac_time_options.png | 3 + pygmt/tests/baseline/test_sac_time_scale.png | 3 + .../baseline/test_sac_time_window_bare.png | 3 + pygmt/tests/baseline/test_sac_vertical.png | 3 + pygmt/tests/test_sac.py | 131 ++++++++++++++++++ 9 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 pygmt/tests/baseline/test_sac_fill_positive_negative.png create mode 100644 pygmt/tests/baseline/test_sac_preprocess.png create mode 100644 pygmt/tests/baseline/test_sac_profile.png create mode 100644 pygmt/tests/baseline/test_sac_time_options.png create mode 100644 pygmt/tests/baseline/test_sac_time_scale.png create mode 100644 pygmt/tests/baseline/test_sac_time_window_bare.png create mode 100644 pygmt/tests/baseline/test_sac_vertical.png diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index dc5e7547200..12f7e313ea8 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -17,10 +17,33 @@ def sac( self, data: PathLike | Sequence[PathLike], pen: str | None = None, - time_window: Sequence[float] | None = None, + time_window: Sequence[float] | bool = False, offset: float | Sequence[float] | None = None, - fill: str | None = None, - amplitude_scale: float | str | None = None, + profile: str | None = None, + preprocess: str | None = None, + fill: str | Sequence[str] | bool = False, + amplitude_scale: float | str | Sequence[float | str] | None = None, + vertical: bool = False, + time_scale: float | str | None = None, + reduction_velocity: float | None = None, + time_shift: float | None = None, + time_reference: Literal[ + "b", + "e", + "o", + "a", + "t0", + "t1", + "t2", + "t3", + "t4", + "t5", + "t6", + "t7", + "t8", + "t9", + ] + | None = None, projection: str | None = None, region: Sequence[float | str] | str | None = None, frame: Frame | Axis | Literal["none"] | str | Sequence[str] | bool = False, @@ -47,10 +70,15 @@ def sac( - B = frame - C = time_window - D = offset + - E = profile + - F = preprocess - G = fill - J = projection - M = amplitude_scale + - Q = vertical - R = region + - S = time_scale + - T = **+r**: reduction_velocity, **+s**: time_shift, **+t**: time_reference - V = verbose - W = pen - c = panel @@ -67,20 +95,60 @@ def sac( Read and plot seismograms in the time window between *t0* and *t1* only, where *t0* and *t1* are relative to the reference time (see ``time_reference``). If no reference time is set, the reference time in - the SAC header is used. + the SAC header is used. Set to ``True`` to determine *t0*/*t1* from the + *xmin*/*xmax* of the ``region`` parameter. offset Offset the seismogram positions by the given amounts *dx*[/ *dy*] [Default is no offset]. If *dy* is not given, it is set equal to *dx*. + profile + Choose the profile type, i.e., the type of the y axis. Use ``a`` for + azimuth, ``b`` for back-azimuth, ``k`` for epicentral distance in km, + ``d`` for epicentral distance in degrees, ``n`` for trace number (the + first trace is numbered *n*, e.g., ``n0``), and ``u`` for user-defined + profile (the y positions are determined by the SAC header variable + ``usern``, e.g., ``u0``). + preprocess + Preprocess the data before plotting. Use ``i`` for integral, ``q`` for + square, and ``r`` for removing the mean value. The letters can repeat + multiple times, and the order controls the processing order, e.g., + ``"rii"`` converts acceleration to displacement. fill Paint the positive or negative portion of the traces. Use ``p``/``n`` to paint the positive/negative portion [Default paints the positive portion], ``+g`` *fill* to set the fill color [Default is ``"black"``], - ``+z`` *zero* to define the zero line, and ``+t`` *t0*/*t1* to paint - a time window only. + ``+z`` *zero* to define the zero line, and ``+t`` *t0*/*t1* to paint a + time window only. Can be repeated to paint the positive and negative + portions separately, e.g., ``["p+gblack", "n+gred"]``. Set to ``True`` + to paint the positive portion with the default fill. amplitude_scale - Set the vertical scaling of the traces. If a unit is appended, all - traces are scaled to the given height on the map; otherwise all traces - are multiplied by the value. + Set the vertical scaling of the traces. + + - If only *size* is given (optionally with a unit), all traces are + scaled to the given height on the map. + - If *size*/*alpha* is given with a negative *alpha*, all traces use + the same scaling factor determined by the first trace, which is + scaled to *size*. + - If *alpha* is 0, all traces are multiplied by *size* [no unit is + allowed]. + - If *alpha* is positive, all traces are multiplied by *size* times the + epicentral distance (in km) raised to the power *alpha*. + vertical + Plot traces vertically, i.e., the y axis is time and the x axis is + amplitude. + time_scale + Set the time scale in seconds per unit while plotting on geographic + plots. Append a unit (``c``, ``i``, or ``p``); if omitted, the unit is + controlled by :gmt-term:`PROJ_LENGTH_UNIT`. Use the ``i`` prefix to give + the reciprocal scale, i.e., unit per second. + reduction_velocity + Set the reduction velocity in km/s. The trace times are reduced by + *distance*/*reduction_velocity*, where *distance* is the epicentral + distance in the SAC header. + time_shift + Shift all traces by the given number of seconds. + time_reference + Align all traces along the given time mark. Choose from ``"b"``, + ``"e"``, ``"o"``, ``"a"``, or ``"t0"`` to ``"t9"``. $projection $region $frame @@ -92,8 +160,37 @@ def sac( aliasdict = AliasSystem( C=Alias(time_window, name="time_window", sep="/", size=2), D=Alias(offset, name="offset", sep="/", size=(1, 2)), + E=Alias(profile, name="profile"), + F=Alias(preprocess, name="preprocess"), G=Alias(fill, name="fill"), - M=Alias(amplitude_scale, name="amplitude_scale"), + M=Alias(amplitude_scale, name="amplitude_scale", sep="/", size=(1, 2)), + Q=Alias(vertical, name="vertical"), + S=Alias(time_scale, name="time_scale"), + T=[ + Alias(reduction_velocity, name="reduction_velocity", prefix="+r"), + Alias(time_shift, name="time_shift", prefix="+s"), + Alias( + time_reference, + name="time_reference", + prefix="+t", + mapping={ + "b": -5, + "e": -4, + "o": -3, + "a": -2, + "t0": 0, + "t1": 1, + "t2": 2, + "t3": 3, + "t4": 4, + "t5": 5, + "t6": 6, + "t7": 7, + "t8": 8, + "t9": 9, + }, + ), + ], W=Alias(pen, name="pen"), ).add_common( B=frame, diff --git a/pygmt/tests/baseline/test_sac_fill_positive_negative.png b/pygmt/tests/baseline/test_sac_fill_positive_negative.png new file mode 100644 index 00000000000..3927e01885c --- /dev/null +++ b/pygmt/tests/baseline/test_sac_fill_positive_negative.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9969d33d01bb7f0f54483170ec4cceac3f4f9ede10f1b07355b9c9fa26ccd281 +size 17104 diff --git a/pygmt/tests/baseline/test_sac_preprocess.png b/pygmt/tests/baseline/test_sac_preprocess.png new file mode 100644 index 00000000000..d3f7929e0b0 --- /dev/null +++ b/pygmt/tests/baseline/test_sac_preprocess.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47cf61a63fbc8a8be105096a3f7ba1ce4538cd8c67873fec71c4fa4c014e4e6a +size 20430 diff --git a/pygmt/tests/baseline/test_sac_profile.png b/pygmt/tests/baseline/test_sac_profile.png new file mode 100644 index 00000000000..39a40da1889 --- /dev/null +++ b/pygmt/tests/baseline/test_sac_profile.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6574526ec089585cf4d8757faacfbed0aefadbd81bfcfb052d3304424f3d6b9 +size 20796 diff --git a/pygmt/tests/baseline/test_sac_time_options.png b/pygmt/tests/baseline/test_sac_time_options.png new file mode 100644 index 00000000000..d895a7dc14b --- /dev/null +++ b/pygmt/tests/baseline/test_sac_time_options.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a09482de41d2c8e4c7ed9f90578dd96214ff2e901d5e0b11a7b0f57bcf35eaa +size 19292 diff --git a/pygmt/tests/baseline/test_sac_time_scale.png b/pygmt/tests/baseline/test_sac_time_scale.png new file mode 100644 index 00000000000..b265287e0e1 --- /dev/null +++ b/pygmt/tests/baseline/test_sac_time_scale.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0a66bd8a8ccaf4c51303e2ea3e799275bd7a454acdb1ea9874f8a53ace7a949 +size 11664 diff --git a/pygmt/tests/baseline/test_sac_time_window_bare.png b/pygmt/tests/baseline/test_sac_time_window_bare.png new file mode 100644 index 00000000000..3f030c58b3c --- /dev/null +++ b/pygmt/tests/baseline/test_sac_time_window_bare.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5940df97c2f6e1d30c0ed9fb6ce35ca4d5e8261627a2c2d51df0f6ce968a5e8 +size 20820 diff --git a/pygmt/tests/baseline/test_sac_vertical.png b/pygmt/tests/baseline/test_sac_vertical.png new file mode 100644 index 00000000000..760dd9d2327 --- /dev/null +++ b/pygmt/tests/baseline/test_sac_vertical.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a4ecd30d97cbc23a0fee4ea84ba34b4b7ca0d713c9653cf2b308d673e397ef +size 24242 diff --git a/pygmt/tests/test_sac.py b/pygmt/tests/test_sac.py index 8ce0d7ac1d3..7980851a140 100644 --- a/pygmt/tests/test_sac.py +++ b/pygmt/tests/test_sac.py @@ -72,6 +72,42 @@ def test_sac_time_window(): return fig +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_time_window_bare(): + """ + Plot a SAC waveform in the time window determined by the region. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + time_window=True, + region=[9, 20, -2, 2], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_fill_positive_negative(): + """ + Plot a SAC waveform with the positive and negative portions filled. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + fill=["p+gblack", "n+gred"], + preprocess="r", + region=[9, 20, -2, 2], + projection="X15c/5c", + frame=True, + ) + return fig + + @XFAIL_GMT_LE_6_6 @pytest.mark.mpl_image_compare def test_sac_offset(): @@ -127,3 +163,98 @@ def test_sac_amplitude_scale(): pen="0.5p,red", ) return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_profile(): + """ + Plot a SAC waveform on a trace number profile. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + profile="n1", + region=[9, 20, -1, 3], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_preprocess(): + """ + Plot a SAC waveform with the mean removed. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + preprocess="r", + region=[9, 20, -2, 2], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_vertical(): + """ + Plot a SAC waveform vertically. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + vertical=True, + region=[-2, 2, 9, 20], + projection="X5c/15c", + frame=True, + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_time_scale(): + """ + Plot a SAC waveform on a geographic map with a time scale. + """ + fig = Figure() + # The station is at (stlo, stla) = (-120, 48), so the region is extended + # westward to keep the waveform from sticking to the map boundary. + fig.basemap(region=[-135, -30, 35, 65], projection="M10c", frame=True) + fig.sac( + data=SAC_DATA, + amplitude_scale="1i", + # Use the reciprocal time scale, i.e., 0.5 cm per second, so that the + # 10-s waveform occupies 5 cm on the map. + time_scale="i0.5c", + pen="0.5p,red", + ) + return fig + + +@XFAIL_GMT_LE_6_6 +@pytest.mark.mpl_image_compare +def test_sac_time_options(): + """ + Plot a SAC waveform with time alignment, shift, and reduction velocity. + """ + fig = Figure() + fig.sac( + data=SAC_DATA, + reduction_velocity=8, + time_shift=2, + time_reference="o", + region=[5, 18, -2, 2], + projection="X15c/5c", + frame=True, + pen="0.5p,red", + ) + return fig From 9a53a9dfb60c5a1d19f22e9c6439015c56870b2d Mon Sep 17 00:00:00 2001 From: He Xingchen Date: Sat, 5 Sep 2026 10:00:45 +0800 Subject: [PATCH 09/18] Make Figure.sac parameters more Pythonic --- pygmt/src/sac.py | 111 ++++++++++++++++++++++++++++++++-------- pygmt/tests/test_sac.py | 7 +-- 2 files changed, 95 insertions(+), 23 deletions(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 12f7e313ea8..7a3f887f0e3 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -8,6 +8,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session +from pygmt.exceptions import GMTValueError from pygmt.helpers import build_arg_list, fmt_docstring from pygmt.params import Axis, Frame @@ -19,9 +20,30 @@ def sac( pen: str | None = None, time_window: Sequence[float] | bool = False, offset: float | Sequence[float] | None = None, - profile: str | None = None, + profile: Literal[ + "azimuth", + "back_azimuth", + "distance_in_km", + "distance_in_degree", + "trace_number", + "user0", + "user1", + "user2", + "user3", + "user4", + "user5", + "user6", + "user7", + "user8", + "user9", + ] + | None = None, + trace_number: int | None = None, preprocess: str | None = None, - fill: str | Sequence[str] | bool = False, + positive_fill: str | None = None, + negative_fill: str | None = None, + fill_zero: float | None = None, + fill_time_window: Sequence[float] | None = None, amplitude_scale: float | str | Sequence[float | str] | None = None, vertical: bool = False, time_scale: float | str | None = None, @@ -70,9 +92,9 @@ def sac( - B = frame - C = time_window - D = offset - - E = profile + - E = profile, trace_number - F = preprocess - - G = fill + - G = positive_fill, negative_fill, fill_zero, fill_time_window - J = projection - M = amplitude_scale - Q = vertical @@ -101,25 +123,28 @@ def sac( Offset the seismogram positions by the given amounts *dx*[/ *dy*] [Default is no offset]. If *dy* is not given, it is set equal to *dx*. profile - Choose the profile type, i.e., the type of the y axis. Use ``a`` for - azimuth, ``b`` for back-azimuth, ``k`` for epicentral distance in km, - ``d`` for epicentral distance in degrees, ``n`` for trace number (the - first trace is numbered *n*, e.g., ``n0``), and ``u`` for user-defined - profile (the y positions are determined by the SAC header variable - ``usern``, e.g., ``u0``). + Choose the profile type, i.e., the type of the y axis. Choose from + ``"azimuth"``, ``"back_azimuth"``, ``"distance_in_km"``, + ``"distance_in_degree"``, ``"trace_number"``, or ``"user0"`` to + ``"user9"``. User-defined profiles use the corresponding SAC header + variable, e.g., ``"user0"`` uses ``user0``. + trace_number + Set the number of the first trace for a trace-number profile. If not + specified, the first trace is numbered 0. preprocess Preprocess the data before plotting. Use ``i`` for integral, ``q`` for square, and ``r`` for removing the mean value. The letters can repeat multiple times, and the order controls the processing order, e.g., ``"rii"`` converts acceleration to displacement. - fill - Paint the positive or negative portion of the traces. Use ``p``/``n`` to - paint the positive/negative portion [Default paints the positive - portion], ``+g`` *fill* to set the fill color [Default is ``"black"``], - ``+z`` *zero* to define the zero line, and ``+t`` *t0*/*t1* to paint a - time window only. Can be repeated to paint the positive and negative - portions separately, e.g., ``["p+gblack", "n+gred"]``. Set to ``True`` - to paint the positive portion with the default fill. + positive_fill + Set the color or pattern for filling the positive portion of the traces. + negative_fill + Set the color or pattern for filling the negative portion of the traces. + fill_zero + Set the zero line for ``positive_fill`` and ``negative_fill``. + fill_time_window + Set the time window *t0*/*t1* for ``positive_fill`` and + ``negative_fill``. amplitude_scale Set the vertical scaling of the traces. @@ -157,12 +182,58 @@ def sac( $perspective $transparency """ + if profile is not None and trace_number is not None: + raise GMTValueError( + [profile, trace_number], + description="parameters 'profile' and 'trace_number'", + reason="Only one can be specified.", + ) + + profile_mapping = { + "azimuth": "a", + "back_azimuth": "b", + "distance_in_km": "k", + "distance_in_degree": "d", + "trace_number": "n", + **{f"user{number}": f"u{number}" for number in range(10)}, + } + profile_alias = ( + Alias(trace_number, name="trace_number", prefix="n") + if trace_number is not None + else Alias(profile, name="profile", mapping=profile_mapping) + ) + + fill_modifiers = "".join( + modifier + for modifier in ( + Alias(fill_zero, name="fill_zero", prefix="+z")._value, + Alias( + fill_time_window, + name="fill_time_window", + prefix="+t", + sep="/", + size=2, + )._value, + ) + if modifier is not None + ) + fill_options = [ + f"p+g{positive_fill}{fill_modifiers}" if positive_fill is not None else None, + f"n+g{negative_fill}{fill_modifiers}" if negative_fill is not None else None, + ] + if fill_modifiers and not any(fill_options): + raise GMTValueError( + [fill_zero, fill_time_window], + description="parameters 'fill_zero' and 'fill_time_window'", + reason="At least one of 'positive_fill' or 'negative_fill' must be specified.", + ) + aliasdict = AliasSystem( C=Alias(time_window, name="time_window", sep="/", size=2), D=Alias(offset, name="offset", sep="/", size=(1, 2)), - E=Alias(profile, name="profile"), + E=profile_alias, F=Alias(preprocess, name="preprocess"), - G=Alias(fill, name="fill"), + G=Alias([option for option in fill_options if option is not None], name="fill"), M=Alias(amplitude_scale, name="amplitude_scale", sep="/", size=(1, 2)), Q=Alias(vertical, name="vertical"), S=Alias(time_scale, name="time_scale"), diff --git a/pygmt/tests/test_sac.py b/pygmt/tests/test_sac.py index 7980851a140..5b146da23d5 100644 --- a/pygmt/tests/test_sac.py +++ b/pygmt/tests/test_sac.py @@ -99,7 +99,8 @@ def test_sac_fill_positive_negative(): fig = Figure() fig.sac( data=SAC_DATA, - fill=["p+gblack", "n+gred"], + positive_fill="black", + negative_fill="red", preprocess="r", region=[9, 20, -2, 2], projection="X15c/5c", @@ -138,7 +139,7 @@ def test_sac_fill(): fig = Figure() fig.sac( data=SAC_DATA, - fill="+ggray", + positive_fill="gray", region=[9, 20, -2, 2], projection="X15c/5c", frame=True, @@ -174,7 +175,7 @@ def test_sac_profile(): fig = Figure() fig.sac( data=SAC_DATA, - profile="n1", + trace_number=1, region=[9, 20, -1, 3], projection="X15c/5c", frame=True, From b9e1d59a517e76c3198eee721aff3ff70c06b8b8 Mon Sep 17 00:00:00 2001 From: He Xingchen Date: Sat, 5 Sep 2026 10:15:13 +0800 Subject: [PATCH 10/18] style check --- pygmt/src/sac.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 7a3f887f0e3..89fdc4882ab 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -203,20 +203,22 @@ def sac( else Alias(profile, name="profile", mapping=profile_mapping) ) - fill_modifiers = "".join( - modifier - for modifier in ( - Alias(fill_zero, name="fill_zero", prefix="+z")._value, - Alias( - fill_time_window, - name="fill_time_window", - prefix="+t", - sep="/", - size=2, - )._value, - ) - if modifier is not None - ) + fill_modifier_values: list[str] = [] + for modifier in ( + Alias(fill_zero, name="fill_zero", prefix="+z")._value, + Alias( + fill_time_window, + name="fill_time_window", + prefix="+t", + sep="/", + size=2, + )._value, + ): + if modifier is not None: + if not isinstance(modifier, str): + raise GMTValueError(modifier, description="fill modifier") + fill_modifier_values.append(modifier) + fill_modifiers = "".join(fill_modifier_values) fill_options = [ f"p+g{positive_fill}{fill_modifiers}" if positive_fill is not None else None, f"n+g{negative_fill}{fill_modifiers}" if negative_fill is not None else None, From 119eb75301aa9e8f82500d269bed4af4888055e3 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 7 Sep 2026 13:51:27 +0800 Subject: [PATCH 11/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 89fdc4882ab..2afbf579144 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -114,10 +114,11 @@ def sac( pen Set pen attributes for all traces [Default is ``"0.25p,black,solid"``]. time_window + (*t0*, *t1*). Read and plot seismograms in the time window between *t0* and *t1* only, where *t0* and *t1* are relative to the reference time (see ``time_reference``). If no reference time is set, the reference time in - the SAC header is used. Set to ``True`` to determine *t0*/*t1* from the + the SAC header is used. Set to ``True`` to determine *t0* and *t1* from the *xmin*/*xmax* of the ``region`` parameter. offset Offset the seismogram positions by the given amounts *dx*[/ *dy*] From 68bc581e841c8c4bd9ad6e8eff09463806fcfe80 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 7 Sep 2026 14:25:46 +0800 Subject: [PATCH 12/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 2afbf579144..a02b8a6edb5 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -38,7 +38,7 @@ def sac( "user9", ] | None = None, - trace_number: int | None = None, + trace_number: int = 0, preprocess: str | None = None, positive_fill: str | None = None, negative_fill: str | None = None, From 51a905d56ce04af25ba383624c6becb1d623641f Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 7 Sep 2026 14:26:00 +0800 Subject: [PATCH 13/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index a02b8a6edb5..e2532628ad2 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -195,7 +195,7 @@ def sac( "back_azimuth": "b", "distance_in_km": "k", "distance_in_degree": "d", - "trace_number": "n", + "trace_number": f"n{trace_number}", **{f"user{number}": f"u{number}" for number in range(10)}, } profile_alias = ( From 6cf82ce89fab86593164aba0a86f507b7831dd22 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 7 Sep 2026 14:26:13 +0800 Subject: [PATCH 14/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index e2532628ad2..147a59aa09c 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -198,11 +198,6 @@ def sac( "trace_number": f"n{trace_number}", **{f"user{number}": f"u{number}" for number in range(10)}, } - profile_alias = ( - Alias(trace_number, name="trace_number", prefix="n") - if trace_number is not None - else Alias(profile, name="profile", mapping=profile_mapping) - ) fill_modifier_values: list[str] = [] for modifier in ( From dff982e0d1bfd073e0df2c56332d06ad1d7d2401 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 7 Sep 2026 14:31:21 +0800 Subject: [PATCH 15/18] Update pygmt/src/sac.py Co-authored-by: Dongdong Tian --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 147a59aa09c..b2b7702bac7 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -229,7 +229,7 @@ def sac( aliasdict = AliasSystem( C=Alias(time_window, name="time_window", sep="/", size=2), D=Alias(offset, name="offset", sep="/", size=(1, 2)), - E=profile_alias, + E=Alias(profile, name="profile", mapping=profile_mapping, F=Alias(preprocess, name="preprocess"), G=Alias([option for option in fill_options if option is not None], name="fill"), M=Alias(amplitude_scale, name="amplitude_scale", sep="/", size=(1, 2)), From 2ce8214606723f0e11516a48a6b398864bab5070 Mon Sep 17 00:00:00 2001 From: He Xingchen Date: Mon, 7 Sep 2026 14:43:27 +0800 Subject: [PATCH 16/18] make format --- pygmt/src/sac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index b2b7702bac7..79f8d232307 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -229,7 +229,7 @@ def sac( aliasdict = AliasSystem( C=Alias(time_window, name="time_window", sep="/", size=2), D=Alias(offset, name="offset", sep="/", size=(1, 2)), - E=Alias(profile, name="profile", mapping=profile_mapping, + E=Alias(profile, name="profile", mapping=profile_mapping), F=Alias(preprocess, name="preprocess"), G=Alias([option for option in fill_options if option is not None], name="fill"), M=Alias(amplitude_scale, name="amplitude_scale", sep="/", size=(1, 2)), From e3d94a41787642f3a1a21304b68465148283e9ef Mon Sep 17 00:00:00 2001 From: He Xingchen Date: Mon, 7 Sep 2026 15:15:22 +0800 Subject: [PATCH 17/18] use trace name,defaut 0 --- pygmt/src/sac.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 79f8d232307..2113c65c62b 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -36,8 +36,7 @@ def sac( "user7", "user8", "user9", - ] - | None = None, + ] = "trace_number", trace_number: int = 0, preprocess: str | None = None, positive_fill: str | None = None, @@ -128,10 +127,11 @@ def sac( ``"azimuth"``, ``"back_azimuth"``, ``"distance_in_km"``, ``"distance_in_degree"``, ``"trace_number"``, or ``"user0"`` to ``"user9"``. User-defined profiles use the corresponding SAC header - variable, e.g., ``"user0"`` uses ``user0``. + variable, e.g., ``"user0"`` uses ``user0``. [Default is + ``"trace_number"``]. trace_number - Set the number of the first trace for a trace-number profile. If not - specified, the first trace is numbered 0. + Set the number of the first trace when ``profile="trace_number"``. + [Default is 0]. preprocess Preprocess the data before plotting. Use ``i`` for integral, ``q`` for square, and ``r`` for removing the mean value. The letters can repeat @@ -183,13 +183,6 @@ def sac( $perspective $transparency """ - if profile is not None and trace_number is not None: - raise GMTValueError( - [profile, trace_number], - description="parameters 'profile' and 'trace_number'", - reason="Only one can be specified.", - ) - profile_mapping = { "azimuth": "a", "back_azimuth": "b", From 3316d55ab6e858d65e7894bb585a97037c0a3962 Mon Sep 17 00:00:00 2001 From: He Xingchen Date: Mon, 7 Sep 2026 16:06:06 +0800 Subject: [PATCH 18/18] reback --- pygmt/src/sac.py | 6 +++--- pygmt/tests/test_sac.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pygmt/src/sac.py b/pygmt/src/sac.py index 2113c65c62b..5af12d96ae8 100644 --- a/pygmt/src/sac.py +++ b/pygmt/src/sac.py @@ -36,7 +36,8 @@ def sac( "user7", "user8", "user9", - ] = "trace_number", + ] + | None = None, trace_number: int = 0, preprocess: str | None = None, positive_fill: str | None = None, @@ -127,8 +128,7 @@ def sac( ``"azimuth"``, ``"back_azimuth"``, ``"distance_in_km"``, ``"distance_in_degree"``, ``"trace_number"``, or ``"user0"`` to ``"user9"``. User-defined profiles use the corresponding SAC header - variable, e.g., ``"user0"`` uses ``user0``. [Default is - ``"trace_number"``]. + variable, e.g., ``"user0"`` uses ``user0``. trace_number Set the number of the first trace when ``profile="trace_number"``. [Default is 0]. diff --git a/pygmt/tests/test_sac.py b/pygmt/tests/test_sac.py index 5b146da23d5..d9645fd2edc 100644 --- a/pygmt/tests/test_sac.py +++ b/pygmt/tests/test_sac.py @@ -175,6 +175,7 @@ def test_sac_profile(): fig = Figure() fig.sac( data=SAC_DATA, + profile="trace_number", trace_number=1, region=[9, 20, -1, 3], projection="X15c/5c",