imap-processing 0.18.0__py3-none-any.whl → 0.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imap-processing might be problematic. Click here for more details.
- imap_processing/_version.py +2 -2
- imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +301 -274
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +28 -28
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
- imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
- imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
- imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
- imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
- imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +85 -2
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +12 -4
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
- imap_processing/cli.py +95 -41
- imap_processing/codice/codice_l1a.py +131 -31
- imap_processing/codice/codice_l2.py +118 -10
- imap_processing/codice/constants.py +740 -595
- imap_processing/decom.py +1 -4
- imap_processing/ena_maps/ena_maps.py +32 -25
- imap_processing/ena_maps/utils/naming.py +8 -2
- imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
- imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
- imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
- imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
- imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
- imap_processing/glows/l1b/glows_l1b.py +99 -9
- imap_processing/glows/l1b/glows_l1b_data.py +350 -38
- imap_processing/glows/l2/glows_l2.py +11 -0
- imap_processing/hi/hi_l1a.py +124 -3
- imap_processing/hi/hi_l1b.py +154 -71
- imap_processing/hi/hi_l2.py +84 -51
- imap_processing/hi/utils.py +153 -8
- imap_processing/hit/l0/constants.py +3 -0
- imap_processing/hit/l0/decom_hit.py +3 -6
- imap_processing/hit/l1a/hit_l1a.py +311 -21
- imap_processing/hit/l1b/hit_l1b.py +54 -126
- imap_processing/hit/l2/hit_l2.py +6 -6
- imap_processing/ialirt/calculate_ingest.py +219 -0
- imap_processing/ialirt/constants.py +12 -2
- imap_processing/ialirt/generate_coverage.py +15 -2
- imap_processing/ialirt/l0/ialirt_spice.py +5 -2
- imap_processing/ialirt/l0/parse_mag.py +293 -42
- imap_processing/ialirt/l0/process_hit.py +5 -3
- imap_processing/ialirt/l0/process_swapi.py +41 -25
- imap_processing/ialirt/process_ephemeris.py +70 -14
- imap_processing/idex/idex_l0.py +2 -2
- imap_processing/idex/idex_l1a.py +2 -3
- imap_processing/idex/idex_l1b.py +2 -3
- imap_processing/idex/idex_l2a.py +130 -4
- imap_processing/idex/idex_l2b.py +158 -143
- imap_processing/idex/idex_utils.py +1 -3
- imap_processing/lo/l0/lo_science.py +25 -24
- imap_processing/lo/l1b/lo_l1b.py +3 -3
- imap_processing/lo/l1c/lo_l1c.py +116 -50
- imap_processing/lo/l2/lo_l2.py +29 -29
- imap_processing/lo/lo_ancillary.py +55 -0
- imap_processing/mag/l1a/mag_l1a.py +1 -0
- imap_processing/mag/l1a/mag_l1a_data.py +26 -0
- imap_processing/mag/l1b/mag_l1b.py +3 -2
- imap_processing/mag/l1c/interpolation_methods.py +14 -15
- imap_processing/mag/l1c/mag_l1c.py +23 -6
- imap_processing/mag/l1d/mag_l1d.py +57 -14
- imap_processing/mag/l1d/mag_l1d_data.py +167 -30
- imap_processing/mag/l2/mag_l2_data.py +10 -2
- imap_processing/quality_flags.py +9 -1
- imap_processing/spice/geometry.py +76 -33
- imap_processing/spice/pointing_frame.py +0 -6
- imap_processing/spice/repoint.py +29 -2
- imap_processing/spice/spin.py +28 -8
- imap_processing/spice/time.py +12 -22
- imap_processing/swapi/l1/swapi_l1.py +10 -4
- imap_processing/swapi/l2/swapi_l2.py +15 -17
- imap_processing/swe/l1b/swe_l1b.py +1 -2
- imap_processing/ultra/constants.py +1 -24
- imap_processing/ultra/l0/ultra_utils.py +9 -11
- imap_processing/ultra/l1a/ultra_l1a.py +1 -2
- imap_processing/ultra/l1b/cullingmask.py +6 -3
- imap_processing/ultra/l1b/de.py +81 -23
- imap_processing/ultra/l1b/extendedspin.py +13 -10
- imap_processing/ultra/l1b/lookup_utils.py +281 -28
- imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
- imap_processing/ultra/l1b/ultra_l1b_culling.py +161 -3
- imap_processing/ultra/l1b/ultra_l1b_extended.py +253 -47
- imap_processing/ultra/l1c/helio_pset.py +97 -24
- imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +83 -16
- imap_processing/ultra/l1c/ultra_l1c.py +6 -2
- imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +385 -277
- imap_processing/ultra/l2/ultra_l2.py +0 -1
- imap_processing/ultra/utils/ultra_l1_utils.py +28 -3
- imap_processing/utils.py +3 -4
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +2 -2
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +102 -95
- imap_processing/idex/idex_l2c.py +0 -84
- imap_processing/spice/kernels.py +0 -187
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Union
|
|
6
5
|
|
|
7
6
|
import numpy as np
|
|
8
7
|
import xarray as xr
|
|
9
8
|
|
|
9
|
+
from imap_processing.ialirt.l0.ialirt_spice import (
|
|
10
|
+
transform_instrument_vectors_to_inertial,
|
|
11
|
+
)
|
|
10
12
|
from imap_processing.ialirt.l0.mag_l0_ialirt_data import (
|
|
11
13
|
Packet0,
|
|
12
14
|
Packet1,
|
|
@@ -22,7 +24,13 @@ from imap_processing.mag.l1b.mag_l1b import (
|
|
|
22
24
|
)
|
|
23
25
|
from imap_processing.mag.l1d.mag_l1d_data import MagL1d
|
|
24
26
|
from imap_processing.mag.l2.mag_l2_data import MagL2L1dBase
|
|
25
|
-
from imap_processing.spice.
|
|
27
|
+
from imap_processing.spice.geometry import (
|
|
28
|
+
SpiceFrame,
|
|
29
|
+
cartesian_to_spherical,
|
|
30
|
+
frame_transform,
|
|
31
|
+
spherical_to_cartesian,
|
|
32
|
+
)
|
|
33
|
+
from imap_processing.spice.time import met_to_ttj2000ns, met_to_utc, ttj2000ns_to_et
|
|
26
34
|
|
|
27
35
|
logger = logging.getLogger(__name__)
|
|
28
36
|
|
|
@@ -195,7 +203,7 @@ def get_time(
|
|
|
195
203
|
(grouped_data["group"] == group).values
|
|
196
204
|
][pkt_counter == 2]
|
|
197
205
|
|
|
198
|
-
time_data: dict[str,
|
|
206
|
+
time_data: dict[str, int | float] = {
|
|
199
207
|
"pri_coarsetm": int(pri_coarsetm.item()),
|
|
200
208
|
"pri_fintm": int(pri_fintm.item()),
|
|
201
209
|
"sec_coarsetm": int(sec_coarsetm.item()),
|
|
@@ -290,7 +298,6 @@ def calculate_l1b(
|
|
|
290
298
|
|
|
291
299
|
def calibrate_and_offset_vectors(
|
|
292
300
|
vectors: np.ndarray,
|
|
293
|
-
range_vals: np.ndarray,
|
|
294
301
|
calibration: np.ndarray,
|
|
295
302
|
offsets: np.ndarray,
|
|
296
303
|
is_magi: bool = False,
|
|
@@ -301,9 +308,7 @@ def calibrate_and_offset_vectors(
|
|
|
301
308
|
Parameters
|
|
302
309
|
----------
|
|
303
310
|
vectors : np.ndarray
|
|
304
|
-
Raw magnetic vectors, shape (n,
|
|
305
|
-
range_vals : np.ndarray
|
|
306
|
-
Range indices for each vector, shape (n). Values 0–3.
|
|
311
|
+
Raw magnetic vectors, shape (n, 4).
|
|
307
312
|
calibration : np.ndarray
|
|
308
313
|
Calibration matrix, shape (3, 3, 4).
|
|
309
314
|
offsets : np.ndarray
|
|
@@ -319,11 +324,9 @@ def calibrate_and_offset_vectors(
|
|
|
319
324
|
calibrated_and_offset_vectors : np.ndarray
|
|
320
325
|
Calibrated and offset vectors, shape (n, 3).
|
|
321
326
|
"""
|
|
322
|
-
# Append range as 4th column
|
|
323
|
-
vec_plus_range = np.concatenate((vectors, range_vals[:, np.newaxis]), axis=1)
|
|
324
|
-
|
|
325
327
|
# Apply calibration matrix -> (n,4)
|
|
326
|
-
|
|
328
|
+
# apply_calibration_offset_single_vector
|
|
329
|
+
calibrated = MagL2L1dBase.apply_calibration(vectors.reshape(1, 4), calibration)
|
|
327
330
|
|
|
328
331
|
# Apply offsets per vector
|
|
329
332
|
# vec shape (4)
|
|
@@ -338,9 +341,193 @@ def calibrate_and_offset_vectors(
|
|
|
338
341
|
return calibrated[:, :3]
|
|
339
342
|
|
|
340
343
|
|
|
344
|
+
def apply_gradiometry_correction(
|
|
345
|
+
mago_vectors_eclipj2000: np.ndarray,
|
|
346
|
+
mago_time_data: np.ndarray,
|
|
347
|
+
magi_vectors_eclipj2000: np.ndarray,
|
|
348
|
+
magi_time_data: np.ndarray,
|
|
349
|
+
gradiometer_factor: np.ndarray,
|
|
350
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
351
|
+
"""
|
|
352
|
+
Align MAGi to MAGo timestamps and apply gradiometry correction.
|
|
353
|
+
|
|
354
|
+
Parameters
|
|
355
|
+
----------
|
|
356
|
+
mago_vectors_eclipj2000 : np.ndarray
|
|
357
|
+
MAGo vectors in inertial frame, shape (N, 3).
|
|
358
|
+
mago_time_data : np.ndarray
|
|
359
|
+
Time for primary sensor, shape (N, 3).
|
|
360
|
+
magi_vectors_eclipj2000 : np.ndarray
|
|
361
|
+
MAGi vectors in inertial frame, shape (M, 3).
|
|
362
|
+
magi_time_data : np.ndarray
|
|
363
|
+
Time for secondary sensor, shape (N, 3).
|
|
364
|
+
gradiometer_factor : np.ndarray
|
|
365
|
+
A (3,3) element matrix to scale and rotate the gradiometer offsets.
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
mago_corrected : np.ndarray
|
|
370
|
+
Corrected MAGo vectors in inertial frame, shape (N, 3).
|
|
371
|
+
magnitude : np.ndarray
|
|
372
|
+
Magnitude of corrected MAGo vectors, shape (N,).
|
|
373
|
+
"""
|
|
374
|
+
gradiometry_offsets = MagL1d.calculate_gradiometry_offsets(
|
|
375
|
+
mago_vectors_eclipj2000,
|
|
376
|
+
mago_time_data,
|
|
377
|
+
magi_vectors_eclipj2000,
|
|
378
|
+
magi_time_data,
|
|
379
|
+
)
|
|
380
|
+
mago_corrected = MagL1d.apply_gradiometry_offsets(
|
|
381
|
+
gradiometry_offsets, mago_vectors_eclipj2000, gradiometer_factor
|
|
382
|
+
)
|
|
383
|
+
magnitude = np.linalg.norm(mago_corrected, axis=-1).squeeze()
|
|
384
|
+
|
|
385
|
+
return mago_corrected, magnitude
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def transform_to_inertial(
|
|
389
|
+
sc_spin_phase_rad: np.ndarray,
|
|
390
|
+
sc_inertial_right: np.ndarray,
|
|
391
|
+
sc_inertial_decline: np.ndarray,
|
|
392
|
+
attitude_time: np.ndarray,
|
|
393
|
+
target_time: float,
|
|
394
|
+
mag_vector: np.ndarray,
|
|
395
|
+
) -> np.ndarray:
|
|
396
|
+
"""
|
|
397
|
+
Transform vector to ECLIPJ2000.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
sc_spin_phase_rad : numpy.ndarray
|
|
402
|
+
Spin phase for 4 packets 0 to 2π radians, shape (4).
|
|
403
|
+
sc_inertial_right : numpy.ndarray
|
|
404
|
+
Inertial right ascension for 4 packets 0 to 2π radians, shape (4).
|
|
405
|
+
sc_inertial_decline : numpy.ndarray
|
|
406
|
+
Inertial declination for 4 packets -π/2 to π/2 radians, shape (4).
|
|
407
|
+
attitude_time : np.ndarray
|
|
408
|
+
Timestamps for the 4 packets.
|
|
409
|
+
Example: test_met = grouped_data["met"][
|
|
410
|
+
(grouped_data["group"] == group).values].
|
|
411
|
+
ttj2000ns = met_to_ttj2000ns(test_met.values).
|
|
412
|
+
target_time : float
|
|
413
|
+
Time at which to apply the transformation.
|
|
414
|
+
Will be primary_epoch (mago vector) or secondary_epoch (magi vector).
|
|
415
|
+
Example: time_data['primary_epoch'].
|
|
416
|
+
mag_vector : numpy.ndarray
|
|
417
|
+
Vector, shape (3).
|
|
418
|
+
|
|
419
|
+
Returns
|
|
420
|
+
-------
|
|
421
|
+
inertial_vector : np.ndarray
|
|
422
|
+
Transformed vector in the ECLIPJ2000 frame, shape (3,).
|
|
423
|
+
|
|
424
|
+
Notes
|
|
425
|
+
-----
|
|
426
|
+
The MAG vectors are calculated based on 4 packets,
|
|
427
|
+
each of which contains its own spin phase,
|
|
428
|
+
inertial right ascension, and inertial decline.
|
|
429
|
+
"""
|
|
430
|
+
if target_time < attitude_time.min() or target_time > attitude_time.max():
|
|
431
|
+
logger.warning(
|
|
432
|
+
f"target_time {target_time} is outside attitude_time bounds "
|
|
433
|
+
f"[{attitude_time.min()}, {attitude_time.max()}]; using edge values."
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# Get sort order based on attitude_time
|
|
437
|
+
sort_idx = np.argsort(attitude_time)
|
|
438
|
+
|
|
439
|
+
# Sort all arrays accordingly
|
|
440
|
+
attitude_time = attitude_time[sort_idx]
|
|
441
|
+
sc_spin_phase_rad = sc_spin_phase_rad[sort_idx]
|
|
442
|
+
sc_inertial_right = sc_inertial_right[sort_idx]
|
|
443
|
+
sc_inertial_decline = sc_inertial_decline[sort_idx]
|
|
444
|
+
|
|
445
|
+
# Interpolate spin phase, RA, and Dec at target_time
|
|
446
|
+
# Convert RA/Dec to unit cartesian vectors
|
|
447
|
+
spherical_coords = np.stack(
|
|
448
|
+
[
|
|
449
|
+
np.ones_like(sc_inertial_right),
|
|
450
|
+
np.degrees(sc_inertial_right),
|
|
451
|
+
np.degrees(sc_inertial_decline),
|
|
452
|
+
],
|
|
453
|
+
axis=-1,
|
|
454
|
+
)
|
|
455
|
+
vecs = spherical_to_cartesian(spherical_coords)
|
|
456
|
+
|
|
457
|
+
# Interpolate in Cartesian space
|
|
458
|
+
vx = np.interp(target_time, attitude_time, vecs[:, 0])
|
|
459
|
+
vy = np.interp(target_time, attitude_time, vecs[:, 1])
|
|
460
|
+
vz = np.interp(target_time, attitude_time, vecs[:, 2])
|
|
461
|
+
v_interp = np.array([vx, vy, vz])
|
|
462
|
+
# Normalize vector so that its magnitude is 1.
|
|
463
|
+
v_interp /= np.linalg.norm(v_interp)
|
|
464
|
+
|
|
465
|
+
# Convert back to spherical
|
|
466
|
+
ra_dec = cartesian_to_spherical(v_interp)
|
|
467
|
+
ra_deg = ra_dec[1]
|
|
468
|
+
dec_deg = ra_dec[2]
|
|
469
|
+
|
|
470
|
+
# Account for discontinuities in spin phase.
|
|
471
|
+
spin_phase_unwrapped = np.unwrap(sc_spin_phase_rad)
|
|
472
|
+
spin_phase_interp = np.interp(target_time, attitude_time, spin_phase_unwrapped)
|
|
473
|
+
spin_phase_deg = np.degrees(spin_phase_interp) % 360
|
|
474
|
+
|
|
475
|
+
# Transform each into ECLIPJ2000
|
|
476
|
+
inertial_vector = transform_instrument_vectors_to_inertial(
|
|
477
|
+
np.asarray(mag_vector).reshape(1, 3),
|
|
478
|
+
np.array([spin_phase_deg]),
|
|
479
|
+
np.array([ra_deg]),
|
|
480
|
+
np.array([dec_deg]),
|
|
481
|
+
)[0]
|
|
482
|
+
|
|
483
|
+
return inertial_vector
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def transform_to_frames(
|
|
487
|
+
target_time: np.ndarray,
|
|
488
|
+
inertial_vector: np.ndarray,
|
|
489
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
490
|
+
"""
|
|
491
|
+
Transform vector to different frames.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
target_time : np.ndarray
|
|
496
|
+
Time at which to apply the transformation.
|
|
497
|
+
Will be primary_epoch (mago vector).
|
|
498
|
+
Example: time_data['primary_epoch'].
|
|
499
|
+
inertial_vector : np.ndarray
|
|
500
|
+
Transformed vector in the ECLIPJ2000 frame, shape (3,).
|
|
501
|
+
|
|
502
|
+
Returns
|
|
503
|
+
-------
|
|
504
|
+
gse_vector : np.ndarray
|
|
505
|
+
Transformed vector in the GSE frame, shape (3,).
|
|
506
|
+
gsm_vector : np.ndarray
|
|
507
|
+
Transformed vector in the GSM frame, shape (3,).
|
|
508
|
+
rtn_vector : np.ndarray
|
|
509
|
+
Transformed vector in the RTN frame, shape (3,).
|
|
510
|
+
"""
|
|
511
|
+
et_target_time = ttj2000ns_to_et(target_time)
|
|
512
|
+
|
|
513
|
+
gse_vector = frame_transform(
|
|
514
|
+
et_target_time, inertial_vector, SpiceFrame.ECLIPJ2000, SpiceFrame.IMAP_GSE
|
|
515
|
+
)
|
|
516
|
+
gsm_vector = frame_transform(
|
|
517
|
+
et_target_time, inertial_vector, SpiceFrame.ECLIPJ2000, SpiceFrame.IMAP_GSM
|
|
518
|
+
)
|
|
519
|
+
rtn_vector = frame_transform(
|
|
520
|
+
et_target_time, inertial_vector, SpiceFrame.ECLIPJ2000, SpiceFrame.IMAP_RTN
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
return gse_vector, gsm_vector, rtn_vector
|
|
524
|
+
|
|
525
|
+
|
|
341
526
|
def process_packet(
|
|
342
|
-
accumulated_data: xr.Dataset,
|
|
343
|
-
|
|
527
|
+
accumulated_data: xr.Dataset,
|
|
528
|
+
engineering_calibration_dataset: xr.Dataset,
|
|
529
|
+
l1d_calibration_dataset: xr.Dataset,
|
|
530
|
+
) -> list[dict]:
|
|
344
531
|
"""
|
|
345
532
|
Parse the MAG packets.
|
|
346
533
|
|
|
@@ -348,8 +535,10 @@ def process_packet(
|
|
|
348
535
|
----------
|
|
349
536
|
accumulated_data : xr.Dataset
|
|
350
537
|
Packets dataset accumulated over 1 min.
|
|
351
|
-
|
|
352
|
-
|
|
538
|
+
engineering_calibration_dataset : xr.Dataset
|
|
539
|
+
Engineering calibration dataset.
|
|
540
|
+
l1d_calibration_dataset : xr.Dataset
|
|
541
|
+
L1D calibration dataset.
|
|
353
542
|
|
|
354
543
|
Returns
|
|
355
544
|
-------
|
|
@@ -375,8 +564,12 @@ def process_packet(
|
|
|
375
564
|
grouped_data = find_groups(accumulated_data, (0, 3), "pkt_counter", "met")
|
|
376
565
|
|
|
377
566
|
unique_groups = np.unique(grouped_data["group"])
|
|
378
|
-
l1b_data = []
|
|
379
567
|
mag_data = []
|
|
568
|
+
met_all = []
|
|
569
|
+
mago_vectors_all = []
|
|
570
|
+
mago_times_all = []
|
|
571
|
+
magi_vectors_all = []
|
|
572
|
+
magi_times_all = []
|
|
380
573
|
|
|
381
574
|
for group in unique_groups:
|
|
382
575
|
# Get status values for each group.
|
|
@@ -412,7 +605,7 @@ def process_packet(
|
|
|
412
605
|
pkt_counter,
|
|
413
606
|
science_data,
|
|
414
607
|
status_data,
|
|
415
|
-
|
|
608
|
+
engineering_calibration_dataset,
|
|
416
609
|
)
|
|
417
610
|
|
|
418
611
|
# Note: primary = MAGo, secondary = MAGi.
|
|
@@ -423,41 +616,99 @@ def process_packet(
|
|
|
423
616
|
if status_data["sec_isvalid"] == 0:
|
|
424
617
|
updated_vector_magi = np.full(4, -32768)
|
|
425
618
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
619
|
+
mago_calibration = l1d_calibration_dataset["URFTOORFO"][0]
|
|
620
|
+
magi_calibration = l1d_calibration_dataset["URFTOORFI"][0]
|
|
621
|
+
offsets = l1d_calibration_dataset["offsets"][0]
|
|
622
|
+
|
|
623
|
+
mago_out = calibrate_and_offset_vectors(
|
|
624
|
+
updated_vector_mago, mago_calibration, offsets, is_magi=False
|
|
625
|
+
)
|
|
626
|
+
magi_out = calibrate_and_offset_vectors(
|
|
627
|
+
updated_vector_magi, magi_calibration, offsets, is_magi=True
|
|
628
|
+
)
|
|
629
|
+
sc_spin_phase_rad = grouped_data["sc_spin_phase"][
|
|
630
|
+
(grouped_data["group"] == group).values
|
|
631
|
+
]
|
|
632
|
+
sc_inertial_right = grouped_data["sc_inertial_right"][
|
|
633
|
+
(grouped_data["group"] == group).values
|
|
634
|
+
]
|
|
635
|
+
sc_inertial_decline = grouped_data["sc_inertial_decline"][
|
|
636
|
+
(grouped_data["group"] == group).values
|
|
637
|
+
]
|
|
638
|
+
|
|
639
|
+
attitude_time = met_to_ttj2000ns(
|
|
640
|
+
grouped_data["met"][(grouped_data["group"] == group).values]
|
|
435
641
|
)
|
|
436
642
|
|
|
437
|
-
|
|
643
|
+
# Convert to ECLIPJ2000 frame.
|
|
644
|
+
mago_inertial_vector = transform_to_inertial(
|
|
645
|
+
sc_spin_phase_rad.values,
|
|
646
|
+
sc_inertial_right.values,
|
|
647
|
+
sc_inertial_decline.values,
|
|
648
|
+
attitude_time,
|
|
649
|
+
time_data["primary_epoch"],
|
|
650
|
+
mago_out,
|
|
651
|
+
)
|
|
652
|
+
magi_inertial_vector = transform_to_inertial(
|
|
653
|
+
sc_spin_phase_rad.values,
|
|
654
|
+
sc_inertial_right.values,
|
|
655
|
+
sc_inertial_decline.values,
|
|
656
|
+
attitude_time,
|
|
657
|
+
time_data["secondary_epoch"],
|
|
658
|
+
magi_out,
|
|
659
|
+
)
|
|
438
660
|
|
|
439
|
-
# Placeholder for real data.
|
|
440
661
|
met = grouped_data["met"][(grouped_data["group"] == group).values]
|
|
662
|
+
met_all.append(met.values[0])
|
|
663
|
+
mago_times_all.append(time_data["primary_epoch"])
|
|
664
|
+
mago_vectors_all.append(mago_inertial_vector)
|
|
665
|
+
magi_vectors_all.append(magi_inertial_vector)
|
|
666
|
+
magi_times_all.append(time_data["secondary_epoch"])
|
|
667
|
+
|
|
668
|
+
mago_corrected, magnitude = apply_gradiometry_correction(
|
|
669
|
+
np.array(mago_vectors_all),
|
|
670
|
+
np.array(mago_times_all),
|
|
671
|
+
np.array(magi_vectors_all),
|
|
672
|
+
np.array(magi_times_all),
|
|
673
|
+
l1d_calibration_dataset["gradiometer_factor"].values.squeeze(),
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
gse_vector, gsm_vector, rtn_vector = transform_to_frames(
|
|
677
|
+
np.array(mago_times_all), mago_corrected
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
spherical = cartesian_to_spherical(gsm_vector)
|
|
681
|
+
phi_gsm = spherical[:, 1]
|
|
682
|
+
theta_gsm = spherical[:, 2]
|
|
683
|
+
|
|
684
|
+
spherical = cartesian_to_spherical(gse_vector)
|
|
685
|
+
phi_gse = spherical[:, 1]
|
|
686
|
+
theta_gse = spherical[:, 2]
|
|
687
|
+
|
|
688
|
+
# Omit the first value since we expect it to be extrapolated.
|
|
689
|
+
for i in range(len(mago_corrected)):
|
|
690
|
+
if i == 0:
|
|
691
|
+
continue
|
|
692
|
+
|
|
441
693
|
mag_data.append(
|
|
442
694
|
{
|
|
443
695
|
"apid": 478,
|
|
444
|
-
"met": int(
|
|
445
|
-
"met_in_utc": met_to_utc(
|
|
446
|
-
"ttj2000ns": int(met_to_ttj2000ns(
|
|
447
|
-
|
|
448
|
-
"
|
|
449
|
-
"
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
-
"
|
|
453
|
-
"
|
|
454
|
-
"
|
|
455
|
-
"
|
|
456
|
-
"mag_theta_B_GSE": Decimal("0.0"),
|
|
696
|
+
"met": int(met_all[i]),
|
|
697
|
+
"met_in_utc": met_to_utc(met_all[i]).split(".")[0],
|
|
698
|
+
"ttj2000ns": int(met_to_ttj2000ns(met_all[i])),
|
|
699
|
+
"mag_epoch": int(mago_times_all[i]),
|
|
700
|
+
"mag_B_GSE": [Decimal(str(v)) for v in gse_vector[i]],
|
|
701
|
+
"mag_B_GSM": [Decimal(str(v)) for v in gsm_vector[i]],
|
|
702
|
+
"mag_B_RTN": [Decimal(str(v)) for v in rtn_vector[i]],
|
|
703
|
+
"mag_B_magnitude": Decimal(str(magnitude[i])),
|
|
704
|
+
"mag_phi_B_GSM": Decimal(str(phi_gsm[i])),
|
|
705
|
+
"mag_theta_B_GSM": Decimal(str(theta_gsm[i])),
|
|
706
|
+
"mag_phi_B_GSE": Decimal(str(phi_gse[i])),
|
|
707
|
+
"mag_theta_B_GSE": Decimal(str(theta_gse[i])),
|
|
457
708
|
}
|
|
458
709
|
)
|
|
459
710
|
|
|
460
|
-
return mag_data
|
|
711
|
+
return mag_data
|
|
461
712
|
|
|
462
713
|
|
|
463
714
|
def retrieve_matrix_from_single_l1b_calibration(
|
|
@@ -91,18 +91,20 @@ def create_l1(
|
|
|
91
91
|
fast_rate_1_dict = {
|
|
92
92
|
prefix: value
|
|
93
93
|
for prefix, value in zip(
|
|
94
|
-
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_1"], fast_rate_1.data
|
|
94
|
+
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_1"], fast_rate_1.data, strict=False
|
|
95
95
|
)
|
|
96
96
|
}
|
|
97
97
|
fast_rate_2_dict = {
|
|
98
98
|
prefix: value
|
|
99
99
|
for prefix, value in zip(
|
|
100
|
-
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_2"], fast_rate_2.data
|
|
100
|
+
HIT_PREFIX_TO_RATE_TYPE["FAST_RATE_2"], fast_rate_2.data, strict=False
|
|
101
101
|
)
|
|
102
102
|
}
|
|
103
103
|
slow_rate_dict = {
|
|
104
104
|
prefix: value
|
|
105
|
-
for prefix, value in zip(
|
|
105
|
+
for prefix, value in zip(
|
|
106
|
+
HIT_PREFIX_TO_RATE_TYPE["SLOW_RATE"], slow_rate.data, strict=False
|
|
107
|
+
)
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
l1 = {**fast_rate_1_dict, **fast_rate_2_dict, **slow_rate_dict}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Optional
|
|
6
5
|
|
|
7
6
|
import numpy as np
|
|
8
7
|
import pandas as pd
|
|
@@ -10,13 +9,12 @@ import xarray as xr
|
|
|
10
9
|
from scipy.optimize import curve_fit
|
|
11
10
|
from scipy.special import erf
|
|
12
11
|
|
|
13
|
-
from imap_processing import imap_module_directory
|
|
14
12
|
from imap_processing.ialirt.constants import IalirtSwapiConstants as Consts
|
|
15
13
|
from imap_processing.ialirt.utils.grouping import find_groups
|
|
16
14
|
from imap_processing.ialirt.utils.time import calculate_time
|
|
17
15
|
from imap_processing.spice.time import met_to_ttj2000ns, met_to_utc
|
|
18
16
|
from imap_processing.swapi.l1.swapi_l1 import process_sweep_data
|
|
19
|
-
from imap_processing.swapi.l2.swapi_l2 import
|
|
17
|
+
from imap_processing.swapi.l2.swapi_l2 import SWAPI_LIVETIME
|
|
20
18
|
|
|
21
19
|
logger = logging.getLogger(__name__)
|
|
22
20
|
|
|
@@ -70,7 +68,7 @@ def count_rate(
|
|
|
70
68
|
def optimize_pseudo_parameters(
|
|
71
69
|
count_rates: np.ndarray,
|
|
72
70
|
count_rate_error: np.ndarray,
|
|
73
|
-
energy_passbands:
|
|
71
|
+
energy_passbands: np.ndarray,
|
|
74
72
|
) -> (dict)[str, list[float]]:
|
|
75
73
|
"""
|
|
76
74
|
Find the pseudo speed (u), density (n) and temperature (T) of solar wind particles.
|
|
@@ -84,7 +82,7 @@ def optimize_pseudo_parameters(
|
|
|
84
82
|
count_rate_error : np.ndarray
|
|
85
83
|
Standard deviation of the coincidence count rates parameter.
|
|
86
84
|
energy_passbands : np.ndarray, default None
|
|
87
|
-
Energy
|
|
85
|
+
Energy values, taken from the SWAPI lookup table.
|
|
88
86
|
|
|
89
87
|
Returns
|
|
90
88
|
-------
|
|
@@ -92,21 +90,6 @@ def optimize_pseudo_parameters(
|
|
|
92
90
|
Dictionary containing the optimized speed, density, and temperature values for
|
|
93
91
|
each sweep included in the input count_rates array.
|
|
94
92
|
"""
|
|
95
|
-
if not energy_passbands:
|
|
96
|
-
# Read in energy passbands
|
|
97
|
-
energy_data = pd.read_csv(
|
|
98
|
-
f"{imap_module_directory}/tests/swapi/lut/imap_swapi_esa-unit"
|
|
99
|
-
f"-conversion_20250626_v001.csv"
|
|
100
|
-
)
|
|
101
|
-
energy_passbands = (
|
|
102
|
-
energy_data["Energy"][0:63]
|
|
103
|
-
.replace(",", "", regex=True)
|
|
104
|
-
.to_numpy()
|
|
105
|
-
.astype(float)
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
# Initial guess pulled from page 52 of the IMAP SWAPI Instrument Algorithms Document
|
|
109
|
-
initial_param_guess = np.array([550, 5.27, 1e5])
|
|
110
93
|
solution_dict = { # type: ignore
|
|
111
94
|
"pseudo_speed": [],
|
|
112
95
|
"pseudo_density": [],
|
|
@@ -116,8 +99,16 @@ def optimize_pseudo_parameters(
|
|
|
116
99
|
for sweep in np.arange(count_rates.shape[0]):
|
|
117
100
|
current_sweep_count_rates = count_rates[sweep, :]
|
|
118
101
|
current_sweep_count_rate_errors = count_rate_error[sweep, :]
|
|
119
|
-
# Find the max count rate, and use the
|
|
102
|
+
# Find the max count rate, and use the 5 points surrounding it
|
|
120
103
|
max_index = np.argmax(current_sweep_count_rates)
|
|
104
|
+
initial_speed_guess = np.sqrt(energy_passbands[max_index]) * Consts.speed_coeff
|
|
105
|
+
initial_param_guess = np.array(
|
|
106
|
+
[
|
|
107
|
+
initial_speed_guess,
|
|
108
|
+
5 * (400 / initial_speed_guess) ** 2,
|
|
109
|
+
60000 * (initial_speed_guess / 400) ** 2,
|
|
110
|
+
]
|
|
111
|
+
)
|
|
121
112
|
sol = curve_fit(
|
|
122
113
|
f=count_rate,
|
|
123
114
|
xdata=energy_passbands.take(
|
|
@@ -138,7 +129,9 @@ def optimize_pseudo_parameters(
|
|
|
138
129
|
return solution_dict
|
|
139
130
|
|
|
140
131
|
|
|
141
|
-
def process_swapi_ialirt(
|
|
132
|
+
def process_swapi_ialirt(
|
|
133
|
+
unpacked_data: xr.Dataset, calibration_lut_table: pd.DataFrame
|
|
134
|
+
) -> list[dict]:
|
|
142
135
|
"""
|
|
143
136
|
Extract I-ALiRT variables and calculate coincidence count rate.
|
|
144
137
|
|
|
@@ -146,6 +139,8 @@ def process_swapi_ialirt(unpacked_data: xr.Dataset) -> list[dict]:
|
|
|
146
139
|
----------
|
|
147
140
|
unpacked_data : xr.Dataset
|
|
148
141
|
SWAPI I-ALiRT data that has been parsed from the spacecraft packet.
|
|
142
|
+
calibration_lut_table : pd.DataFrame
|
|
143
|
+
DataFrame containing the contents of the SWAPI esa-unit-conversion lookup table.
|
|
149
144
|
|
|
150
145
|
Returns
|
|
151
146
|
-------
|
|
@@ -191,10 +186,31 @@ def process_swapi_ialirt(unpacked_data: xr.Dataset) -> list[dict]:
|
|
|
191
186
|
continue
|
|
192
187
|
|
|
193
188
|
raw_coin_count = process_sweep_data(grouped_dataset, "swapi_coin_cnt")
|
|
194
|
-
raw_coin_rate = raw_coin_count /
|
|
195
|
-
count_rate_error = np.sqrt(raw_coin_count) /
|
|
189
|
+
raw_coin_rate = raw_coin_count / SWAPI_LIVETIME
|
|
190
|
+
count_rate_error = np.sqrt(raw_coin_count) / SWAPI_LIVETIME
|
|
196
191
|
|
|
197
|
-
|
|
192
|
+
# Extract energy values from the calibration lookup table file
|
|
193
|
+
calibration_lut_table["timestamp"] = pd.to_datetime(
|
|
194
|
+
calibration_lut_table["timestamp"], format="%m/%d/%Y %H:%M"
|
|
195
|
+
)
|
|
196
|
+
calibration_lut_table["timestamp"] = calibration_lut_table["timestamp"].to_numpy(
|
|
197
|
+
dtype="datetime64[ns]"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Find the sweep's energy data for the latest time, where sweep_id == 2
|
|
201
|
+
subset = calibration_lut_table[
|
|
202
|
+
(calibration_lut_table["timestamp"] == calibration_lut_table["timestamp"].max())
|
|
203
|
+
& (calibration_lut_table["Sweep #"] == 2)
|
|
204
|
+
]
|
|
205
|
+
if subset.empty:
|
|
206
|
+
energy_passbands = np.full(63, np.nan, dtype=np.float64)
|
|
207
|
+
else:
|
|
208
|
+
subset = subset.sort_values(["timestamp", "ESA Step #"])
|
|
209
|
+
energy_passbands = subset["Energy"][0:63].to_numpy().astype(float)
|
|
210
|
+
|
|
211
|
+
solution = optimize_pseudo_parameters(
|
|
212
|
+
raw_coin_rate, count_rate_error, energy_passbands
|
|
213
|
+
)
|
|
198
214
|
|
|
199
215
|
swapi_data = []
|
|
200
216
|
|