Skip to content

Commit e957429

Browse files
authored
Add files via upload
1 parent 9edf517 commit e957429

1 file changed

Lines changed: 264 additions & 0 deletions

File tree

src/power_normalization.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#!/usr/bin/env python
2+
"""
3+
4+
power_normalization.py
5+
6+
Purpose: Normalize frequency-corrected power using a spline fit of specified
7+
order.
8+
9+
NOTE: Dependent on final format of geometry instances ("geo_inst")
10+
11+
Revisions:
12+
gjs_power_normalization.py
13+
Feb 26 2018 - gsteranka - Original version
14+
gjs_power_normalization_v2.py
15+
Mar 06 2018 - gsteranka - Edited so user only needs to instantiate and
16+
run get_spline_fit, which now returns spline fit
17+
for a specified array of SPM values.
18+
gjs_power_normalization_v3.py
19+
Mar 19 2018 - gsteranka - Edited so default inputs are set as attributes in
20+
__init__, and the inputs in get_spline_fit can
21+
override those defaults as the new attribute
22+
values. Done this way to keep track of what the
23+
previous fit specifications were inside of the
24+
instance.
25+
power_normalization.py
26+
Mar 20 2018 - gsteranka - Edited to get rid of debug steps
27+
Mar 20 2018 - gsteranka - Fixed bugs with spm_vals_down getting defined
28+
wrong, and with default knots sometimes stretching
29+
beyond the input times to make a spline fit for.
30+
Added error check if no input points fall within
31+
specified freespace regions
32+
Apr 04 2018 - gsteranka - In initial downsampling step before resample_poly,
33+
downsample IQ_c_raw instead of p_obs_raw. This
34+
accompanies the recent change in norm_diff_class.py
35+
Apr 04 2018 - gsteranka - In loop to make knots_spm and knots_rho, change i
36+
index to int(i)
37+
Apr 05 2018 - gsteranka - Added index sorting right before splrep to take
38+
ingress files, since splrep expects x values to be
39+
in order, which isn't true for ingress
40+
"""
41+
42+
import numpy as np
43+
import pandas as pd
44+
from scipy import signal
45+
from scipy.interpolate import interp1d
46+
from scipy.interpolate import splrep
47+
from scipy.interpolate import splev
48+
import sys
49+
50+
class Normalization(object):
51+
"""Class to get a power normalizing spline fit for frequency corrected
52+
data uniformly spaced in time at raw resolution.
53+
54+
Example:
55+
>>> fit_inst = FreqOffsetFit(rsr_inst, geo_inst, f_spm, f_offset, \
56+
f_uso, kernels, k=k, rho_exclude=rho_exclude)
57+
>>> (spm_raw, IQ_c_raw) = fit_inst.get_IQ_c()
58+
>>> norm_inst = Normalization(spm_raw, IQ_c_raw, geo_inst)
59+
>>> spm_fit = np.arange(spm_raw[0], spm_raw[-1], 1.0)
60+
>>> spline_fit = norm_inst.get_spline_fit(spm_fit, k=k, \
61+
knots_km=knots_km, dt_down=dt_down, \
62+
freespace_km=freespace_km, TEST=TEST)
63+
64+
Attributes:
65+
_k (int): Order of the spline fit. Default order is 2
66+
_knots_km (list): List of knots for the spline fit. Should only
67+
be at places where there is data. Specify in km
68+
_dt_down (float): Time spacing to downsample to before making
69+
a spline fit
70+
_freespace_km (list): Set of radius values, in km, to treat as
71+
free space. These are the regions that the spline fit is made
72+
to. Specify in km. Be sure to include a region for each knot
73+
specified
74+
__spm_raw (np.ndarray): Raw resolution SPM values
75+
__IQ_c_raw (np.ndarray): Raw resolution frequency corrected I and Q
76+
__rho_interp_func (scipy.interpolate.interpolate.interp1d): interp1d
77+
function to get set of rho values in km for a set of SPM
78+
"""
79+
80+
def __init__(self, spm_raw, IQ_c_raw, geo_inst, TEST=False):
81+
"""Instantiation defines raw resolution SPM, frequency corrected I
82+
and Q, and a function to interpolate radius to any SPM value. These
83+
are set as attributes
84+
85+
Args:
86+
spm_raw (np.ndarray): Raw resolution array of SPM values
87+
IQ_c_raw (np.ndarray): Frequency corrected complex signal at
88+
raw resolution
89+
geo_inst: Instance of geometry class. Contains attributes
90+
t_oet_spm and rho_km_vals. Can create mock version from a
91+
geometry file using geo_file_into_instance.py
92+
TEST (bool): Optional boolean argument that, if True, prints out
93+
intermediate values"""
94+
95+
self.__spm_raw = spm_raw
96+
self.__IQ_c_raw = IQ_c_raw
97+
98+
# Function to interpolate spm to rho values in km
99+
rho_interp_func = interp1d(np.asarray(geo_inst.t_oet_spm_vals),
100+
np.asarray(geo_inst.rho_km_vals),
101+
fill_value='extrapolate')
102+
self.__rho_interp_func = rho_interp_func
103+
104+
# Default arguments for spline fit. Chosen because:
105+
# k: Order higher than 2 gives too much freedom to spline fit.
106+
# knots_km: By experimentation, these tend to give the most
107+
# consistently good spline fits. Sometimes need to
108+
# adjust if a file's free space power cuts off sooner
109+
# or begins later than expected
110+
# freespace_km: These regions are always free space and
111+
# uninterrupted by diffraction pattern or an
112+
# eccentric ringlet. These accompany the default
113+
# knots
114+
self._k = 2
115+
self._knots_km = np.array([70445., 87400., 117730., 119950., 133550.,
116+
194269.])
117+
self._dt_down = 0.5
118+
self._freespace_km = [[69100.0, 73500], [87350.0, 87450.0],
119+
[117690.0, 117780.0], [119850.0, 120020.0],
120+
[133500.0, 133650.0], [137000.0, 194400.0]]
121+
122+
if TEST:
123+
sample_rate_hz = int(round(1.0/(self.__spm_raw[1] -
124+
self.__spm_raw[0])))
125+
print('\nGeometry SPM, rho:')
126+
for i in range(10):
127+
print(geo_inst.t_oet_spm_vals[i], geo_inst.rho_km_vals[i])
128+
print('\nRaw resolution SPM, rho at 1 sec. interval:')
129+
for i in range(10):
130+
print(self.__spm_raw[i*sample_rate_hz],
131+
self.__rho_interp_func(self.__spm_raw[i*sample_rate_hz]))
132+
133+
134+
def __downsample_IQ(self, dt_down, TEST=False):
135+
"""Downsample complex signal to specified time spacing to avoid
136+
diffraction pattern ruining spline fit
137+
138+
Args:
139+
dt_down (float): Time spacing to downsample to
140+
TEST (bool): If True, prints downsampled results
141+
142+
Returns:
143+
spm_vals_down (np.ndarray): SPM values after downsampling
144+
rho_km_vals_down (np.ndarray): Rho values after downsampling
145+
p_obs_down (np.ndarray): Observed power after downsampling"""
146+
147+
# Downsampling coefficient q
148+
dt_raw = self.__spm_raw[1] - self.__spm_raw[0]
149+
q = round(dt_down / dt_raw)
150+
151+
# Downsample IQ_c by factor of q and not power because this is to
152+
# match the resampling done in norm_diff_class.py, where it also
153+
# resamples IQ_c
154+
IQ_c_down = signal.resample_poly(self.__IQ_c_raw, 1, q)
155+
p_obs_down = abs(IQ_c_down)**2
156+
157+
# New SPM, rho, and p_obs, at the downsampled resolution
158+
spm_vals_down = np.linspace(self.__spm_raw[0], self.__spm_raw[-1],
159+
num=len(p_obs_down))
160+
rho_km_vals_down = self.__rho_interp_func(spm_vals_down)
161+
162+
if TEST:
163+
print('\nDownsampled SPM, rho:')
164+
for i in range(10):
165+
print(spm_vals_down[i], rho_km_vals_down[i])
166+
167+
return spm_vals_down, rho_km_vals_down, p_obs_down
168+
169+
170+
def get_spline_fit(self, spm_fit, k=None, knots_km=None,
171+
dt_down=None, freespace_km=None, TEST=False):
172+
"""Make spline fit to observed downsampled power at specified set
173+
of times spm_fit. Specified keyword arguments will override the
174+
corresponding defaults set in __init__
175+
176+
Args:
177+
spm_fit (np.ndarray): SPM values to evaluate the spline fit at
178+
k (int): Order of the spline fit. Default order is 2
179+
knots_km (list): List of knots for the spline fit. Should only
180+
be at places where there is data. Specify in km
181+
dt_down (float): Time spacing to downsample to before making
182+
a spline fit
183+
freespace_km (list): Set of radius values, in km, to treat as
184+
free space. These are the regions that the spline fit is made
185+
to. Specify in km. Be sure to include a region for each knot
186+
specified
187+
TEST (bool): If True, print out intermediate values
188+
189+
Returns:
190+
spline_fit: Spline fit to observed power at the specified times
191+
spm_fit"""
192+
193+
# Update defaults if any keyword arguments were specified
194+
if k is not None:
195+
self._k = k
196+
if knots_km is not None:
197+
self._knots_km = knots_km
198+
if dt_down is not None:
199+
self._dt_down = dt_down
200+
if freespace_km is not None:
201+
self._freespace_km = freespace_km
202+
203+
# Use whatever most up-to-date arguments are
204+
k = self._k
205+
knots_km = self._knots_km
206+
dt_down = self._dt_down
207+
freespace_km = self._freespace_km
208+
209+
# Downsample I and Q to the time spacing dt_down
210+
(spm_vals_down, rho_km_vals_down,
211+
p_obs_down) = self.__downsample_IQ(dt_down, TEST=TEST)
212+
213+
# Define freespace regions
214+
ind_free = []
215+
for i in range(len(freespace_km)):
216+
ind_free.append(np.argwhere((rho_km_vals_down > freespace_km[i][0])
217+
& (rho_km_vals_down < freespace_km[i][1])))
218+
ind_free = np.reshape(np.concatenate(ind_free), -1)
219+
220+
# Limit downsampled data to freespace regions
221+
spm_vals_free = spm_vals_down[ind_free]
222+
rho_km_vals_free = rho_km_vals_down[ind_free]
223+
p_obs_free = p_obs_down[ind_free]
224+
225+
# Find knots in range of specified times
226+
knots_km_where = np.argwhere(
227+
np.logical_and(knots_km>min(rho_km_vals_free),
228+
knots_km<max(rho_km_vals_free)))
229+
230+
# If specified times don't have freespace region, you can't make a fit
231+
if len(knots_km_where) == 0:
232+
print('ERROR (Normalization.get_spline_fit): No specified times \n'+
233+
'fall within freespace range. Freespace ranges are '+
234+
str(freespace_km))
235+
sys.exit()
236+
237+
# Select data points closest to selected knots
238+
n_knots_km = len(knots_km_where)
239+
knots_spm = np.zeros(n_knots_km)
240+
knots_rho = np.zeros(n_knots_km)
241+
for i in knots_km_where:#range(n_knots_km):
242+
_ind = np.argmin(abs(rho_km_vals_free - knots_km[int(i)]))
243+
knots_spm[i] = spm_vals_free[_ind]
244+
knots_rho[i] = rho_km_vals_free[_ind]
245+
246+
if TEST:
247+
print('\nSelected knots:')
248+
print(knots_spm)
249+
print(knots_rho)
250+
251+
# Make and evaluate the spline fit. Sort spm_vals_free and p_obs_free
252+
# in case it's ingress, since indices were gotten from rho values,
253+
# which are not in order for ingress
254+
ind_sort = np.argsort(spm_vals_free)
255+
ind_knot_sort = np.argsort(knots_spm)
256+
spline_rep = splrep(spm_vals_free[ind_sort], p_obs_free[ind_sort],
257+
k=k, t=knots_spm[ind_knot_sort])
258+
spline_fit = splev(spm_fit, spline_rep)
259+
260+
return spline_fit
261+
262+
263+
if __name__ == '__main__':
264+
pass

0 commit comments

Comments
 (0)