Repository: obspy/obspy

Issue:
SACPZ format output is not in radians per second convention
### Avoid duplicates

- [X] I searched existing issues

### Bug Summary

Poles and zeros are expected to be in radians per second by the sac program, and likely other programs that read these files.  Furthermore, there is no annotation or indication in the format that would indicate any other units.

A simple conversion of StationXML, which allows the units to be either rad/s or Hertz, to "SACPZ" does not convert to rad/s.  An example of such a conversion using a StationXML response with a PZ (Laplace) response stage in Hertz is as follows:

[G_CAN__LHZ.xml.gz](https://github.com/obspy/obspy/files/12162807/G_CAN__LHZ.xml.gz)
```Python
import obspy
inv=obspy.read_inventory('G_CAN__LHZ.xml')
inv.write('G_CAN__LHZ.sacpz',format='SACPZ')
... UserWarning: More than one PolesZerosResponseStage encountered. Returning first one found.
```

Never mind the multiple PZ error, the initial set is what we need.

The first PZs are converted to "SACPZ" format, but with the PZs, A0 and CONSTANT in Hertz.  There is no indication of units.  Even if there were units noted, the sac program would not recognize them.  Using this "SACPZ" file with sac, and possibly other programs, will result in completely incorrect transform with the user having no indication that anything is wrong.

I suggest that when writing "SACPZ" format that the units are consistently in radians per second to avoid confusion and quiet data corruption.  A correct conversion from the test StationXML to SAC PZ (by [irisws-sacpz](https://service.iris.edu/irisws/sacpz/1)) to rad/s would look like:

```
* **********************************
* NETWORK   (KNETWK): G
* STATION    (KSTNM): CAN
* LOCATION   (KHOLE): 
* CHANNEL   (KCMPNM): LHZ
* CREATED           : 2023-07-25T14:40:53
* START             : 1989-06-02T00:00:00
* END               : 2006-12-10T02:00:00
* DESCRIPTION       : Canberra, Australia
* LATITUDE          : -35.318715
* LONGITUDE         : 148.996325
* ELEVATION         : 700.0
* DEPTH             : 0.0
* DIP               : 0.0
* AZIMUTH           : 0.0
* SAMPLE RATE       : 1.0
* INPUT UNIT        : M
* OUTPUT UNIT       : COUNTS
* INSTTYPE          : STRECKEISEN STS1
* INSTGAIN          : 2.252000e+03 (M/S)
* COMMENT           : 
* SENSITIVITY       : 1.844840e+09 (M/S)
* A0                : 3.959488e+03
* **********************************
ZEROS	3
	+0.000000e+00	+0.000000e+00	
	+0.000000e+00	+0.000000e+00	
	+0.000000e+00	+0.000000e+00	
POLES	4
	-1.233948e-02	+1.234319e-02	
	-1.233948e-02	-1.234319e-02	
	-3.917566e+01	+4.912339e+01	
	-3.917566e+01	-4.912339e+01	
CONSTANT	7.304622e+12
```

There does appear to be some code to convert to rad/s, but only when attaching responses to traces.  In that case the caller must know the units a priori to know if a conversion is needed.  This is the case even when attaching PZs from RESP, which is weird as the RESP specifies the units.







### Code to Reproduce

_No response_

### Error Traceback

_No response_

### ObsPy Version?

1.4.0

### Operating System?

macOS

### Python Version?

3.11.4

### Installation Method?

None


Discussion:
Sorry this got overlooked somehow.. nice catch and this should be an easy fix. I'm just confused right now with `A0` in the example you posted. The StationXML has `A0=100.295` and the correct SACPZ has `A0=3.959488e+03`. From memory, with 4 poles and 3 zeros shouldn't the difference also just be a factor of `(2 * Pi) **(4-3)`?
I believe it's because the conversion to radians/sec is done prior to converting from velocity to displacement (by adding a zero).  The original response in `m/s` has 2 zeros and 4 poles, making the calculation:
```Python
In [1]: 100.295 * (2*3.14159)**(4-2)
Out[1]: 3959.4812047591577
```

Now, I do not readily have a good answer why that order of operations is correct.  For what it's worth, reviewing the rdseed source code, it's done in that order, and likely why the service is following suit.
Ah, I overlooked that it's also changed to displacement.. :+1: 
Hmm yeah, the calculation seems OK now, A0 seems correct like this. Does `SACPZ` always use `meters` / `displacement`? Just if you know right away, I guess I could research it. (If velocity is OK too, maybe that might it might be simpler to just leave the physical units alone and only do the radians/Hertz conversion?)
> Does SACPZ always use meters / displacement?

Well the sac convention is that data are in nanometers / displacement, although I think it's common for folks to work in meters also and just skip converting to nanometers.

A quick search of the source code and it does not appear that sac reads the `INPUT UNIT` annotation.  The help for the `transfer` command says:

> The INPUT UNIT for any polezero produced by sacpz or evalresp will be "M"

but really, following the use of any SACPZ file for deconvolution, the data are in whatever units the SACPZ describe.

At the DMC we convert the time dimension to displacement and leave the length dimension as it is, but it is very commonly meters and so that becomes presumed unit.


Resolving patch:
diff --git a/obspy/core/inventory/response.py b/obspy/core/inventory/response.py
--- a/obspy/core/inventory/response.py
+++ b/obspy/core/inventory/response.py
@@ -319,6 +319,35 @@ def pz_transfer_function_type(self, value):
         else:
             raise ValueError(msg)
 
+    def to_radians_per_second(self):
+        """
+        Convert to type 'LAPLACE (RADIANS/SECOND)'
+        """
+        if self.pz_transfer_function_type == 'LAPLACE (RADIANS/SECOND)':
+            return
+        elif self.pz_transfer_function_type == 'LAPLACE (HERTZ)':
+            twopi = 2 * pi
+            self.normalization_factor *= twopi ** (
+                len(self.poles) - len(self.zeros))
+            self.poles = [
+                ComplexWithUncertainties(
+                    x.real * twopi, x.imag * twopi,
+                    upper_uncertainty=x.upper_uncertainty * twopi,
+                    lower_uncertainty=x.lower_uncertainty * twopi)
+                for x in self.poles]
+            self.zeros = [
+                ComplexWithUncertainties(
+                    x.real * twopi, x.imag * twopi,
+                    upper_uncertainty=x.upper_uncertainty * twopi,
+                    lower_uncertainty=x.lower_uncertainty * twopi)
+                for x in self.zeros]
+            self.pz_transfer_function_type = 'LAPLACE (RADIANS/SECOND)'
+        else:
+            msg = (f"Can not convert transfer function type "
+                   f"'{self.pz_transfer_function_type}' to "
+                   f"'LAPLACE (RADIANS/SECOND)'")
+            raise ValueError(msg)
+
 
 class CoefficientsTypeResponseStage(ResponseStage):
     """
@@ -1994,7 +2023,8 @@ def from_paz(cls, zeros, poles, stage_gain,
         return resp
 
 
-def paz_to_sacpz_string(paz, instrument_sensitivity):
+def paz_to_sacpz_string(paz, instrument_sensitivity,
+                        to_radians_per_second=True):
     """
     Returns SACPZ ASCII text representation of Response.
 
@@ -2002,9 +2032,21 @@ def paz_to_sacpz_string(paz, instrument_sensitivity):
     :param paz: Poles and Zeros response information
     :type instrument_sensitivity: :class:`InstrumentSensitivity`
     :param paz: Overall instrument sensitivity of response
+    :type to_radians_per_second: bool
+    :param to_radians_per_second: Whether to convert poles and zeros to
+        radians/s before returning SACPZ string. This should be done in most
+        cases as SAC is expecting radians/s.
     :rtype: str
     :returns: Textual SACPZ representation of poles and zeros response stage.
     """
+    if to_radians_per_second:
+        paz = copy.deepcopy(paz)
+        paz.to_radians_per_second()
+    if paz.pz_transfer_function_type != 'LAPLACE (RADIANS/SECOND)':
+        msg = ('Returning a SACPZ string from a PAZResponseStage that is not '
+               'radians/s. SAC is expecting SACPZ data to be in radians/s '
+               '(see #3334).')
+        warnings.warn(msg)
     # assemble output string
     out = []
     out.append("ZEROS %i" % len(paz.zeros))
diff --git a/obspy/io/sac/sacpz.py b/obspy/io/sac/sacpz.py
--- a/obspy/io/sac/sacpz.py
+++ b/obspy/io/sac/sacpz.py
@@ -12,6 +12,7 @@
 """
 import numpy as np
 import warnings
+from copy import deepcopy
 from pathlib import Path
 
 from obspy import UTCDateTime
@@ -55,6 +56,10 @@ def _write_sacpz(inventory, file_or_file_object):
                     warnings.warn(msg)
                     continue
                 input_unit = sens.input_units.upper()
+                # convert hertz to radians/s, see #3334
+                paz = deepcopy(paz)
+                paz.to_radians_per_second()
+                # SAC seems to expect "meters" as the unit
                 if input_unit == "M":
                     pass
                 elif input_unit in ["M/S", "M/SEC"]:


Test patch:
diff --git a/obspy/core/tests/data/G_CAN__LHZ.xml b/obspy/core/tests/data/G_CAN__LHZ.xml
new file mode 100644
--- /dev/null
+++ b/obspy/core/tests/data/G_CAN__LHZ.xml
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+ <FDSNStationXML xmlns="http://www.fdsn.org/xml/station/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:iris="http://www.fdsn.org/xml/station/1/iris" xsi:schemaLocation="http://www.fdsn.org/xml/station/1 http://www.fdsn.org/xml/station/fdsn-station-1.1.xsd" schemaVersion="1.1">
+  <Source>IRIS-DMC</Source>
+  <Sender>IRIS-DMC</Sender>
+  <Module>IRIS WEB SERVICE: fdsnws-station | version: 1.1.52</Module>
+  <ModuleURI>http://service.iris.edu/fdsnws/station/1/query?net=G&amp;sta=CAN&amp;cha=LHZ&amp;starttime=2004-12-26&amp;endtime=2004-12-26&amp;level=response&amp;format=xml&amp;includecomments=true&amp;nodata=404</ModuleURI>
+  <Created>2023-07-25T14:33:04.2071</Created>
+  <Network code="G" startDate="1982-01-01T00:00:00.0000" alternateCode="GEOSCOPE" restrictedStatus="open">
+   <Description>GEOSCOPE ()</Description>
+   <Identifier type="DOI">10.18715/GEOSCOPE.G
+   </Identifier>
+   <TotalNumberStations>55</TotalNumberStations>
+   <SelectedNumberStations>1</SelectedNumberStations>
+   <Station code="CAN" startDate="1987-11-27T00:00:00.0000" restrictedStatus="open" iris:alternateNetworkCodes="_FDSN,_FDSN-ALL,_REALTIME,_STS-1,.UNRESTRICTED">
+    <Latitude>-35.318715</Latitude>
+    <Longitude>148.996325</Longitude>
+    <Elevation>700.0</Elevation>
+    <Site>
+     <Name>Canberra, Australia</Name>
+    </Site>
+    <CreationDate>1987-11-27T00:00:00.0000</CreationDate>
+    <TotalNumberChannels>35</TotalNumberChannels>
+    <SelectedNumberChannels>1</SelectedNumberChannels>
+    <Channel code="LHZ" locationCode="" startDate="1989-06-02T00:00:00.0000" endDate="2006-12-10T02:00:00.0000" restrictedStatus="open">
+     <Latitude>-35.318715</Latitude>
+     <Longitude>148.996325</Longitude>
+     <Elevation>700</Elevation>
+     <Depth>0</Depth>
+     <Azimuth>0</Azimuth>
+     <Dip>-90</Dip>
+     <Type>CONTINUOUS</Type>
+     <Type>GEOPHYSICAL</Type>
+     <SampleRate>1E00</SampleRate>
+     <ClockDrift>1.5E-02</ClockDrift>
+     <Sensor>
+      <Description>STRECKEISEN STS1</Description>
+     </Sensor>
+     <Response>
+     <InstrumentSensitivity>
+       <Value>1.84484E9</Value>
+       <Frequency>0.01</Frequency>
+       <InputUnits>
+         <Name>m/s</Name>
+         <Description>VELOCITY in Meters Per Second</Description>
+       </InputUnits>
+       <OutputUnits>
+         <Name>counts</Name>
+         <Description>DIGITAL UNIT in Counts</Description>
+       </OutputUnits>
+     </InstrumentSensitivity>
+      <Stage number="1">
+      <PolesZeros>
+        <InputUnits>
+          <Name>m/s</Name>
+          <Description>VELOCITY in Meters Per Second</Description>
+        </InputUnits>
+        <OutputUnits>
+          <Name>V</Name>
+          <Description>Volts</Description>
+        </OutputUnits>
+        <PzTransferFunctionType>LAPLACE (HERTZ)</PzTransferFunctionType>
+        <NormalizationFactor>100.295</NormalizationFactor>
+        <NormalizationFrequency unit="HERTZ">0.01</NormalizationFrequency>
+        <Zero number="0">
+          <Real minusError="0.0" plusError="0.0">0.0</Real>
+          <Imaginary minusError="0.0" plusError="0.0">0.0</Imaginary>
+        </Zero>
+        <Zero number="1">
+          <Real minusError="0.0" plusError="0.0">0.0</Real>
+          <Imaginary minusError="0.0" plusError="0.0">0.0</Imaginary>
+        </Zero>
+        <Pole number="0">
+          <Real minusError="0.0" plusError="0.0">-0.00196389</Real>
+          <Imaginary minusError="0.0" plusError="0.0">0.00196448</Imaginary>
+        </Pole>
+        <Pole number="1">
+          <Real minusError="0.0" plusError="0.0">-0.00196389</Real>
+          <Imaginary minusError="0.0" plusError="0.0">-0.00196448</Imaginary>
+        </Pole>
+        <Pole number="2">
+          <Real minusError="0.0" plusError="0.0">-6.235</Real>
+          <Imaginary minusError="0.0" plusError="0.0">7.81823</Imaginary>
+        </Pole>
+        <Pole number="3">
+          <Real minusError="0.0" plusError="0.0">-6.235</Real>
+          <Imaginary minusError="0.0" plusError="0.0">-7.81823</Imaginary>
+        </Pole>
+      </PolesZeros>
+      <StageGain>
+        <Value>2252.0</Value>
+        <Frequency>0.01</Frequency>
+      </StageGain>
+
+      </Stage>
+      <Stage number="2">
+      <PolesZeros>
+        <InputUnits>
+          <Name>V</Name>
+          <Description>Volts</Description>
+        </InputUnits>
+        <OutputUnits>
+          <Name>V</Name>
+          <Description>Volts</Description>
+        </OutputUnits>
+        <PzTransferFunctionType>LAPLACE (HERTZ)</PzTransferFunctionType>
+        <NormalizationFactor>15301.0</NormalizationFactor>
+        <NormalizationFrequency unit="HERTZ">0.01</NormalizationFrequency>
+        <Pole number="0">
+          <Real minusError="0.0" plusError="0.0">-4.83034</Real>
+          <Imaginary minusError="0.0" plusError="0.0">1.25225</Imaginary>
+        </Pole>
+        <Pole number="1">
+          <Real minusError="0.0" plusError="0.0">-4.83034</Real>
+          <Imaginary minusError="0.0" plusError="0.0">-1.25225</Imaginary>
+        </Pole>
+        <Pole number="2">
+          <Real minusError="0.0" plusError="0.0">-3.5344</Real>
+          <Imaginary minusError="0.0" plusError="0.0">3.5155</Imaginary>
+        </Pole>
+        <Pole number="3">
+          <Real minusError="0.0" plusError="0.0">-3.5344</Real>
+          <Imaginary minusError="0.0" plusError="0.0">-3.5155</Imaginary>
+        </Pole>
+        <Pole number="4">
+          <Real minusError="0.0" plusError="0.0">-1.29488</Real>
+          <Imaginary minusError="0.0" plusError="0.0">4.8011</Imaginary>
+        </Pole>
+        <Pole number="5">
+          <Real minusError="0.0" plusError="0.0">-1.29488</Real>
+          <Imaginary minusError="0.0" plusError="0.0">-4.8011</Imaginary>
+        </Pole>
+      </PolesZeros>
+      <StageGain>
+        <Value>1.0</Value>
+        <Frequency>0.01</Frequency>
+      </StageGain>
+
+      </Stage>
+      <Stage number="3">
+      <Coefficients>
+        <InputUnits>
+          <Name>V</Name>
+          <Description>Volts</Description>
+        </InputUnits>
+        <OutputUnits>
+          <Name>counts</Name>
+          <Description>DIGITAL UNIT in Counts</Description>
+        </OutputUnits>
+        <CfTransferFunctionType>DIGITAL</CfTransferFunctionType>
+      </Coefficients>
+      <Decimation>
+        <InputSampleRate unit="HERTZ">20.0</InputSampleRate>
+        <Factor>1</Factor>
+        <Offset>0</Offset>
+        <Delay>0.0</Delay>
+        <Correction>0.0</Correction>
+      </Decimation>
+      <StageGain>
+        <Value>819200.0</Value>
+        <Frequency>0.0</Frequency>
+      </StageGain>
+
+      </Stage>
+      <Stage number="4">
+      <Coefficients>
+        <InputUnits>
+          <Name>counts</Name>
+          <Description>DIGITAL UNIT in Counts</Description>
+        </InputUnits>
+        <OutputUnits>
+          <Name>counts</Name>
+          <Description>DIGITAL UNIT in Counts</Description>
+        </OutputUnits>
+        <CfTransferFunctionType>DIGITAL</CfTransferFunctionType>
+        <Numerator minusError="0.0" plusError="0.0">-1.37791E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.50207E-9</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">4.12298E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.61024E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">3.48144E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.23953E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.1633E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.68504E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-9.01955E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.40573E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.92826E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.53591E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.07119E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.31791E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">8.45268E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.6501E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.18798E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.00598E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">7.3645E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.6633E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.7042E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-7.31531E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-8.0559E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-5.61434E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.11834E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.39757E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.8454E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.38511E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.16845E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.65919E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.12583E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.59675E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-5.58182E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.09312E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.38741E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.59549E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.19576E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00106069</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00137144</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00125805</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">6.32719E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.19908E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00162522</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00257098</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00282804</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00211048</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.23021E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00186453</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0040629</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0053411</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00500105</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0027684</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00100509</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00532275</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00875579</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00986079</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00769521</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00226706</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00525413</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0126879</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0173759</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0169908</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0104044</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00165914</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0164315</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0295065</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0358112</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0309306</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0124392</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0191435</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0600871</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.104009</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.143173</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.170214</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.179864</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.170214</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.143173</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.104009</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0600871</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0191435</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0124392</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0309306</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0358112</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0295065</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0164315</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00165914</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0104044</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0169908</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0173759</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0126879</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00525413</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00226706</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00769521</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00986079</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00875579</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00532275</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00100509</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0027684</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00500105</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0053411</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0040629</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00186453</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.23021E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00211048</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00282804</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00257098</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.00162522</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.19908E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">6.32719E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00125805</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00137144</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00106069</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.19576E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.59549E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.38741E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.09312E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-5.58182E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.59675E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.12583E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.65919E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.16845E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.38511E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.8454E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.39757E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.11834E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-5.61434E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-8.0559E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-7.31531E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.7042E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.6633E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">7.3645E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.00598E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.18798E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.6501E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">8.45268E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.31791E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.07119E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.53591E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.92826E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.40573E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-9.01955E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.68504E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.1633E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">5.23953E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">3.48144E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.61024E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">4.12298E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.50207E-9</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.37791E-8</Numerator>
+      </Coefficients>
+      <Decimation>
+        <InputSampleRate unit="HERTZ">20.0</InputSampleRate>
+        <Factor>4</Factor>
+        <Offset>0</Offset>
+        <Delay>0.0</Delay>
+        <Correction>0.0</Correction>
+      </Decimation>
+      <StageGain>
+        <Value>1.0</Value>
+        <Frequency>0.0</Frequency>
+      </StageGain>
+
+      </Stage>
+      <Stage number="5">
+      <Coefficients>
+        <InputUnits>
+          <Name>counts</Name>
+          <Description>DIGITAL UNIT in Counts</Description>
+        </InputUnits>
+        <OutputUnits>
+          <Name>counts</Name>
+          <Description>DIGITAL UNIT in Counts</Description>
+        </OutputUnits>
+        <CfTransferFunctionType>DIGITAL</CfTransferFunctionType>
+        <Numerator minusError="0.0" plusError="0.0">2.8063E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">7.6076E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.4E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.6583E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">3.0363E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.8137E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.6737E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.893E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-7.4176E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.2279E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.803E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.3505E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.6659E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.4577E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.3777E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.1279E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">4.6381E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.7759E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.5918E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.2162E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.7077E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.8783E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.5192E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.4396E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.8012E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.2294E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.6145E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0010224</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0013429</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0015435</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0015377</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0012476</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.2234E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">3.4285E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00159</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0029896</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0043419</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.005393</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0058643</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0054955</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0040952</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0015934</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0019143</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0061389</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.010594</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.014625</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.017461</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.018308</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.016443</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.01133</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0027142</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0093044</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.024236</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.041215</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.059066</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.076411</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.091812</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.10393</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.11167</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.11434</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.11167</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.10393</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.091812</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.076411</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.059066</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.041215</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.024236</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0093044</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0027142</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.01133</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.016443</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.018308</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.017461</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.014625</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.010594</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0061389</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0019143</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0015934</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0040952</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0054955</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0058643</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.005393</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0043419</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.0029896</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">0.00159</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">3.4285E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.2234E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0012476</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0015377</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0015435</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0013429</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-0.0010224</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-6.6145E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.2294E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.8012E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.4396E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.5192E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.8783E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.7077E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.2162E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.5918E-4</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.7759E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">4.6381E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">9.1279E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.3777E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.4577E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.6659E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-2.3505E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.803E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.2279E-5</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-7.4176E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-3.893E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-1.6737E-6</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">-4.8137E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">3.0363E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.6583E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">1.4E-7</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">7.6076E-8</Numerator>
+        <Numerator minusError="0.0" plusError="0.0">2.8063E-8</Numerator>
+      </Coefficients>
+      <Decimation>
+        <InputSampleRate unit="HERTZ">5.0</InputSampleRate>
+        <Factor>5</Factor>
+        <Offset>0</Offset>
+        <Delay>0.0</Delay>
+        <Correction>0.0</Correction>
+      </Decimation>
+      <StageGain>
+        <Value>1.0</Value>
+        <Frequency>0.0</Frequency>
+      </StageGain>
+
+      </Stage>
+     </Response>
+    </Channel>
+   </Station>
+  </Network>
+ </FDSNStationXML>
diff --git a/obspy/core/tests/test_response.py b/obspy/core/tests/test_response.py
--- a/obspy/core/tests/test_response.py
+++ b/obspy/core/tests/test_response.py
@@ -10,6 +10,7 @@
     (https://www.gnu.org/copyleft/lesser.html)
 """
 import warnings
+from copy import deepcopy
 from math import pi
 
 import numpy as np
@@ -598,3 +599,27 @@ def test_non_SI_unit_input_first_stage(self, testdata):
             got = [resp._get_overall_sensitivity_and_gain(f, output='VEL')[1]
                    for f in freqs]
             np.testing.assert_allclose(got, expected, rtol=1e-2)
+
+    def test_pazresponsestage_hertz_to_radians(self, testdata):
+        """
+        Tests converting a PAZResponseStage from Hertz to Radians/s
+        """
+        inv = read_inventory(testdata['G_CAN__LHZ.xml'], 'STATIONXML')
+        paz_before = inv[0][0][0].response.response_stages[0]
+        paz_after = deepcopy(paz_before)
+        paz_after.to_radians_per_second()
+
+        expected_zeros = [0j, 0j]
+        expected_poles = [-1.233948e-02+1.234319e-02j,
+                          -1.233948e-02-1.234319e-02j,
+                          -3.917566e+01+4.912339e+01j,
+                          -3.917566e+01-4.912339e+01j]
+
+        assert paz_before.pz_transfer_function_type == 'LAPLACE (HERTZ)'
+        assert paz_after.pz_transfer_function_type == \
+            'LAPLACE (RADIANS/SECOND)'
+        assert round(paz_after.normalization_factor, 3) == 3.959488e+03
+        np.testing.assert_allclose(
+            paz_after.zeros, expected_zeros, rtol=1e-6)
+        np.testing.assert_allclose(
+            paz_after.poles, expected_poles, rtol=1e-6)
diff --git a/obspy/io/sac/tests/test_sacpz.py b/obspy/io/sac/tests/test_sacpz.py
--- a/obspy/io/sac/tests/test_sacpz.py
+++ b/obspy/io/sac/tests/test_sacpz.py
@@ -11,6 +11,7 @@
 from obspy import read_inventory, Trace
 from obspy.core.inventory.util import Equipment
 from obspy.core.util import NamedTemporaryFile
+from obspy.core.util.testing import WarningsCapture
 from obspy.io.sac import attach_paz, attach_resp
 
 
@@ -212,3 +213,57 @@ def test_sacpaz_from_resp(self, testdata):
         # plt.semilogx(f,phase1)
         # plt.semilogx(f,phase2,'k--')
         # plt.show()
+
+    def test_writing_sacpz_hertz_to_radians(self, root):
+        """
+        Tests writing out a response with poles and zeros described in Hertz as
+        a SACPZ file, which implicitely expects the given data to be RADIANS/S,
+        so that a conversion is needed.
+
+        See #3334
+        """
+        path = root / 'core' / 'tests' / 'data' / 'G_CAN__LHZ.xml'
+        inv = read_inventory(path, 'STATIONXML')
+
+        expected_a0 = 3.959488e+03
+        expected_constant = 7.304622e+12
+        expected_zeros = [0j, 0j, 0j]
+        expected_poles = [-1.233948e-02+1.234319e-02j,
+                          -1.233948e-02-1.234319e-02j,
+                          -3.917566e+01+4.912339e+01j,
+                          -3.917566e+01-4.912339e+01j]
+
+        sio = io.StringIO()
+        # ignore a warning because there is an additional unity PAZ stage
+        with WarningsCapture():
+            inv.write(sio, format='SACPZ')
+        sio.seek(0)
+        lines = sio.readlines()
+        # make sure we find the lines we are looking for
+        for expected_start in ('* A0  ', 'ZEROS', 'POLES', 'CONSTANT'):
+            for line in lines:
+                if line.startswith(expected_start):
+                    break
+            else:
+                msg = f"No line starting with '{expected_start}' found."
+                pytest.fail(msg)
+        # now test values
+        for i, line in enumerate(lines):
+            if line.startswith('* A0  '):
+                value = float(line.split()[-1])
+                assert round(value, 3) == expected_a0
+            elif line.startswith('ZEROS'):
+                num_zeros = int(line.split()[-1])
+                assert num_zeros == 3
+                zeros = [complex(*map(float, lines[i+1+j].split()))
+                         for j in range(num_zeros)]
+                np.testing.assert_allclose(zeros, expected_zeros)
+            elif line.startswith('POLES'):
+                num_poles = int(line.split()[-1])
+                assert num_poles == 4
+                poles = [complex(*map(float, lines[i+1+j].split()))
+                         for j in range(num_poles)]
+                np.testing.assert_allclose(poles, expected_poles)
+            elif line.startswith('CONSTANT'):
+                value = float(line.split()[-1])
+                assert round(value, 3) == expected_constant