obspy / obspy

ObsPy: A Python Toolbox for seismology/seismological observatories.

Home Page:https://www.obspy.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Input unit with a metrix prefix not implemented in remove_response

luvec opened this issue · comments

Avoid duplicates

  • I searched existing issues

Bug Summary

I'm not quite sure if it's a bug or feature. Remove_response cannot recognize the correct scale for a unit with a metric prefix (such as nm/s). The description on the remove_response webpage says: "VEL velocity, output unit is meters/second" but in reality, it remains in the original prefixed unit.

I can see that in Response._call_eval_resp_for_frequencies it is already prepared to scale a response according to the input unit:

# Scale factor with the same logic as evalresp.
if key in ["CM/S**2", "CM/S", "CM/SEC", "CM"]:
scale_factor[0] = 1.0E2
elif key in ["MM/S**2", "MM/S", "MM/SEC", "MM"]:
scale_factor[0] = 1.0E3
elif key in ["NM/S**2", "NM/S", "NM/SEC", "NM"]:
scale_factor[0] = 1.0E9

but is not used finally:
# XXX: Check if this is really not needed.
# output *= scale_factor[0]

Code to Reproduce

No response

Error Traceback

No response

ObsPy Version?

1.4.0

Operating System?

Windows

Python Version?

3.10.8

Installation Method?

conda

Can you add a self-contained example please?

from obspy.clients.fdsn import Client
from obspy import UTCDateTime

net = 'SL'
stat = 'BOJS'
chan = 'LHZ'
torigin = UTCDateTime('2023-10-09T18:23:09')
client = Client('ODC')

inv_nms = client.get_stations(network=net, station=stat, channel=chan, endafter=torigin, level='response')
print(inv_nms.networks[0].stations[0].channels[0].response)    # input unit: nm/s

# create the same inventory but with the input unit: m/s 
inv_ms = inv_nms.copy()
sensitivity = inv_ms.networks[0].stations[0].channels[0].response.instrument_sensitivity
sensitivity.input_units = 'm/s'
sensitivity.value *= 1e9
gain_0 = inv_ms.networks[0].stations[0].channels[0].response.response_stages[0]
gain_0.input_units = 'm/s'
gain_0.stage_gain *= 1e9
print(inv_ms.networks[0].stations[0].channels[0].response)

# download a signal & attach the response
tr_nms = client.get_waveforms(network=net, station=stat, location='*', channel=chan, 
                              starttime=torigin, endtime=torigin+600)[0]
tr_ms = tr_nms.copy()
tr_nms.attach_response(inv_nms)
tr_ms.attach_response(inv_ms)

# remove response
# ... output unit should be m/s in both cases
# ... see https://docs.obspy.org/packages/autogen/obspy.core.trace.Trace.remove_response.html#obspy.core.trace.Trace.remove_response
# ... output = "VEL": velocity, output unit is meters/second
tr_nms.remove_response(output='VEL')
tr_ms.remove_response(output='VEL')
# but it isn't, nano- prefix was not taken into account
print('nm/s response:', tr_nms.data.max())
print(' m/s response:', tr_ms.data.max())

and output:

Channel Response
From nm/s () to COUNTS ()
Overall Sensitivity: 1.84549 defined at 50.000 Hz
3 stages:
Stage 1: PolesZerosResponseStage from nm/s to V, gain: 1.1e-06
Stage 2: CoefficientsTypeResponseStage from V to COUNTS, gain: 1.6777e+06
Stage 3: FIRResponseStage from COUNTS to COUNTS, gain: 1

Channel Response
From m/s () to COUNTS ()
Overall Sensitivity: 1.84549e+09 defined at 50.000 Hz
3 stages:
Stage 1: PolesZerosResponseStage from m/s to V, gain: 1100
Stage 2: CoefficientsTypeResponseStage from V to COUNTS, gain: 1.6777e+06
Stage 3: FIRResponseStage from COUNTS to COUNTS, gain: 1

nm/s response: 9327.849844573393
m/s response: 9.327849844573399e-06

OK, I see. Well, we definitely wanna do something about this. I guess, ideally we just want to use what is in place inside the evalresp C code, the unit juggling seems to be done there, not sure why it isn't applied inside evalresp. Anybody used to debugging C want to give it a shot?

Alternatively we could do it in Python, like mentioned and commented out in the code. But it feels like it would be safer to find out why it is not applied inside evalresp try to fix that instead.

So I had a more detailed look at this today. The way we call evalresp (the quite old version we use), the function that evaluates the scale factor and sets a global variable in evalresp to account for it later never gets called the way we use only a few select functions of libevalresp.

From what I can see evalresp also just looks at the first selected stage input units and sets that scale factor accordingly, so we can just do the same in Python for now.

N.b.: It looks like in newer versions of evalresp this was changed and the scale factor gets attached to the channel object.. ideally we'd want to update to a more recent version of evalresp, tbh, but that might be quite some work changing our wrapper code.