Skip to content

Commit 22b17ce

Browse files
authored
Add files via upload
1 parent c8631a4 commit 22b17ce

1 file changed

Lines changed: 261 additions & 0 deletions

File tree

src/norm_diff_class.py

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#!/usr/bin/env python
2+
"""
3+
4+
norm_diff_class.py
5+
6+
Purpose: Apply previously made calibration file to calibrate raw data
7+
(frequency correct and normalize), which produces the diffraction
8+
pattern given to Fresnel Inversion code.
9+
10+
Revisions:
11+
gjs_produce_normalized_diffraction_pattern.py
12+
Mar 07 2018 - gsteranka - Original version. Tentative rough draft, since
13+
haven't yet chosen format of calibration file, and
14+
also don't have Ryan's code to find window width
15+
from a given window type and resolution.
16+
Mar 19 2018 - gsteranka - Edited to take new tentative final form of cal
17+
file, which has the columns as 1) SPM 2) Predicted
18+
sky frequency 3) Residual frequency fit
19+
4) Freespace power spline fit 5) Frequency offset
20+
fit. Also edited to save an obs file and return
21+
everything in the obs file too
22+
produce_normalized_diffraction_pattern.py
23+
Mar 20 2018 - gsteranka - Eliminate debug steps, and switched obs_file to be
24+
optional inputs
25+
norm_diff_class.py
26+
Mar 22 2018 - gsteranka - Edit previous code to be a class, with Fresnel
27+
inversion inputs as attributes
28+
Mar 30 2018 - gsteranka - Edited reading in of geometry file to read in csv
29+
format
30+
Apr 03 2018 - gsteranka - When first defining p_norm_vals, do it by
31+
normalizing IQ_c_desired, not IQ_m_desired
32+
Apr 09 2018 - gsteranka - Obs files now save absolute value of rho dot,
33+
which eliminates a lot of error checking in
34+
Fresnel Inversion step. Added
35+
"fill_value='extrapolate'" to rho_interp_func,
36+
p_free_interp_func, spm_desired_func, and
37+
f_sky_pred_func
38+
Apr 19 2018 - gsteranka - Changed attribute names to agree w/ what Ryan
39+
uses: rho_km_desired --> rho_km_vals
40+
spm_desired --> spm_vals
41+
f_sky_pred_vals --> f_sky_hz_vals
42+
"""
43+
44+
import numpy as np
45+
import pandas as pd
46+
from scipy.interpolate import interp1d
47+
import spiceypy as spice
48+
import sys
49+
50+
from rsr_reader import RSRReader
51+
from resample_IQ import resample_IQ
52+
53+
54+
class NormDiff(object):
55+
"""Produce a normalized diffraction pattern using a previously made
56+
calibration file (cal_file). Contains everything needed for Fresnel
57+
Inversion as attributes.
58+
59+
Attributes:
60+
rho_km_desired (np.ndarray): Ring intercept radius values, in km,
61+
at final spacing specified in dr_km
62+
spm_desired (np.ndarray): Set of SPM values corresponding to
63+
rho_km_desired
64+
p_norm_vals (np.ndarray): Power normalized to 1. This is the diffraction
65+
pattern that is input to the Fresnel inversion step
66+
phase_rad_vals (np.ndarray): Phase of the complex signal, in radians.
67+
This is the other part of the diffraction pattern that is input to
68+
the Fresnel Inversion step
69+
B_rad_vals (np.ndarray): Ring opening angle of Saturn
70+
D_km_vals (np.ndarray): Spacecraft-RIP (Ring Intercept Point) distance
71+
F_km_vals (np.ndarray): Fresnel scale, in km
72+
f_sky_pred_vals (np.ndarray): Predicted sky frequency, in Hz.
73+
phi_rad_vals (np.ndarray): Observed ring azimuth, in radians
74+
rho_dot_kms_vals (np.ndarray): Ring intercept point velocity
75+
"""
76+
77+
def __init__(self, rsr_file, dr_km, rho_km_range, res_km, window_type,
78+
geo_file, cal_file, dr_km_tol=0.01,
79+
decimate_16khz_to_1khz=False):
80+
"""
81+
Instantiation defines all attributes of instance.
82+
83+
Args:
84+
rsr_file (str): Full path name of RSR file to process
85+
dr_km (float): Desired final radial spacing of processed data, in km
86+
rho_km_range (list): Radius range to process over, in km
87+
res_km (float): Resolution to later do Fresnel Inversion at, in km
88+
window_type (str): Window type to later do Fresnel Inversion at
89+
geo_file (str): Full path name of a geoemtry file corresponding to
90+
rsr_file
91+
cal_file (str): Full path name of a calibration file corresponding to
92+
rsr_file
93+
dr_km_tol (float): Optional keyword argument, in km, that specifies the
94+
maximum distance the starting point of the final set of radius
95+
values can be away from an integer number of dr_km. For example, if
96+
you say dr_km_tol=0.01 and dr_km=0.25, the starting point of the
97+
final set of rho values could be 70000.26 km, but not 70000.261 km
98+
decimate_16khz_to_1khz (bool): Optional boolean keyword argument that
99+
specifies whether or not to decimate a 16khz sample rate file down
100+
to 1khz sample rate. Can only specify when rsr_file is 16khz. Value
101+
of this should match what you said in RSRfile.get_IQ method when you
102+
initially made the calibration file. Defualt is False
103+
"""
104+
105+
# Magical code to find radius range necessary for Fresnel Inversion.
106+
# For now just use the input radius range
107+
rho_km_range_inversion = rho_km_range
108+
109+
# Read geometry file for radius-SPM conversion
110+
geo = pd.read_csv(geo_file, header=None)
111+
spm_geo = np.asarray(geo[0][:])
112+
rho_km_geo = np.asarray(geo[3][:])
113+
B_rad_geo = np.asarray(geo[6][:]) * spice.rpd()
114+
D_km_geo = np.asarray(geo[7][:])
115+
phi_rad_geo = np.asarray(geo[5][:]) * spice.rpd()
116+
rho_dot_kms_geo = np.asarray(geo[8][:])
117+
F_km_geo = np.asarray(geo[10][:])
118+
119+
# Function to convert SPM to rho
120+
rho_interp_func = interp1d(spm_geo, rho_km_geo, fill_value='extrapolate')
121+
122+
# Find SPM range corresponding to rho range. Bottom-most line of this
123+
# block is to handle ingress files
124+
spm_start = max(spm_geo[rho_km_geo <= rho_km_range[0]])
125+
spm_end = min(spm_geo[rho_km_geo >= rho_km_range[1]])
126+
spm_range = [spm_start, spm_end]
127+
spm_range = [min(spm_range), max(spm_range)]
128+
129+
rsr_inst = RSRReader(rsr_file)
130+
(spm_vals, IQ_m) = rsr_inst.get_IQ(spm_range=spm_range,
131+
decimate_16khz_to_1khz=
132+
decimate_16khz_to_1khz)
133+
rho_km_vals = rho_interp_func(spm_vals)
134+
135+
cal = pd.read_csv(cal_file, delim_whitespace=True, header=None)
136+
137+
spm_cal = np.asarray(cal[0][:])
138+
f_sky_pred_cal = np.asarray(cal[1][:])
139+
p_free_cal = np.asarray(cal[3][:])
140+
f_offset_fit_cal = np.asarray(cal[4][:])
141+
rho_km_cal = rho_interp_func(spm_cal)
142+
143+
# If specified rho range that cal file can't cover
144+
if (spm_range[0]<min(spm_cal)) | (spm_range[1]>max(spm_cal)):
145+
print('ERROR (NormDiff): Specified cal file is missing \n'
146+
+'points required to pre-process points in rho_km_range.\n'
147+
+'Calibration file rho range is '+str(min(rho_km_cal))+'km, '
148+
+str(max(rho_km_cal))+'km, while specified rho_km_range is \n'
149+
+str(rho_km_range[0])+'km, '+str(rho_km_range[1])+'km')
150+
sys.exit()
151+
152+
# Inteprolate frequency offset to finer spacing in preparation
153+
# for integration
154+
dt = 0.1
155+
n_pts = round((spm_cal[-1] - spm_cal[0])/dt)
156+
spm_cal_interp = spm_cal[0] + dt*np.arange(n_pts)
157+
f_offset_fit_func = interp1d(spm_cal, f_offset_fit_cal,
158+
fill_value='extrapolate')
159+
f_offset_fit_interp = f_offset_fit_func(spm_cal_interp)
160+
161+
# Integrate frequency offset to detrend IQ_m
162+
f_detrend_interp = np.cumsum(f_offset_fit_interp)*dt
163+
f_detrend_interp_rad = f_detrend_interp * spice.twopi()
164+
f_detrend_rad_func = interp1d(spm_cal_interp, f_detrend_interp_rad,
165+
fill_value='extrapolate')
166+
f_detrend_rad = f_detrend_rad_func(spm_vals)
167+
168+
# Detrend raw measured I and Q
169+
IQ_c = IQ_m*np.exp(-1j*f_detrend_rad)
170+
171+
(rho_km_desired, IQ_c_desired) = resample_IQ(rho_km_vals, IQ_c, dr_km,
172+
dr_km_tol=dr_km_tol)
173+
174+
# Interpolate freespace power (spline fit) to rho_km_desired
175+
p_free_interp_func = interp1d(rho_km_cal, p_free_cal,
176+
fill_value='extrapolate')
177+
p_free = p_free_interp_func(rho_km_desired)
178+
179+
# Construct power from IQ_c_desired and not IQ_m_desired or resampled
180+
# power, because p_free was constructed from downsampled IQ_c. It's
181+
# important to not use IQ_m_desired because I and Q each vary
182+
# sinusoidally, meaning that if you resample too much, they get
183+
# averaged to near zero.
184+
p_norm_vals = (abs(IQ_c_desired)**2)/p_free
185+
phase_rad_vals = np.arctan2(np.imag(IQ_c_desired),
186+
np.real(IQ_c_desired))
187+
188+
spm_desired_func = interp1d(rho_km_geo, spm_geo,
189+
fill_value='extrapolate')
190+
spm_desired = spm_desired_func(rho_km_desired)
191+
192+
self.__set_attributes(rho_km_desired, spm_desired, p_norm_vals,
193+
phase_rad_vals,
194+
rho_km_geo, B_rad_geo, D_km_geo, F_km_geo,
195+
phi_rad_geo, rho_dot_kms_geo,
196+
rho_km_cal, f_sky_pred_cal)
197+
198+
199+
def __set_attributes(self, rho_km_desired, spm_desired, p_norm_vals,
200+
phase_rad_vals,
201+
rho_km_geo, B_rad_geo, D_km_geo, F_km_geo, phi_rad_geo,
202+
rho_dot_kms_geo,
203+
rho_km_cal, f_sky_pred_cal):
204+
"""Private method called by __init__ to set attributes of the
205+
instance"""
206+
207+
self.rho_km_vals = rho_km_desired
208+
self.spm_vals = spm_desired
209+
self.p_norm_vals = p_norm_vals
210+
self.phase_rad_vals = phase_rad_vals
211+
212+
# Ring opening angle at final spacing
213+
B_rad_func = interp1d(rho_km_geo, B_rad_geo, fill_value='extrapolate')
214+
self.B_rad_vals = B_rad_func(rho_km_desired)
215+
216+
# Spacecraft - RIP distance at final spacing
217+
D_km_func = interp1d(rho_km_geo, D_km_geo, fill_value='extrapolate')
218+
self.D_km_vals = D_km_func(rho_km_desired)
219+
220+
# Fresnel scale at final spacing
221+
F_km_func = interp1d(rho_km_geo, F_km_geo, fill_value='extrapolate')
222+
self.F_km_vals = F_km_func(rho_km_desired)
223+
224+
# Sky frequency at final spacing
225+
f_sky_pred_func = interp1d(rho_km_cal, f_sky_pred_cal,
226+
fill_value='extrapolate')
227+
self.f_sky_hz_vals = f_sky_pred_func(rho_km_desired)
228+
229+
# Observed Ring Azimuth at final spacing
230+
phi_rad_func = interp1d(rho_km_geo, phi_rad_geo, fill_value='extrapolate')
231+
self.phi_rad_vals = phi_rad_func(rho_km_desired)
232+
233+
# RIP velocity at final spacing
234+
rho_dot_kms_func = interp1d(rho_km_geo, rho_dot_kms_geo,
235+
fill_value='extrapolate')
236+
self.rho_dot_kms_vals = rho_dot_kms_func(rho_km_desired)
237+
238+
239+
def save_obs(self, obs_file):
240+
"""Save an obs file, which contains all of the attributes in the
241+
instance, and therefore has everything needed for Fresnel Inversion
242+
243+
Args:
244+
obs_file (str): Full path name of obs file to be created
245+
"""
246+
247+
# Save obs file with absolute value of rho dot. With this and
248+
# increasing rho_km_desired, ingress files can be put into
249+
# inversion code without any error checks to differentiate
250+
# from egress
251+
np.savetxt(obs_file,
252+
np.c_[self.rho_km_vals, self.spm_vals,
253+
self.p_norm_vals, self.phase_rad_vals,
254+
self.B_rad_vals, self.D_km_vals, self.F_km_vals,
255+
self.f_sky_hz_vals, self.phi_rad_vals,
256+
abs(self.rho_dot_kms_vals)],
257+
fmt='%32.16f '*10)
258+
259+
260+
if __name__ == '__main__':
261+
pass

0 commit comments

Comments
 (0)