imap-processing 0.7.0__py3-none-any.whl → 0.9.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/__init__.py +1 -1
- imap_processing/_version.py +2 -2
- imap_processing/ccsds/excel_to_xtce.py +36 -2
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +1 -1
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +145 -30
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +36 -36
- imap_processing/cdf/config/imap_hi_variable_attrs.yaml +136 -9
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +14 -0
- imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +63 -1
- imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +9 -0
- imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +14 -7
- imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +577 -235
- imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +326 -0
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +33 -23
- imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +24 -28
- imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +1 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +137 -79
- imap_processing/cdf/config/imap_variable_schema.yaml +13 -0
- imap_processing/cdf/imap_cdf_manager.py +31 -27
- imap_processing/cdf/utils.py +3 -5
- imap_processing/cli.py +25 -14
- imap_processing/codice/codice_l1a.py +153 -63
- imap_processing/codice/constants.py +10 -10
- imap_processing/codice/decompress.py +10 -11
- imap_processing/codice/utils.py +1 -0
- imap_processing/glows/l1a/glows_l1a.py +1 -2
- imap_processing/glows/l1b/glows_l1b.py +3 -3
- imap_processing/glows/l1b/glows_l1b_data.py +59 -37
- imap_processing/glows/l2/glows_l2_data.py +123 -0
- imap_processing/hi/l1a/hi_l1a.py +4 -4
- imap_processing/hi/l1a/histogram.py +107 -109
- imap_processing/hi/l1a/science_direct_event.py +92 -225
- imap_processing/hi/l1b/hi_l1b.py +85 -11
- imap_processing/hi/l1c/hi_l1c.py +23 -1
- imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +3994 -0
- imap_processing/hi/utils.py +1 -1
- imap_processing/hit/hit_utils.py +221 -0
- imap_processing/hit/l0/constants.py +118 -0
- imap_processing/hit/l0/decom_hit.py +100 -156
- imap_processing/hit/l1a/hit_l1a.py +170 -184
- imap_processing/hit/l1b/hit_l1b.py +33 -153
- imap_processing/ialirt/l0/process_codicelo.py +153 -0
- imap_processing/ialirt/l0/process_hit.py +5 -5
- imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +281 -0
- imap_processing/ialirt/process_ephemeris.py +212 -0
- imap_processing/idex/idex_l1a.py +65 -84
- imap_processing/idex/idex_l1b.py +192 -0
- imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -0
- imap_processing/idex/packet_definitions/idex_packet_definition.xml +97 -595
- imap_processing/lo/l0/decompression_tables/decompression_tables.py +17 -1
- imap_processing/lo/l0/lo_science.py +45 -13
- imap_processing/lo/l1a/lo_l1a.py +76 -8
- imap_processing/lo/packet_definitions/lo_xtce.xml +8344 -1849
- imap_processing/mag/l0/decom_mag.py +4 -3
- imap_processing/mag/l1a/mag_l1a.py +12 -13
- imap_processing/mag/l1a/mag_l1a_data.py +1 -2
- imap_processing/mag/l1b/mag_l1b.py +90 -7
- imap_processing/spice/geometry.py +156 -16
- imap_processing/spice/time.py +144 -2
- imap_processing/swapi/l1/swapi_l1.py +4 -4
- imap_processing/swapi/l2/swapi_l2.py +1 -1
- imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +1535 -446
- imap_processing/swe/l1b/swe_l1b_science.py +8 -8
- imap_processing/swe/l2/swe_l2.py +134 -17
- imap_processing/tests/ccsds/test_data/expected_output.xml +2 -1
- imap_processing/tests/ccsds/test_excel_to_xtce.py +4 -4
- imap_processing/tests/cdf/test_imap_cdf_manager.py +0 -10
- imap_processing/tests/codice/conftest.py +1 -17
- imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
- imap_processing/tests/codice/test_codice_l0.py +8 -2
- imap_processing/tests/codice/test_codice_l1a.py +127 -107
- imap_processing/tests/codice/test_codice_l1b.py +1 -0
- imap_processing/tests/codice/test_decompress.py +7 -7
- imap_processing/tests/conftest.py +100 -58
- imap_processing/tests/glows/conftest.py +6 -0
- imap_processing/tests/glows/test_glows_l1b.py +9 -9
- imap_processing/tests/glows/test_glows_l1b_data.py +9 -9
- imap_processing/tests/hi/test_data/l0/H90_NHK_20241104.bin +0 -0
- imap_processing/tests/hi/test_data/l0/H90_sci_cnt_20241104.bin +0 -0
- imap_processing/tests/hi/test_data/l0/H90_sci_de_20241104.bin +0 -0
- imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
- imap_processing/tests/hi/test_hi_l1b.py +73 -3
- imap_processing/tests/hi/test_hi_l1c.py +10 -2
- imap_processing/tests/hi/test_l1a.py +31 -58
- imap_processing/tests/hi/test_science_direct_event.py +58 -0
- imap_processing/tests/hi/test_utils.py +4 -3
- imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
- imap_processing/tests/hit/{test_hit_decom.py → test_decom_hit.py} +95 -36
- imap_processing/tests/hit/test_hit_l1a.py +299 -179
- imap_processing/tests/hit/test_hit_l1b.py +231 -24
- imap_processing/tests/hit/test_hit_utils.py +218 -0
- imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +89 -0
- imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +29 -0
- imap_processing/tests/ialirt/test_data/l0/apid01152.tlm +0 -0
- imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/ialirt/unit/test_process_codicelo.py +106 -0
- imap_processing/tests/ialirt/unit/test_process_ephemeris.py +109 -0
- imap_processing/tests/ialirt/unit/test_process_hit.py +9 -6
- imap_processing/tests/idex/conftest.py +2 -2
- imap_processing/tests/idex/imap_idex_l0_raw_20231214_v001.pkts +0 -0
- imap_processing/tests/idex/impact_14_tof_high_data.txt +4444 -4444
- imap_processing/tests/idex/test_idex_l0.py +4 -4
- imap_processing/tests/idex/test_idex_l1a.py +8 -2
- imap_processing/tests/idex/test_idex_l1b.py +126 -0
- imap_processing/tests/lo/test_lo_l1a.py +7 -16
- imap_processing/tests/lo/test_lo_science.py +69 -5
- imap_processing/tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts +0 -0
- imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SCI_DE_dec_DN_with_fills.csv +1999 -0
- imap_processing/tests/mag/imap_mag_l1a_norm-magi_20251017_v001.cdf +0 -0
- imap_processing/tests/mag/test_mag_l1b.py +97 -7
- imap_processing/tests/spice/test_data/imap_ena_sim_metakernel.template +3 -1
- imap_processing/tests/spice/test_geometry.py +115 -9
- imap_processing/tests/spice/test_time.py +135 -6
- imap_processing/tests/swapi/test_swapi_decom.py +75 -69
- imap_processing/tests/swapi/test_swapi_l1.py +4 -4
- imap_processing/tests/swe/conftest.py +33 -0
- imap_processing/tests/swe/l1_validation/swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat +4332 -0
- imap_processing/tests/swe/test_swe_l1b.py +29 -8
- imap_processing/tests/swe/test_swe_l2.py +64 -8
- imap_processing/tests/test_utils.py +2 -2
- imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
- imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E12.cdf +0 -0
- imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E24.cdf +0 -0
- imap_processing/tests/ultra/unit/test_de.py +113 -0
- imap_processing/tests/ultra/unit/test_spatial_utils.py +125 -0
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +27 -3
- imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +31 -10
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +55 -35
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +10 -68
- imap_processing/ultra/constants.py +12 -3
- imap_processing/ultra/l1b/de.py +168 -30
- imap_processing/ultra/l1b/ultra_l1b_annotated.py +24 -10
- imap_processing/ultra/l1b/ultra_l1b_extended.py +46 -80
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +60 -144
- imap_processing/ultra/utils/spatial_utils.py +221 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/METADATA +15 -14
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/RECORD +142 -139
- imap_processing/cdf/cdf_attribute_manager.py +0 -322
- imap_processing/cdf/config/shared/default_global_cdf_attrs_schema.yaml +0 -246
- imap_processing/cdf/config/shared/default_variable_cdf_attrs_schema.yaml +0 -466
- imap_processing/hi/l0/decom_hi.py +0 -24
- imap_processing/hi/packet_definitions/hi_packet_definition.xml +0 -482
- imap_processing/hit/l0/data_classes/housekeeping.py +0 -240
- imap_processing/hit/l0/data_classes/science_packet.py +0 -259
- imap_processing/hit/l0/utils/hit_base.py +0 -57
- imap_processing/tests/cdf/shared/default_global_cdf_attrs_schema.yaml +0 -246
- imap_processing/tests/cdf/shared/default_variable_cdf_attrs_schema.yaml +0 -466
- imap_processing/tests/cdf/test_cdf_attribute_manager.py +0 -353
- imap_processing/tests/codice/data/imap_codice_l0_hi-counters-aggregated_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-counters-singles_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-omni_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-pha_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-sectored_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hskp_20100101_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-counters-aggregated_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-counters-singles_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-angular_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-priority_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-species_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-pha_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-angular_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-priority_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-species_20240429_v001.pkts +0 -0
- imap_processing/tests/hi/test_decom.py +0 -55
- imap_processing/tests/hi/test_l1a_sci_de.py +0 -72
- imap_processing/tests/idex/imap_idex_l0_raw_20230725_v001.pkts +0 -0
- imap_processing/tests/mag/imap_mag_l1a_burst-magi_20231025_v001.cdf +0 -0
- /imap_processing/{hi/l0/__init__.py → tests/glows/test_glows_l2_data.py} +0 -0
- /imap_processing/tests/hit/test_data/{imap_hit_l0_hk_20100105_v001.pkts → imap_hit_l0_raw_20100105_v001.pkts} +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,188 +1,108 @@
|
|
|
1
1
|
"""Module to create pointing sets."""
|
|
2
2
|
|
|
3
|
-
import typing
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
import cdflib
|
|
7
6
|
import numpy as np
|
|
8
|
-
import spiceypy as spice
|
|
9
7
|
from numpy.typing import NDArray
|
|
10
8
|
|
|
11
|
-
from imap_processing.spice.
|
|
9
|
+
from imap_processing.spice.geometry import (
|
|
10
|
+
SpiceFrame,
|
|
11
|
+
cartesian_to_spherical,
|
|
12
|
+
imap_state,
|
|
13
|
+
spherical_to_cartesian,
|
|
14
|
+
)
|
|
12
15
|
from imap_processing.ultra.constants import UltraConstants
|
|
16
|
+
from imap_processing.ultra.utils.spatial_utils import build_spatial_bins
|
|
13
17
|
|
|
14
18
|
# TODO: add species binning.
|
|
15
19
|
|
|
16
20
|
|
|
17
|
-
def build_energy_bins() -> tuple[
|
|
21
|
+
def build_energy_bins() -> tuple[list[tuple[float, float]], np.ndarray]:
|
|
18
22
|
"""
|
|
19
23
|
Build energy bin boundaries.
|
|
20
24
|
|
|
21
25
|
Returns
|
|
22
26
|
-------
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
intervals : list[tuple[float, float]]
|
|
28
|
+
Energy bins.
|
|
25
29
|
energy_midpoints : np.ndarray
|
|
26
30
|
Array of energy bin midpoints.
|
|
27
31
|
"""
|
|
28
|
-
# TODO: these value will almost certainly change.
|
|
29
|
-
alpha = 0.2 # deltaE/E
|
|
30
|
-
energy_start = 3.385 # energy start for the Ultra grids
|
|
31
|
-
n_bins = 23 # number of energy bins
|
|
32
|
-
|
|
33
32
|
# Calculate energy step
|
|
34
|
-
energy_step = (1 +
|
|
33
|
+
energy_step = (1 + UltraConstants.ALPHA / 2) / (1 - UltraConstants.ALPHA / 2)
|
|
35
34
|
|
|
36
35
|
# Create energy bins.
|
|
37
|
-
energy_bin_edges =
|
|
36
|
+
energy_bin_edges = UltraConstants.ENERGY_START * energy_step ** np.arange(
|
|
37
|
+
UltraConstants.N_BINS + 1
|
|
38
|
+
)
|
|
38
39
|
# Add a zero to the left side for outliers and round to nearest 3 decimal places.
|
|
39
40
|
energy_bin_edges = np.around(np.insert(energy_bin_edges, 0, 0), 3)
|
|
40
41
|
energy_midpoints = (energy_bin_edges[:-1] + energy_bin_edges[1:]) / 2
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
spacing: float = 0.5,
|
|
47
|
-
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
48
|
-
"""
|
|
49
|
-
Build spatial bin boundaries for azimuth and elevation.
|
|
50
|
-
|
|
51
|
-
Parameters
|
|
52
|
-
----------
|
|
53
|
-
spacing : float, optional
|
|
54
|
-
The bin spacing in degrees (default is 0.5 degrees).
|
|
55
|
-
|
|
56
|
-
Returns
|
|
57
|
-
-------
|
|
58
|
-
az_bin_edges : np.ndarray
|
|
59
|
-
Array of azimuth bin boundary values.
|
|
60
|
-
el_bin_edges : np.ndarray
|
|
61
|
-
Array of elevation bin boundary values.
|
|
62
|
-
az_bin_midpoints : np.ndarray
|
|
63
|
-
Array of azimuth bin midpoint values.
|
|
64
|
-
el_bin_midpoints : np.ndarray
|
|
65
|
-
Array of elevation bin midpoint values.
|
|
66
|
-
"""
|
|
67
|
-
# Azimuth bins from 0 to 360 degrees.
|
|
68
|
-
az_bin_edges = np.arange(0, 360 + spacing, spacing)
|
|
69
|
-
az_bin_midpoints = az_bin_edges[:-1] + spacing / 2 # Midpoints between edges
|
|
70
|
-
|
|
71
|
-
# Elevation bins from -90 to 90 degrees.
|
|
72
|
-
el_bin_edges = np.arange(-90, 90 + spacing, spacing)
|
|
73
|
-
el_bin_midpoints = el_bin_edges[:-1] + spacing / 2 # Midpoints between edges
|
|
74
|
-
|
|
75
|
-
return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def cartesian_to_spherical(
|
|
79
|
-
v: NDArray,
|
|
80
|
-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
81
|
-
"""
|
|
82
|
-
Convert cartesian coordinates to spherical coordinates.
|
|
83
|
-
|
|
84
|
-
Parameters
|
|
85
|
-
----------
|
|
86
|
-
v : np.ndarray
|
|
87
|
-
A NumPy array with shape (n, 3) where each
|
|
88
|
-
row represents a vector
|
|
89
|
-
with x, y, z-components.
|
|
90
|
-
|
|
91
|
-
Returns
|
|
92
|
-
-------
|
|
93
|
-
az : np.ndarray
|
|
94
|
-
The azimuth angles in degrees.
|
|
95
|
-
el : np.ndarray
|
|
96
|
-
The elevation angles in degrees.
|
|
97
|
-
r : np.ndarray
|
|
98
|
-
The radii, or magnitudes, of the vectors.
|
|
99
|
-
"""
|
|
100
|
-
vx = v[:, 0]
|
|
101
|
-
vy = v[:, 1]
|
|
102
|
-
vz = v[:, 2]
|
|
103
|
-
|
|
104
|
-
# Magnitude of the velocity vector
|
|
105
|
-
magnitude_v = np.sqrt(vx**2 + vy**2 + vz**2)
|
|
106
|
-
|
|
107
|
-
vhat_x = -vx / magnitude_v
|
|
108
|
-
vhat_y = -vy / magnitude_v
|
|
109
|
-
vhat_z = -vz / magnitude_v
|
|
110
|
-
|
|
111
|
-
# Elevation angle (angle from the z-axis, range: [-pi/2, pi/2])
|
|
112
|
-
el = np.arcsin(vhat_z)
|
|
113
|
-
|
|
114
|
-
# Azimuth angle (angle in the xy-plane, range: [0, 2*pi])
|
|
115
|
-
az = np.arctan2(vhat_y, vhat_x)
|
|
116
|
-
|
|
117
|
-
# Ensure azimuth is from 0 to 2PI
|
|
118
|
-
az = az % (2 * np.pi)
|
|
43
|
+
intervals = [
|
|
44
|
+
(float(energy_bin_edges[i]), float(energy_bin_edges[i + 1]))
|
|
45
|
+
for i in range(len(energy_bin_edges) - 1)
|
|
46
|
+
]
|
|
119
47
|
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def spherical_to_cartesian(
|
|
124
|
-
r: np.ndarray, theta: np.ndarray, phi: np.ndarray
|
|
125
|
-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
126
|
-
"""
|
|
127
|
-
Convert spherical coordinates to Cartesian coordinates.
|
|
128
|
-
|
|
129
|
-
Parameters
|
|
130
|
-
----------
|
|
131
|
-
r : np.ndarray
|
|
132
|
-
Radius.
|
|
133
|
-
theta : np.ndarray
|
|
134
|
-
Azimuth angle in radians.
|
|
135
|
-
phi : array-like or float
|
|
136
|
-
Elevation angle in radians.
|
|
137
|
-
|
|
138
|
-
Returns
|
|
139
|
-
-------
|
|
140
|
-
x, y, z : tuple
|
|
141
|
-
Cartesian coordinates.
|
|
142
|
-
"""
|
|
143
|
-
x = r * np.cos(phi) * np.cos(theta)
|
|
144
|
-
y = r * np.cos(phi) * np.sin(theta)
|
|
145
|
-
z = r * np.sin(phi)
|
|
146
|
-
|
|
147
|
-
return x, y, z
|
|
48
|
+
return intervals, energy_midpoints
|
|
148
49
|
|
|
149
50
|
|
|
150
51
|
def get_histogram(
|
|
151
|
-
|
|
52
|
+
vhat: tuple[np.ndarray, np.ndarray, np.ndarray],
|
|
152
53
|
energy: np.ndarray,
|
|
153
54
|
az_bin_edges: np.ndarray,
|
|
154
55
|
el_bin_edges: np.ndarray,
|
|
155
|
-
energy_bin_edges:
|
|
56
|
+
energy_bin_edges: list[tuple[float, float]],
|
|
156
57
|
) -> NDArray:
|
|
157
58
|
"""
|
|
158
59
|
Compute a 3D histogram of the particle data.
|
|
159
60
|
|
|
160
61
|
Parameters
|
|
161
62
|
----------
|
|
162
|
-
|
|
163
|
-
The x,y,z-components of the velocity vector.
|
|
63
|
+
vhat : tuple[np.ndarray, np.ndarray, np.ndarray]
|
|
64
|
+
The x,y,z-components of the unit velocity vector.
|
|
164
65
|
energy : np.ndarray
|
|
165
66
|
The particle energy.
|
|
166
67
|
az_bin_edges : np.ndarray
|
|
167
68
|
Array of azimuth bin boundary values.
|
|
168
69
|
el_bin_edges : np.ndarray
|
|
169
70
|
Array of elevation bin boundary values.
|
|
170
|
-
energy_bin_edges :
|
|
71
|
+
energy_bin_edges : list[tuple[float, float]]
|
|
171
72
|
Array of energy bin edges.
|
|
172
73
|
|
|
173
74
|
Returns
|
|
174
75
|
-------
|
|
175
76
|
hist : np.ndarray
|
|
176
77
|
A 3D histogram array.
|
|
78
|
+
|
|
79
|
+
Notes
|
|
80
|
+
-----
|
|
81
|
+
The histogram will now work properly for overlapping energy bins, i.e.
|
|
82
|
+
the same energy value can fall into multiple bins if the intervals overlap.
|
|
177
83
|
"""
|
|
178
|
-
|
|
84
|
+
spherical_coords = cartesian_to_spherical(vhat)
|
|
85
|
+
az, el = (
|
|
86
|
+
spherical_coords[..., 1],
|
|
87
|
+
spherical_coords[..., 2],
|
|
88
|
+
)
|
|
179
89
|
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
90
|
+
# Initialize histogram
|
|
91
|
+
hist_total = np.zeros(
|
|
92
|
+
(len(az_bin_edges) - 1, len(el_bin_edges) - 1, len(energy_bin_edges))
|
|
183
93
|
)
|
|
184
94
|
|
|
185
|
-
|
|
95
|
+
for i, (e_min, e_max) in enumerate(energy_bin_edges):
|
|
96
|
+
# Filter data for current energy bin.
|
|
97
|
+
mask = (energy >= e_min) & (energy < e_max)
|
|
98
|
+
hist, _ = np.histogramdd(
|
|
99
|
+
sample=(az[mask], el[mask], energy[mask]),
|
|
100
|
+
bins=[az_bin_edges, el_bin_edges, [e_min, e_max]],
|
|
101
|
+
)
|
|
102
|
+
# Assign 2D histogram to current energy bin.
|
|
103
|
+
hist_total[:, :, i] = hist[:, :, 0]
|
|
104
|
+
|
|
105
|
+
return hist_total
|
|
186
106
|
|
|
187
107
|
|
|
188
108
|
def get_pointing_frame_exposure_times(
|
|
@@ -211,19 +131,17 @@ def get_pointing_frame_exposure_times(
|
|
|
211
131
|
return exposure
|
|
212
132
|
|
|
213
133
|
|
|
214
|
-
@ensure_spice
|
|
215
|
-
@typing.no_type_check
|
|
216
134
|
def get_helio_exposure_times(
|
|
217
135
|
time: np.ndarray,
|
|
218
136
|
sc_exposure: np.ndarray,
|
|
219
|
-
) ->
|
|
137
|
+
) -> NDArray:
|
|
220
138
|
"""
|
|
221
139
|
Compute a 3D array of the exposure in the helio frame.
|
|
222
140
|
|
|
223
141
|
Parameters
|
|
224
142
|
----------
|
|
225
143
|
time : np.ndarray
|
|
226
|
-
Median time of pointing.
|
|
144
|
+
Median time of pointing in J2000 seconds.
|
|
227
145
|
sc_exposure : np.ndarray
|
|
228
146
|
Spacecraft exposure.
|
|
229
147
|
|
|
@@ -237,7 +155,7 @@ def get_helio_exposure_times(
|
|
|
237
155
|
These calculations are performed once per pointing.
|
|
238
156
|
"""
|
|
239
157
|
# Get bins and midpoints.
|
|
240
|
-
|
|
158
|
+
_, energy_midpoints = build_energy_bins()
|
|
241
159
|
az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = (
|
|
242
160
|
build_spatial_bins()
|
|
243
161
|
)
|
|
@@ -252,15 +170,12 @@ def get_helio_exposure_times(
|
|
|
252
170
|
|
|
253
171
|
# Radial distance.
|
|
254
172
|
r = np.ones(el_grid.shape)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
cartesian = np.vstack(
|
|
259
|
-
[x.flatten(order="F"), y.flatten(order="F"), z.flatten(order="F")]
|
|
260
|
-
)
|
|
173
|
+
spherical_coords = np.stack((r, np.radians(az_grid), np.radians(el_grid)), axis=-1)
|
|
174
|
+
cartesian_coords = spherical_to_cartesian(spherical_coords)
|
|
175
|
+
cartesian = cartesian_coords.reshape(-1, 3, order="F").T
|
|
261
176
|
|
|
262
177
|
# Spacecraft velocity in the pointing (DPS) frame wrt heliosphere.
|
|
263
|
-
state
|
|
178
|
+
state = imap_state(time, ref_frame=SpiceFrame.IMAP_DPS)
|
|
264
179
|
|
|
265
180
|
# Extract the velocity part of the state vector
|
|
266
181
|
spacecraft_velocity = state[3:6]
|
|
@@ -273,7 +188,7 @@ def get_helio_exposure_times(
|
|
|
273
188
|
/ 1e3
|
|
274
189
|
)
|
|
275
190
|
|
|
276
|
-
# Use
|
|
191
|
+
# Use Galilean Transform to transform the velocity wrt spacecraft
|
|
277
192
|
# to the velocity wrt heliosphere.
|
|
278
193
|
# energy_velocity * cartesian -> apply the magnitude of the velocity
|
|
279
194
|
# to every position on the grid in the despun grid.
|
|
@@ -284,10 +199,11 @@ def get_helio_exposure_times(
|
|
|
284
199
|
helio_velocity.T, axis=1, keepdims=True
|
|
285
200
|
)
|
|
286
201
|
# Converts vectors from Cartesian coordinates (x, y, z)
|
|
287
|
-
# into spherical coordinates
|
|
288
|
-
|
|
202
|
+
# into spherical coordinates.
|
|
203
|
+
spherical_coords = cartesian_to_spherical(helio_normalized)
|
|
204
|
+
az, el = spherical_coords[..., 1], spherical_coords[..., 2]
|
|
289
205
|
|
|
290
|
-
#
|
|
206
|
+
# Assign values from sc_exposure directly to bins.
|
|
291
207
|
az_idx = np.digitize(az, az_bin_edges) - 1
|
|
292
208
|
el_idx = np.digitize(el, el_bin_edges[::-1]) - 1
|
|
293
209
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""IMAP Ultra utils for spatial binning and grid creation."""
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_spatial_bins(
|
|
10
|
+
az_spacing: float = 0.5,
|
|
11
|
+
el_spacing: float = 0.5,
|
|
12
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
13
|
+
"""
|
|
14
|
+
Build spatial bin boundaries for azimuth and elevation.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
az_spacing : float, optional
|
|
19
|
+
The azimuth bin spacing in degrees (default is 0.5 degrees).
|
|
20
|
+
el_spacing : float, optional
|
|
21
|
+
The elevation bin spacing in degrees (default is 0.5 degrees).
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
az_bin_edges : np.ndarray
|
|
26
|
+
Array of azimuth bin boundary values.
|
|
27
|
+
el_bin_edges : np.ndarray
|
|
28
|
+
Array of elevation bin boundary values.
|
|
29
|
+
az_bin_midpoints : np.ndarray
|
|
30
|
+
Array of azimuth bin midpoint values.
|
|
31
|
+
el_bin_midpoints : np.ndarray
|
|
32
|
+
Array of elevation bin midpoint values.
|
|
33
|
+
"""
|
|
34
|
+
# Azimuth bins from 0 to 360 degrees.
|
|
35
|
+
az_bin_edges = np.arange(0, 360 + az_spacing, az_spacing)
|
|
36
|
+
az_bin_midpoints = az_bin_edges[:-1] + az_spacing / 2 # Midpoints between edges
|
|
37
|
+
|
|
38
|
+
# Elevation bins from -90 to 90 degrees.
|
|
39
|
+
el_bin_edges = np.arange(-90, 90 + el_spacing, el_spacing)
|
|
40
|
+
el_bin_midpoints = el_bin_edges[:-1] + el_spacing / 2 # Midpoints between edges
|
|
41
|
+
|
|
42
|
+
return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def build_solid_angle_map(
|
|
46
|
+
spacing: float, input_degrees: bool = True, output_degrees: bool = False
|
|
47
|
+
) -> NDArray:
|
|
48
|
+
"""
|
|
49
|
+
Build a solid angle map for a given spacing in degrees.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
spacing : float
|
|
54
|
+
The bin spacing in the specified units.
|
|
55
|
+
input_degrees : bool, optional
|
|
56
|
+
If True, the input spacing is in degrees
|
|
57
|
+
(default is True for radians).
|
|
58
|
+
output_degrees : bool, optional
|
|
59
|
+
If True, the output solid angle map is in square degrees
|
|
60
|
+
(default is False for steradians).
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
solid_angle_grid : np.ndarray
|
|
65
|
+
The solid angle map grid in steradians (default) or square degrees.
|
|
66
|
+
First index is latitude/el, second index is longitude/az.
|
|
67
|
+
"""
|
|
68
|
+
if input_degrees:
|
|
69
|
+
spacing = np.deg2rad(spacing)
|
|
70
|
+
|
|
71
|
+
if spacing <= 0:
|
|
72
|
+
raise ValueError("Spacing must be positive valued, non-zero.")
|
|
73
|
+
|
|
74
|
+
if not np.isclose((np.pi / spacing) % 1, 0):
|
|
75
|
+
raise ValueError("Spacing must divide evenly into pi radians.")
|
|
76
|
+
|
|
77
|
+
latitudes = np.arange(-np.pi / 2, np.pi / 2 + spacing, step=spacing)
|
|
78
|
+
sine_latitudes = np.sin(latitudes)
|
|
79
|
+
delta_sine_latitudes = np.diff(sine_latitudes)
|
|
80
|
+
solid_angle_by_latitude = np.abs(spacing * delta_sine_latitudes)
|
|
81
|
+
|
|
82
|
+
solid_angle_grid = np.repeat(
|
|
83
|
+
solid_angle_by_latitude[:, np.newaxis], (2 * np.pi) / spacing, axis=1
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if output_degrees:
|
|
87
|
+
solid_angle_grid *= (180 / np.pi) ** 2
|
|
88
|
+
|
|
89
|
+
return solid_angle_grid
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_az_el_grid(
|
|
93
|
+
spacing: float,
|
|
94
|
+
input_degrees: bool = False,
|
|
95
|
+
output_degrees: bool = False,
|
|
96
|
+
centered_azimuth: bool = False,
|
|
97
|
+
centered_elevation: bool = True,
|
|
98
|
+
) -> tuple[NDArray, NDArray, NDArray, NDArray]:
|
|
99
|
+
"""
|
|
100
|
+
Build a 2D grid of azimuth and elevation angles.
|
|
101
|
+
|
|
102
|
+
Azimuth and Elevation values represent the center of each grid cell,
|
|
103
|
+
so the grid is offset by half the spacing.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
spacing : float
|
|
108
|
+
Spacing of the grid in degrees if `input_degrees` is True, else radians.
|
|
109
|
+
input_degrees : bool, optional
|
|
110
|
+
Whether the spacing is specified in degrees and must be converted to radians,
|
|
111
|
+
by default False (indicating radians).
|
|
112
|
+
output_degrees : bool, optional
|
|
113
|
+
Whether the output azimuth and elevation angles should be in degrees,
|
|
114
|
+
by default False (indicating radians).
|
|
115
|
+
centered_azimuth : bool, optional
|
|
116
|
+
Whether the azimuth grid should be centered around 0 degrees/0 radians,
|
|
117
|
+
i.e. from -pi to pi radians, by default False, indicating 0 to 2pi radians.
|
|
118
|
+
If True, the azimuth grid will be from -pi to pi radians.
|
|
119
|
+
centered_elevation : bool, optional
|
|
120
|
+
Whether the elevation grid should be centered around 0 degrees/0 radians,
|
|
121
|
+
i.e. from -pi/2 to pi/2 radians, by default True.
|
|
122
|
+
If False, the elevation grid will be from 0 to pi radians.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
tuple[NDArray, NDArray, NDArray, NDArray]
|
|
127
|
+
- The evenly spaced, 1D range of azimuth angles
|
|
128
|
+
e.g.(0, 0.5, 1, ..., 359.5) deg.
|
|
129
|
+
- The evenly spaced, 1D range of elevation angles
|
|
130
|
+
e.g.(-90, -89.5, ..., 89.5) deg.
|
|
131
|
+
- The 2D grid of azimuth angles (azimuths for each elevation).
|
|
132
|
+
This grid will be constant along the elevation (0th) axis.
|
|
133
|
+
- The 2D grid of elevation angles (elevations for each azimuth).
|
|
134
|
+
This grid will be constant along the azimuth (1st) axis.
|
|
135
|
+
|
|
136
|
+
Raises
|
|
137
|
+
------
|
|
138
|
+
ValueError
|
|
139
|
+
If the spacing is not positive or does not divide evenly into pi radians.
|
|
140
|
+
"""
|
|
141
|
+
if input_degrees:
|
|
142
|
+
spacing = np.deg2rad(spacing)
|
|
143
|
+
|
|
144
|
+
if spacing <= 0:
|
|
145
|
+
raise ValueError("Spacing must be positive valued, non-zero.")
|
|
146
|
+
|
|
147
|
+
if not np.isclose((np.pi / spacing) % 1, 0):
|
|
148
|
+
raise ValueError("Spacing must divide evenly into pi radians.")
|
|
149
|
+
|
|
150
|
+
el_range = np.arange(spacing / 2, np.pi, spacing)
|
|
151
|
+
az_range = np.arange(spacing / 2, 2 * np.pi, spacing)
|
|
152
|
+
if centered_azimuth:
|
|
153
|
+
az_range = az_range - np.pi
|
|
154
|
+
if centered_elevation:
|
|
155
|
+
el_range = el_range - np.pi / 2
|
|
156
|
+
|
|
157
|
+
# Reverse the elevation range so that the grid is in the order
|
|
158
|
+
# defined by the Ultra prototype code (`build_dps_grid.m`).
|
|
159
|
+
el_range = el_range[::-1]
|
|
160
|
+
|
|
161
|
+
az_grid, el_grid = np.meshgrid(az_range, el_range)
|
|
162
|
+
|
|
163
|
+
if output_degrees:
|
|
164
|
+
az_range = np.rad2deg(az_range)
|
|
165
|
+
el_range = np.rad2deg(el_range)
|
|
166
|
+
az_grid = np.rad2deg(az_grid)
|
|
167
|
+
el_grid = np.rad2deg(el_grid)
|
|
168
|
+
|
|
169
|
+
return az_range, el_range, az_grid, el_grid
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@typing.no_type_check
|
|
173
|
+
def rewrap_even_spaced_el_az_grid(
|
|
174
|
+
raveled_values: NDArray,
|
|
175
|
+
shape: typing.Optional[tuple[int]] = None,
|
|
176
|
+
extra_axis: bool = False,
|
|
177
|
+
) -> NDArray:
|
|
178
|
+
"""
|
|
179
|
+
Take an unwrapped (raveled) 1D array and reshapes it into a 2D el/az grid.
|
|
180
|
+
|
|
181
|
+
Assumes the following must be true of the original grid:
|
|
182
|
+
1. Grid was evenly spaced in angular space,
|
|
183
|
+
2. Grid had the same spacing in both azimuth and elevation.
|
|
184
|
+
3. Elevation is the 0th axis (and extends a total of 180 degrees),
|
|
185
|
+
4. Azimuth is the 1st axis (and extends a total of 360 degrees).
|
|
186
|
+
5. The grid was raveled in Fortran (F) order.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
raveled_values : NDArray
|
|
191
|
+
1D array of values to be reshaped into a 2D grid.
|
|
192
|
+
shape : tuple[int], optional
|
|
193
|
+
The shape of the original grid, if known, by default None.
|
|
194
|
+
If None, the shape will be inferred from the size of the input array.
|
|
195
|
+
extra_axis : bool, optional
|
|
196
|
+
If True, input is a 2D array with latter axis being 'extra', non-spatial axis.
|
|
197
|
+
This axis (e.g. energy bins) will be preserved in the reshaped grid.
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
NDArray
|
|
202
|
+
The reshaped 2D grid of values.
|
|
203
|
+
|
|
204
|
+
Raises
|
|
205
|
+
------
|
|
206
|
+
ValueError
|
|
207
|
+
If the input is not a 1D array or 2D array with an extra axis.
|
|
208
|
+
"""
|
|
209
|
+
if raveled_values.ndim not in (1, 2) or (
|
|
210
|
+
raveled_values.ndim == 2 and not extra_axis
|
|
211
|
+
):
|
|
212
|
+
raise ValueError("Input must be a 1D array or 2D array with extra axis.")
|
|
213
|
+
|
|
214
|
+
# We can infer the shape if its evenly spaced and 2D
|
|
215
|
+
if not shape:
|
|
216
|
+
spacing_deg = 1 / np.sqrt(raveled_values.shape[0] / (360 * 180))
|
|
217
|
+
shape = (int(180 // spacing_deg), int(360 // spacing_deg))
|
|
218
|
+
|
|
219
|
+
if extra_axis:
|
|
220
|
+
shape = (shape[0], shape[1], raveled_values.shape[1])
|
|
221
|
+
return raveled_values.reshape(shape, order="F")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: imap-processing
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: IMAP Science Operations Center Processing
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: IMAP,SDC,SOC,Science Operations
|
|
@@ -41,6 +41,7 @@ Requires-Dist: pytest-cov (>=4.0.0,<5.0.0) ; extra == "test"
|
|
|
41
41
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
42
42
|
Requires-Dist: requests (>=2.32.3,<3.0.0) ; extra == "test"
|
|
43
43
|
Requires-Dist: ruff (==0.2.1) ; extra == "dev"
|
|
44
|
+
Requires-Dist: sammi-cdf (>=0.1.2,<0.2.0)
|
|
44
45
|
Requires-Dist: space_packet_parser (>=5.0.1,<6.0.0)
|
|
45
46
|
Requires-Dist: sphinx ; extra == "doc"
|
|
46
47
|
Requires-Dist: sphinxcontrib-openapi (>=0.8.3,<0.9.0) ; extra == "doc"
|
|
@@ -82,21 +83,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|
|
82
83
|
<table>
|
|
83
84
|
<tbody>
|
|
84
85
|
<tr>
|
|
85
|
-
<td align="center" valign="top" width="14.28%"><a href="http://greglucas.github.io"><img src="https://avatars.githubusercontent.com/u/12417828?v=4?s=100" width="100px;" alt="Greg Lucas"/><br /><sub><b>Greg Lucas</b></sub></a><br
|
|
86
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tech3371"><img src="https://avatars.githubusercontent.com/u/36522642?v=4?s=100" width="100px;" alt="Tenzin Choedon"/><br /><sub><b>Tenzin Choedon</b></sub></a><br
|
|
87
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/laspsandoval"><img src="https://avatars.githubusercontent.com/u/46567335?v=4?s=100" width="100px;" alt="Laura Sandoval"/><br /><sub><b>Laura Sandoval</b></sub></a><br
|
|
88
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sdhoyt"><img src="https://avatars.githubusercontent.com/u/7146374?v=4?s=100" width="100px;" alt="Sean Hoyt"/><br /><sub><b>Sean Hoyt</b></sub></a><br
|
|
89
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GFMoraga"><img src="https://avatars.githubusercontent.com/u/104743000?v=4?s=100" width="100px;" alt="Gabriel
|
|
90
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bourque"><img src="https://avatars.githubusercontent.com/u/2250769?v=4?s=100" width="100px;" alt="Matthew Bourque"/><br /><sub><b>Matthew Bourque</b></sub></a><br
|
|
91
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/maxinelasp"><img src="https://avatars.githubusercontent.com/u/117409426?v=4?s=100" width="100px;" alt="Maxine Hartnett"/><br /><sub><b>Maxine Hartnett</b></sub></a><br
|
|
86
|
+
<td align="center" valign="top" width="14.28%"><a href="http://greglucas.github.io"><img src="https://avatars.githubusercontent.com/u/12417828?v=4?s=100" width="100px;" alt="Greg Lucas"/><br /><sub><b>Greg Lucas</b></sub></a><br /></td>
|
|
87
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tech3371"><img src="https://avatars.githubusercontent.com/u/36522642?v=4?s=100" width="100px;" alt="Tenzin Choedon"/><br /><sub><b>Tenzin Choedon</b></sub></a><br /></td>
|
|
88
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/laspsandoval"><img src="https://avatars.githubusercontent.com/u/46567335?v=4?s=100" width="100px;" alt="Laura Sandoval"/><br /><sub><b>Laura Sandoval</b></sub></a><br /></td>
|
|
89
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sdhoyt"><img src="https://avatars.githubusercontent.com/u/7146374?v=4?s=100" width="100px;" alt="Sean Hoyt"/><br /><sub><b>Sean Hoyt</b></sub></a><br /></td>
|
|
90
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GFMoraga"><img src="https://avatars.githubusercontent.com/u/104743000?v=4?s=100" width="100px;" alt="Gabriel Moraga"/><br /><sub><b>Gabriel Moraga</b></sub></a><br /></td>
|
|
91
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bourque"><img src="https://avatars.githubusercontent.com/u/2250769?v=4?s=100" width="100px;" alt="Matthew Bourque"/><br /><sub><b>Matthew Bourque</b></sub></a><br /></td>
|
|
92
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/maxinelasp"><img src="https://avatars.githubusercontent.com/u/117409426?v=4?s=100" width="100px;" alt="Maxine Hartnett"/><br /><sub><b>Maxine Hartnett</b></sub></a><br /></td>
|
|
92
93
|
</tr>
|
|
93
94
|
<tr>
|
|
94
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bryan-harter"><img src="https://avatars.githubusercontent.com/u/41062454?v=4?s=100" width="100px;" alt="Bryan Harter"/><br /><sub><b>Bryan Harter</b></sub></a><br
|
|
95
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mstrumik"><img src="https://avatars.githubusercontent.com/u/142874888?v=4?s=100" width="100px;" alt="
|
|
96
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vmartinez-cu"><img src="https://avatars.githubusercontent.com/u/39746325?v=4?s=100" width="100px;" alt="
|
|
97
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/subagonsouth"><img src="https://avatars.githubusercontent.com/u/16110870?v=4?s=100" width="100px;" alt="Tim Plummer"/><br /><sub><b>Tim Plummer</b></sub></a><br
|
|
98
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daralynnrhode"><img src="https://avatars.githubusercontent.com/u/143308810?v=4?s=100" width="100px;" alt="Daralynn Rhode"/><br /><sub><b>Daralynn Rhode</b></sub></a><br
|
|
99
|
-
<td align="center" valign="top" width="14.28%"><a href="https://github.com/anamanica"><img src="https://avatars.githubusercontent.com/u/171708990?v=4?s=100" width="100px;" alt="
|
|
95
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bryan-harter"><img src="https://avatars.githubusercontent.com/u/41062454?v=4?s=100" width="100px;" alt="Bryan Harter"/><br /><sub><b>Bryan Harter</b></sub></a><br /></td>
|
|
96
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mstrumik"><img src="https://avatars.githubusercontent.com/u/142874888?v=4?s=100" width="100px;" alt="Marek Strumik"/><br /><sub><b>Marek Strumik</b></sub></a><br /></td>
|
|
97
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/vmartinez-cu"><img src="https://avatars.githubusercontent.com/u/39746325?v=4?s=100" width="100px;" alt="Veronica Martinez"/><br /><sub><b>Veronica Martinez</b></sub></a><br /></td>
|
|
98
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/subagonsouth"><img src="https://avatars.githubusercontent.com/u/16110870?v=4?s=100" width="100px;" alt="Tim Plummer"/><br /><sub><b>Tim Plummer</b></sub></a><br /></td>
|
|
99
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daralynnrhode"><img src="https://avatars.githubusercontent.com/u/143308810?v=4?s=100" width="100px;" alt="Daralynn Rhode"/><br /><sub><b>Daralynn Rhode</b></sub></a><br /></td>
|
|
100
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/anamanica"><img src="https://avatars.githubusercontent.com/u/171708990?v=4?s=100" width="100px;" alt="Ana Manica"/><br /><sub><b>Ana Manica</b></sub></a><br /></td>
|
|
100
101
|
</tr>
|
|
101
102
|
</tbody>
|
|
102
103
|
</table>
|