Skip to content

Commit b0fe77b

Browse files
authored
IMAP-Lo requested tweaks (#3293)
* get_star_bin_offset guided by STAR_BIN_OFFSET_BY_SYNC dict; pulled out 2 constants - STAR_END_BINS_TO_EXCLUDE, STAR_MIN_COUNT_THRESHOLD. lo_angle_offset gone * A pointing that straddles the star-sensor enable event now takes the state at its start. * Reading lower/upper uncertainty factors, and generating lower/upper systematic errors, but saving the mean in the existing variable. Updated ancillary files. * failing loudly on unexpected ifb_ctrl_star_sync value in NHK data (should be DS/EN); pre-commit checked * testing for KeyError; reintroduced old ancillaries since some tests depend on it * Building docs on a recent python where we have spiceypy
1 parent d78e4e9 commit b0fe77b

11 files changed

Lines changed: 340 additions & 71 deletions

File tree

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: 2
33
build:
44
os: "ubuntu-22.04"
55
tools:
6-
python: "3.10"
6+
python: "3.12"
77

88
sphinx:
99
configuration: docs/source/conf.py

imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,30 @@ bg_rate_sys_err:
239239
VAR_NOTES: Systematic error in the background count rate from non-ENA (non-heliospheric) sources.
240240
VAR_TYPE: support_data
241241

242+
bg_rate_sys_err_minus:
243+
<<: *default_float32
244+
DEPEND_0: epoch
245+
DICT_KEY: SPASE>Particle>ParticleType:Atom,ParticleQuantity:NumberFlux,Qualifier:Uncertainty,CoordinateSystemName:HAE,CoordinateRepresentation:Spherical
246+
DISPLAY_TYPE: map_image
247+
FIELDNAM: Background Rate Sys Error Lower
248+
FORMAT: F6.3
249+
LABLAXIS: Rate Error Lower
250+
UNITS: count s-1
251+
VAR_NOTES: Lower bound of the systematic error in the background count rate from non-ENA (non-heliospheric) sources.
252+
VAR_TYPE: support_data
253+
254+
bg_rate_sys_err_plus:
255+
<<: *default_float32
256+
DEPEND_0: epoch
257+
DICT_KEY: SPASE>Particle>ParticleType:Atom,ParticleQuantity:NumberFlux,Qualifier:Uncertainty,CoordinateSystemName:HAE,CoordinateRepresentation:Spherical
258+
DISPLAY_TYPE: map_image
259+
FIELDNAM: Background Rate Sys Error Upper
260+
FORMAT: F6.3
261+
LABLAXIS: Rate Error Upper
262+
UNITS: count s-1
263+
VAR_NOTES: Upper bound of the systematic error in the background count rate from non-ENA (non-heliospheric) sources.
264+
VAR_TYPE: support_data
265+
242266
bg_intensity:
243267
<<: *default_float32
244268
DEPEND_0: epoch
@@ -275,6 +299,30 @@ bg_intensity_sys_err:
275299
VAR_NOTES: Non-statistical error in the background intensity from non-ENA (non-heliospheric) sources.
276300
VAR_TYPE: support_data
277301

302+
bg_intensity_sys_err_minus:
303+
<<: *default_float32
304+
DEPEND_0: epoch
305+
DICT_KEY: SPASE>Particle>ParticleType:Atom,ParticleQuantity:NumberFlux,Qualifier:Uncertainty,CoordinateSystemName:HAE,CoordinateRepresentation:Spherical
306+
DISPLAY_TYPE: map_image
307+
FIELDNAM: Background Intensity Non-statistical Error Lower
308+
FORMAT: F8.3
309+
LABLAXIS: Intensity Non-statistical Error Lower
310+
UNITS: cm -2 s -1 sr -1 keV -1
311+
VAR_NOTES: Lower bound of the non-statistical error in the background intensity from non-ENA (non-heliospheric) sources.
312+
VAR_TYPE: support_data
313+
314+
bg_intensity_sys_err_plus:
315+
<<: *default_float32
316+
DEPEND_0: epoch
317+
DICT_KEY: SPASE>Particle>ParticleType:Atom,ParticleQuantity:NumberFlux,Qualifier:Uncertainty,CoordinateSystemName:HAE,CoordinateRepresentation:Spherical
318+
DISPLAY_TYPE: map_image
319+
FIELDNAM: Background Intensity Non-statistical Error Upper
320+
FORMAT: F8.3
321+
LABLAXIS: Intensity Non-statistical Error Upper
322+
UNITS: cm -2 s -1 sr -1 keV -1
323+
VAR_NOTES: Upper bound of the non-statistical error in the background intensity from non-ENA (non-heliospheric) sources.
324+
VAR_TYPE: support_data
325+
278326
ena_count:
279327
<<: *default_float32
280328
DEPEND_0: epoch

imap_processing/ena_maps/utils/naming.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,13 @@ def build_map_var_catdesc(self, support_var_name: str) -> str | None:
203203
"bg_intensity": "Background Inten",
204204
"bg_intensity_stat_uncert": "Background Inten Stat. Unc.",
205205
"bg_intensity_sys_err": "Background Inten Sys. Err.",
206+
"bg_intensity_sys_err_minus": "Background Inten Sys. Err. Lower",
207+
"bg_intensity_sys_err_plus": "Background Inten Sys. Err. Upper",
206208
"bg_rate": "Background Count Rate",
207209
"bg_rate_stat_uncert": "Background Count Rate Stat. Unc.",
208210
"bg_rate_sys_err": "Background Count Rate Sys. Err.",
211+
"bg_rate_sys_err_minus": "Background Count Rate Sys. Err. Lower",
212+
"bg_rate_sys_err_plus": "Background Count Rate Sys. Err. Upper",
209213
"ena_count_rate": "ENA Count Rate",
210214
"ena_count_rate_stat_uncert": "ENA Count Rate Stat. Unc.",
211215
"obs_date": "Mean Observation Date",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
esa_mode,incident_E-Step,Observed_E-Step,Cntr_E,Cntr_E_unc,GF_Trpl_H,GF_Trpl_H_unc_minus,GF_Trpl_H_unc_plus
2+
# [1],[1],[1],[keV],[keV],[cm^2sr keV/keV],[cm^2sr keV/keV],[cm^2sr keV/keV]
3+
0,1,1,0.01633,0.28,4.45E-05,4.03E-05,4.20E-05
4+
0,2,2,0.03047,0.43,5.02E-05,4.53E-05,4.72E-05
5+
0,3,3,0.05576,0.89,6.16E-05,5.58E-05,5.82E-05
6+
0,4,4,0.10626,1.9,7.12E-05,4.51E-05,4.89E-05
7+
0,5,5,0.20004,3.4,8.89E-05,5.85E-05,6.31E-05
8+
0,6,6,0.40496,7.3,1.12E-04,6.58E-05,7.23E-05
9+
0,7,7,0.78729,22,1.43E-04,8.25E-05,9.09E-05
10+
1,1,1,0.01719,0.22,1.05E-04,8.82E-05,9.26E-05
11+
1,2,2,0.03236,0.36,1.22E-04,1.02E-04,1.07E-04
12+
1,3,3,0.05948,0.77,1.44E-04,1.21E-04,1.27E-04
13+
1,4,4,0.11441,1.3,1.73E-04,1.17E-04,1.26E-04
14+
1,5,5,0.2137,3,2.15E-04,1.37E-04,1.49E-04
15+
1,6,6,0.43736,5.3,2.82E-04,1.65E-04,1.81E-04
16+
1,7,7,0.83888,11.7,3.61E-04,2.08E-04,2.29E-04
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
esa_mode,incident_E-Step,Observed_E-Step,Cntr_E,Cntr_E_unc,GF_Trpl_O,GF_Trpl_O_unc_minus,GF_Trpl_O_unc_plus
2+
# [1],[1],[1],[keV],[keV],[cm^2sr keV/keV],[cm^2sr keV/keV],[cm^2sr keV/keV]
3+
0,1,1,0.01919,1.372344105,1.98E-05,1.74E-05,1.81E-05
4+
0,2,2,0.03675,2.537963082,3.18E-05,2.39E-05,2.53E-05
5+
0,3,3,0.07121,6.591339559,5.34E-05,4.05E-05,4.29E-05
6+
0,4,4,0.14141,17.43990907,8.64E-05,6.14E-05,6.56E-05
7+
0,5,5,0.274,35.83900763,1.32E-04,9.07E-05,9.72E-05
8+
0,6,6,0.58503,98.06681395,2.46E-04,1.53E-04,1.67E-04
9+
0,7,7,1.13506,238.1076888,3.81E-04,2.23E-04,2.45E-04
10+
1,1,1,0.02043303552,1.575924599,5.02E-05,4.41E-05,4.61E-05
11+
1,2,2,0.03972,2.953020557,8.13E-05,6.09E-05,6.47E-05
12+
1,3,3,0.07648,7.476494389,1.33E-04,1.01E-04,1.07E-04
13+
1,4,4,0.15353,14.87953071,2.38E-04,1.69E-04,1.80E-04
14+
1,5,5,0.29846,41.48950349,3.87E-04,2.67E-04,2.86E-04
15+
1,6,6,0.61524,83.57629594,6.61E-04,4.11E-04,4.47E-04
16+
1,7,7,1.24282,212.0806857,1.02E-03,6.10E-04,6.67E-04

imap_processing/lo/constants.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ class LoConstants:
5858
# The first matching open interval (low < pivot < high) is used; if none matches,
5959
# THRESHOLD_BG_RATE_RAM_DEFAULT / THRESHOLD_BG_RATE_ANTI_RAM_DEFAULT apply.
6060
PIVOT_ANGLE_THRESHOLDS: ClassVar[dict[tuple[float, float], tuple[float, float]]] = {
61-
(88.0, 92.0): (0.014, 0.007),
62-
(73.0, 77.0): (0.0175, 0.00875),
63-
(103.0, 107.0): (0.0112, 0.0056),
61+
(88.0, 92.0): (0.028, 0.014),
62+
(73.0, 77.0): (0.035, 0.0175),
63+
(103.0, 107.0): (0.0224, 0.0112),
6464
}
6565

6666
# Default background-rate thresholds [counts/s] when no pivot range matches.
@@ -75,3 +75,17 @@ class LoConstants:
7575
# Padding [s] added to begin/end of each goodtime interval to ensure complete
7676
# cycles are covered at interval edges.
7777
GOODTIME_PADDING: float = 2.0
78+
79+
# Star-sensor spin-angle binning offset (fractional bin-index shift used when
80+
# computing sample centers), keyed by the IFB star-sync housekeeping state
81+
# (ifb_ctrl_star_sync). Flight software 4.8 enabled star sync ("EN"),
82+
# switching from binning to the bin center (+0.5) to the left edge (+0.0).
83+
STAR_BIN_OFFSET_BY_SYNC: ClassVar[dict[str | None, float]] = {
84+
"DS": 0.5, # star sync disabled (pre FSW 4.8)
85+
"EN": 0.0, # star sync enabled (FSW 4.8+)
86+
}
87+
88+
# Number of ending bins to exclude from each star-sensor profile average.
89+
STAR_END_BINS_TO_EXCLUDE: int = 2
90+
# Minimum COUNT value for a star-sensor record to be considered valid.
91+
STAR_MIN_COUNT_THRESHOLD: int = 700

imap_processing/lo/l1b/lo_l1b.py

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,7 +1986,7 @@ def split_rate_dataset(
19861986

19871987
def filter_valid_star_records(
19881988
l1a_star: xr.Dataset,
1989-
min_count: int = 700,
1989+
min_count: int = c.STAR_MIN_COUNT_THRESHOLD,
19901990
time_window_offset: float = 0.0,
19911991
time_window_duration: float | None = None,
19921992
) -> np.ndarray:
@@ -2014,7 +2014,7 @@ def filter_valid_star_records(
20142014
valid_mask : np.ndarray
20152015
Boolean array indicating valid records.
20162016
"""
2017-
# Section 5: Acceptance Criteria - COUNT >= 700
2017+
# Section 5: Acceptance Criteria - COUNT >= min_count
20182018
count_mask = l1a_star["count"].values >= min_count
20192019

20202020
# shcoarse is already in MET seconds
@@ -2049,7 +2049,7 @@ def filter_valid_star_records(
20492049
def calculate_star_sensor_profile_for_group(
20502050
data: np.ndarray,
20512051
counts: np.ndarray,
2052-
end_bins_to_exclude: int = 2,
2052+
end_bins_to_exclude: int = c.STAR_END_BINS_TO_EXCLUDE,
20532053
) -> tuple[np.ndarray, np.ndarray]:
20542054
"""
20552055
Calculate averaged star sensor amplitude profile for a group of records.
@@ -2098,14 +2098,63 @@ def calculate_star_sensor_profile_for_group(
20982098
return avg_amplitude, count_array
20992099

21002100

2101+
def get_star_bin_offset(l1b_nhk: xr.Dataset, reference_epoch: int) -> float:
2102+
"""
2103+
Determine the star-sensor binning offset from the IFB star-sync state.
2104+
2105+
Reads ``ifb_ctrl_star_sync`` from the NHK housekeeping at the record nearest
2106+
on or before ``reference_epoch`` and maps it to a binning offset via
2107+
``LoConstants.STAR_BIN_OFFSET_BY_SYNC``.
2108+
2109+
Parameters
2110+
----------
2111+
l1b_nhk : xr.Dataset
2112+
L1B NHK dataset containing ``ifb_ctrl_star_sync`` and an ``epoch``
2113+
coordinate (TT2000 nanoseconds since J2000).
2114+
reference_epoch : int
2115+
Epoch at which to evaluate the sync state, in TT2000 nanoseconds since
2116+
J2000. The NHK record in effect at or before this time is used.
2117+
2118+
Returns
2119+
-------
2120+
bin_offset : float
2121+
Fractional bin-index offset to use when computing sample spin-angle
2122+
centers.
2123+
2124+
Raises
2125+
------
2126+
KeyError
2127+
If ``ifb_ctrl_star_sync`` is not present in ``l1b_nhk``.
2128+
"""
2129+
if "ifb_ctrl_star_sync" not in l1b_nhk:
2130+
raise KeyError(
2131+
"ifb_ctrl_star_sync field not found in L1B NHK dataset. "
2132+
"Cannot determine star-sensor binning offset."
2133+
)
2134+
2135+
nhk_epoch = l1b_nhk["epoch"].values
2136+
sync_state = l1b_nhk["ifb_ctrl_star_sync"].values
2137+
2138+
# Use the housekeeping record in effect at the reference epoch (the last
2139+
# NHK sample at or before it), clamping to the first sample if the reference
2140+
# epoch falls before NHK coverage.
2141+
idx = max(int(np.searchsorted(nhk_epoch, reference_epoch, side="right")) - 1, 0)
2142+
state = str(sync_state[idx])
2143+
2144+
offset = c.STAR_BIN_OFFSET_BY_SYNC[state]
2145+
logger.info(f"Star sync state '{state}' -> bin offset {offset}")
2146+
return offset
2147+
2148+
21012149
def calculate_star_sensor_profiles_by_group(
21022150
l1a_star: xr.Dataset,
21032151
sampling_cadence: float,
21042152
spin_period: float,
21052153
group_size: int = 64,
21062154
start_angle_offset: float = 62.0,
2107-
end_bins_to_exclude: int = 2,
2108-
min_count_threshold: int = 700,
2155+
end_bins_to_exclude: int = c.STAR_END_BINS_TO_EXCLUDE,
2156+
min_count_threshold: int = c.STAR_MIN_COUNT_THRESHOLD,
2157+
bin_offset: float = 0.5,
21092158
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
21102159
"""
21112160
Calculate averaged star sensor amplitude profiles for groups of records.
@@ -2129,6 +2178,10 @@ def calculate_star_sensor_profiles_by_group(
21292178
Number of ending bins to exclude from each average (default: 2).
21302179
min_count_threshold : int
21312180
Minimum COUNT value for valid record (default: 700).
2181+
bin_offset : float
2182+
Fractional offset applied to bin indices when computing sample
2183+
spin-angle centers (default: 0.5). Use 0.5 to bin to the bin center
2184+
and 0.0 to bin to the left edge.
21322185
21332186
Returns
21342187
-------
@@ -2152,7 +2205,7 @@ def calculate_star_sensor_profiles_by_group(
21522205
# Calculate spin angles (same for all groups)
21532206
deg_per_bin = 360.0 * (sampling_cadence / 1000.0) / spin_period
21542207
bin_indices = np.arange(720)
2155-
sample_centers = (bin_indices + 0.5) * deg_per_bin
2208+
sample_centers = (bin_indices + bin_offset) * deg_per_bin
21562209
spin_angle = (start_angle_offset + sample_centers) % 360.0
21572210

21582211
if n_valid == 0:
@@ -2282,13 +2335,20 @@ def l1b_star(
22822335
logger.info(f"Using spin duration from spin data: {spin_duration:.6f} s")
22832336

22842337
# TODO: Read from ancillary config file when available
2285-
lo_angle_offset = 2.0
2286-
sc_to_inst_angle_offset = (
2287-
360 * get_spacecraft_to_instrument_spin_phase_offset(SpiceFrame.IMAP_LO)
2288-
+ lo_angle_offset
2338+
sc_to_inst_angle_offset = 360 * get_spacecraft_to_instrument_spin_phase_offset(
2339+
SpiceFrame.IMAP_LO
22892340
)
2290-
end_bins_to_exclude = 2
2291-
min_count_threshold = 700
2341+
end_bins_to_exclude = c.STAR_END_BINS_TO_EXCLUDE
2342+
min_count_threshold = c.STAR_MIN_COUNT_THRESHOLD
2343+
2344+
# Global epoch times from L1A data (used for start_doy/end_doy below).
2345+
global_start_epoch = l1a_star["epoch"].values[0]
2346+
global_end_epoch = l1a_star["epoch"].values[-1]
2347+
2348+
# Select the star-sensor binning convention from the IFB star-sync state in
2349+
# housekeeping. Evaluate at the earliest star record's epoch so a pointing that
2350+
# spans the `EN` event uses the value corresponding to the state at its start.
2351+
bin_offset = get_star_bin_offset(l1b_nhk, int(global_start_epoch))
22922352

22932353
# Calculate profiles for each 64-spin group
22942354
(
@@ -2304,12 +2364,9 @@ def l1b_star(
23042364
start_angle_offset=sc_to_inst_angle_offset,
23052365
end_bins_to_exclude=end_bins_to_exclude,
23062366
min_count_threshold=min_count_threshold,
2367+
bin_offset=bin_offset,
23072368
)
23082369

2309-
# Get global epoch times from L1A data for start_doy and end_doy
2310-
global_start_epoch = l1a_star["epoch"].values[0]
2311-
global_end_epoch = l1a_star["epoch"].values[-1]
2312-
23132370
# Create dataset with spin_angle as coordinate and multiple epochs
23142371
group_epochs = met_to_ttj2000ns(group_mets)
23152372
l1b_star_ds = xr.Dataset(
@@ -2375,7 +2432,6 @@ def l1b_star(
23752432
l1b_star_ds.attrs["pointing_mid_met"] = pointing_mid_met
23762433
l1b_star_ds.attrs["sampling_cadence_ms"] = sampling_cadence
23772434
l1b_star_ds.attrs["spin_duration_sec"] = spin_duration
2378-
l1b_star_ds.attrs["lo_angle_offset_deg"] = lo_angle_offset
23792435
l1b_star_ds.attrs["end_bins_excluded"] = end_bins_to_exclude
23802436
l1b_star_ds.attrs["min_count_threshold"] = min_count_threshold
23812437
l1b_star_ds.attrs["group_size"] = group_size

0 commit comments

Comments
 (0)