Repository: obspy/obspy

Issue:
real_error and imag_error of RESP file format are parsed in unexpected behavior
### Avoid duplicates

- [x] I searched existing issues

### Bug Summary

`real_error` and `imag_error` in the RESP file format are parsed, violating [the FDSN Station XML schema](https://docs.fdsn.org/projects/stationxml/en/latest/reference.html#zero).

This is the simple code to find problem.
```
from obspy.io.xseed.core import _read_resp

a = _read_resp('./RESP')
#./RESP
#...
# #              Complex poles:
# #              i  real          imag          real_error    imag_error
#B053F15-18     0  -5.00000E+01  -5.00000E+01  +1.00000E+00  +2.00000E+00
#...

b = a[0][0][0]

print(f'Poles0= {b.response.get_paz().poles[0]}')
print(f'Poles0_upper_uncertainty= {b.response.get_paz().poles[0].upper_uncertainty} (this should be 1+2j)')
print(f'Poles0_lower_uncertainty= {b.response.get_paz().poles[0].lower_uncertainty} (this should be 1+2j)')
```

The code prints below.
```
Poles0= (-50-50j)
Poles0_upper_uncertainty= (-49-48j) (this should be 1+2j)
Poles0_lower_uncertainty= (-51-52j) (this should be 1+2j)
```

`upper_uncertainty` is parsed as `poles[0]`+(`real_error`+`imag_error` x j). Similarly, `lower_uncertainty` is parsed as `poles[0]`-(`real_error`+`imag_error` x j). This behavior does not follow [the FDSN Station XML schema](https://docs.fdsn.org/projects/stationxml/en/latest/reference.html#zero). 

`upper_uncertainty` should be (`real_error`+`imag_error` x j). `lower_uncertainty` should be (`real_error`+`imag_error` x j)

Following part might need modification ([`obspy/obspy/io/xseed/parser.py`](https://github.com/obspy/obspy/blob/dfb021350cb4e05f6baa76982c789352b02c3241/obspy/io/xseed/parser.py#L1441C1-L1441C2)).
```
                if hasattr(b53, "real_zero"):
                    for r, i, r_err, i_err in zip(
                            _list(b53.real_zero), _list(b53.imaginary_zero),
                            _list(b53.real_zero_error),
                            _list(b53.imaginary_zero_error)):
                        z = ComplexWithUncertainties(r, i)
                        err = ComplexWithUncertainties(r_err, i_err)
                        z.lower_uncertainty = z - err #here (z - err -> err)
                        z.upper_uncertainty = z + err #here (z - err -> err)
                        zeros.append(z)
                poles = []
                # Might somehow also not have zeros.
                if hasattr(b53, "real_pole"):
                    for r, i, r_err, i_err in zip(
                            _list(b53.real_pole), _list(b53.imaginary_pole),
                            _list(b53.real_pole_error),
                            _list(b53.imaginary_pole_error)):
                        p = ComplexWithUncertainties(r, i)
                        err = ComplexWithUncertainties(r_err, i_err)
                        p.lower_uncertainty = p - err #here (z - err -> err)
                        p.upper_uncertainty = p + err #here (z - err -> err)
                        poles.append(p)
```

Although errors of zeros and poles are not used in normal situations, I report it here.

### Code to Reproduce

```python
from obspy.io.xseed.core import _read_resp

a = _read_resp('./RESP')

#./RESP
#...
# #              Complex poles:
# #              i  real          imag          real_error    imag_error
#B053F15-18     0  -5.00000E+01  -5.00000E+01  +1.00000E+00  +2.00000E+00
#...

b = a[0][0][0]

print(f'Poles0= {b.response.get_paz().poles[0]}')
print(f'Poles0_upper_uncertainty= {b.response.get_paz().poles[0].upper_uncertainty} (this should be 1+2j)')
print(f'Poles0_lower_uncertainty= {b.response.get_paz().poles[0].lower_uncertainty} (this should be 1+2j)')
```


### Error Traceback

```Python traceback
No error
```


### ObsPy Version?

1.4.0

### Operating System?

Rocky-Linux-9 (5.14.0-284.30.1.el9_2.x86_64)

### Python Version?

Python 3.9.18

### Installation Method?

pip


Discussion:
@lowbontimp do you happen to have a real world file with errors specified? Some open station with metadata available via FDSN would be best to compare to other formats
@megies here is an example: [link](https://service.iris.edu/irisws/resp/1/query?net=IU&sta=HRV&loc=00&cha=BHZ&starttime=2010-02-17T03:08:00&endtime=2010-04-29T15:13:00) . We stopped publishing these as we figured no one was using them.

Resolving patch:
diff --git a/obspy/io/xseed/parser.py b/obspy/io/xseed/parser.py
--- a/obspy/io/xseed/parser.py
+++ b/obspy/io/xseed/parser.py
@@ -1438,9 +1438,9 @@ def _list(value):
                             _list(b53.real_zero_error),
                             _list(b53.imaginary_zero_error)):
                         z = ComplexWithUncertainties(r, i)
-                        err = ComplexWithUncertainties(r_err, i_err)
-                        z.lower_uncertainty = z - err
-                        z.upper_uncertainty = z + err
+                        err = complex(r_err, i_err)
+                        z.lower_uncertainty = err
+                        z.upper_uncertainty = err
                         zeros.append(z)
                 poles = []
                 # Might somehow also not have zeros.
@@ -1450,9 +1450,9 @@ def _list(value):
                             _list(b53.real_pole_error),
                             _list(b53.imaginary_pole_error)):
                         p = ComplexWithUncertainties(r, i)
-                        err = ComplexWithUncertainties(r_err, i_err)
-                        p.lower_uncertainty = p - err
-                        p.upper_uncertainty = p + err
+                        err = complex(r_err, i_err)
+                        p.lower_uncertainty = err
+                        p.upper_uncertainty = err
                         poles.append(p)
 
                 try:


Test patch:
diff --git a/obspy/io/xseed/tests/data/RESP.HRV.IU.00.BHZ_cropped b/obspy/io/xseed/tests/data/RESP.HRV.IU.00.BHZ_cropped
new file mode 100644
--- /dev/null
+++ b/obspy/io/xseed/tests/data/RESP.HRV.IU.00.BHZ_cropped
@@ -0,0 +1,40 @@
+# response is cropped to first stage, original data from
+# https://service.iris.edu/irisws/resp/1/query?net=IU&sta=HRV
+B050F03 Station:     HRV
+B050F16 Network:     IU
+B052F03 Location:    00
+B052F04 Channel:     BHZ
+B052F22 Start date:  2010,048,03:08:00
+B052F23 End date:    2010,119,15:13:00
+B053F03 Transfer function type:    A - Laplace transform analog response, in rad/sec
+B053F04 Stage sequence number:     1
+B053F05 Response in units lookup:  m/s - Velocity in Meters Per Second
+B053F06 Response out units lookup: V - Volts
+B053F07 A0 normalization factor:   +3.94300E+03
+B053F08 Normalization frequency:   +5.00000E-02
+B053F09 Number of zeroes:          4
+B053F14 Number of poles:           6
+B053F10-13 0  +0.00000E+00  +0.00000E+00  +0.00000E+00  +0.00000E+00
+B053F10-13 1  +0.00000E+00  +0.00000E+00  +0.00000E+00  +0.00000E+00
+B053F10-13 2  -2.60811E-02  +0.00000E+00  +2.37885E-02  +0.00000E+00
+B053F10-13 3  -2.60811E-02  +0.00000E+00  +2.37885E-02  +0.00000E+00
+B053F15-18 0  -1.54713E-02  -1.23400E-02  +6.11428E-04  +7.35041E-04
+B053F15-18 1  -1.54713E-02  +1.23400E-02  +6.11428E-04  +7.35041E-04
+B053F15-18 2  -2.16466E-02  +0.00000E+00  +4.62419E-03  +0.00000E+00
+B053F15-18 3  -2.17396E-02  +0.00000E+00  +4.68987E-03  +0.00000E+00
+B053F15-18 4  -3.91800E+01  -4.91200E+01  +0.00000E+00  +0.00000E+00
+B053F15-18 5  -3.91800E+01  +4.91200E+01  +0.00000E+00  +0.00000E+00
+B057F03 Stage sequence number:         1
+B057F04 Input sample rate (HZ):        2.0000E+01
+B057F05 Decimation factor:             00001
+B057F06 Decimation offset:             00000
+B057F07 Estimated delay (seconds):    +0.0000E+00
+B057F08 Correction applied (seconds): +0.0000E+00
+B058F03 Stage sequence number:        1
+B058F04 Sensitivity:                  +2.39900E+03
+B058F05 Frequency of sensitivity:     +5.00000E-02
+B058F06 Number of calibrations:       0
+B058F03 Stage sequence number:        0
+B058F04 Sensitivity:                  +4.02513E+09
+B058F05 Frequency of sensitivity:     +5.00000E-02
+B058F06 Number of calibrations:       0
diff --git a/obspy/io/xseed/tests/test_parser.py b/obspy/io/xseed/tests/test_parser.py
--- a/obspy/io/xseed/tests/test_parser.py
+++ b/obspy/io/xseed/tests/test_parser.py
@@ -8,7 +8,7 @@
 from lxml import etree
 
 import obspy
-from obspy import UTCDateTime, read
+from obspy import UTCDateTime, read, read_inventory
 from obspy.core.util import NamedTemporaryFile
 from obspy.io.xseed.blockette.blockette010 import Blockette010
 from obspy.io.xseed.blockette.blockette051 import Blockette051
@@ -573,6 +573,22 @@ def test_read_resp_data(self, testdata):
         # Weak but at least tests that something has been read.
         assert set(p.blockettes.keys()) == {34, 50, 52, 53, 54, 57, 58}
 
+    def test_read_resp_paz_uncertainties(self, testdata):
+        """
+        Regression test for correctly reading uncertainties in PAZ complex
+        numbers
+        """
+        inv = read_inventory(testdata['RESP.HRV.IU.00.BHZ_cropped'], "RESP")
+        resp = inv[0][0][0].response
+        pole = resp.response_stages[0].poles[0]
+        assert round(pole.imag, 4) == -0.0123
+        assert round(pole.imag.upper_uncertainty, 6) == 0.000735
+        assert round(pole.imag.lower_uncertainty, 6) == 0.000735
+        zero = resp.response_stages[0].zeros[2]
+        assert round(zero.real, 4) == -0.0261
+        assert round(zero.real.lower_uncertainty, 4) == 0.0238
+        assert round(zero.real.upper_uncertainty, 4) == 0.0238
+
     def clean_unit_string(self, string):
         """
         Returns a list of cleaned strings