Skip to content

Commit 0b5f6c4

Browse files
committed
skyCoords components as single columns
1 parent 12a643d commit 0b5f6c4

3 files changed

Lines changed: 110 additions & 13 deletions

File tree

stixcore/processing/FlareListL3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
class FlareListL3(SingleProductProcessingStepMixin):
2525
"""Processing step from a FlareListManager to monthly solo_L3_stix-flarelist-*.fits file."""
2626

27-
STARTDATE = date(2025, 1, 1)
27+
STARTDATE = date(2025, 7, 1)
2828

2929
def __init__(self, flm: FlareListManager, output_dir: Path):
3030
"""Crates a new Processor.

stixcore/products/level3/flarelist.py

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
22
from datetime import datetime
3+
from itertools import groupby
34

45
import numpy as np
56
from stixpy.calibration.visibility import (
@@ -10,7 +11,7 @@
1011
from stixpy.coordinates.transforms import get_hpc_info
1112
from stixpy.net.client import STIXClient
1213
from stixpy.product import Product as STIXPYProduct
13-
from sunpy.coordinates import HeliographicStonyhurst, Helioprojective
14+
from sunpy.coordinates import HeliographicStonyhurst, Helioprojective, SphericalScreen
1415
from sunpy.map import make_fitswcs_header
1516
from sunpy.net import attrs as a
1617
from sunpy.time import TimeRange
@@ -95,12 +96,26 @@ def add_flare_position(
9596
# helio_frame = Helioprojective(observer="earth")
9697
# SkyCoord(HeliographicStonyhurst(0 * u.deg, 0 * u.deg))
9798
# SkyCoord(0 * u.deg, 0 * u.deg, frame=helio_frame)
98-
data["flare_position"] = [SkyCoord(HeliographicStonyhurst(0 * u.deg, 0 * u.deg)) for i in range(0, len(data))]
99+
100+
n = len(data)
101+
102+
data["flareposition_obs_hgs_x"] = Column(
103+
np.zeros(n, dtype=float) * u.km, description="HeliographicStonyhurst X of observer"
104+
)
105+
data["flareposition_obs_hgs_y"] = Column(
106+
np.zeros(n, dtype=float) * u.km, description="HeliographicStonyhurst Y of observer"
107+
)
108+
data["flareposition_obs_hgs_z"] = Column(
109+
np.zeros(n, dtype=float) * u.km, description="HeliographicStonyhurst Z of observer"
110+
)
111+
data["flareposition_hp_tx"] = Column(np.zeros(n, dtype=float) * u.arcsec, description="Helioprojective Tx")
112+
data["flareposition_hp_ty"] = Column(np.zeros(n, dtype=float) * u.arcsec, description="Helioprojective Ty")
99113

100114
data["anc_ephemeris_path"] = Column(" " * 500, dtype=str, description="TDB")
101115
data["cpd_path"] = Column(" " * 500, dtype=str, description="TDB")
102116
data["_position_status"] = Column(False, dtype=bool, description="TDB")
103117
data["_position_message"] = Column(" " * 500, dtype=str, description="TDB")
118+
104119
to_remove = []
105120
pass_filter = 0
106121
no_ephemeris = 0
@@ -110,9 +125,9 @@ def add_flare_position(
110125
total_flares = len(data)
111126

112127
day_asp_ephemeris_cache = dict()
113-
flare_positions = []
128+
114129
for i, row in enumerate(data):
115-
if filter_function(row) and i < 200:
130+
if filter_function(row): # and i < 200:
116131
pass_filter += 1
117132
peak_time = row[peak_time_colname]
118133
start_time = row[start_time_colname]
@@ -183,7 +198,7 @@ def add_flare_position(
183198
cpd_res["duration"][i] = header["OBT_END"] - header["OBT_BEG"]
184199

185200
# TODO: add more criteria to select the best CPD file
186-
cpd_res.sort(["tbins", "duration"])
201+
cpd_res.sort(["inc_peak", "tbins", "duration"], reverse=True)
187202
# cpd_res.pprint()
188203
best_cpd_idx = 0
189204
else:
@@ -193,24 +208,63 @@ def add_flare_position(
193208

194209
try:
195210
stixpy_cpd = STIXPYProduct(Path(data[i]["cpd_path"]))
196-
coord, map = estimate_stix_flare_location(stixpy_cpd)
211+
time_range = TimeRange(max(peak_time - 20 * u.s, start_time), min(peak_time + 20 * u.s, end_time))
212+
overlaps = calculate_overlap(stixpy_cpd.time_range, time_range)
213+
if overlaps is None:
214+
logger.warning(
215+
f"CPD data does not cover time range around peak time {time_range.start} to {time_range.end}"
216+
)
217+
time_range = stixpy_cpd.time_range
218+
contains_peak_time = False
219+
else:
220+
contains_peak_time = True
221+
time_range = overlaps
222+
223+
mask = (stixpy_cpd.data["time"] >= time_range.start) & (stixpy_cpd.data["time"] <= time_range.end)
224+
data_at_peak = stixpy_cpd.data[mask]
225+
if len(np.unique(data_at_peak["rcr"])) > 1:
226+
logger.warning(
227+
f"Multiple rcr values found for flare at time {time_range.start} : {time_range.end}"
228+
)
229+
# allow a larger time range for finding a constant rcr sequence
230+
if contains_peak_time:
231+
time_range = TimeRange(
232+
max(peak_time - 40 * u.s, start_time), min(peak_time + 40 * u.s, end_time)
233+
)
234+
mask = (stixpy_cpd.data["time"] >= time_range.start) & (
235+
stixpy_cpd.data["time"] <= time_range.end
236+
)
237+
data_at_peak = stixpy_cpd.data[mask]
238+
length, start_idx, rcr = longest_constant_sequence(data_at_peak["rcr"].value)
239+
time_range = TimeRange(
240+
data_at_peak["time"][start_idx], data_at_peak["time"][start_idx + length - 1]
241+
)
242+
logger.info(
243+
f"Using time range {time_range.start} to {time_range.end} for flare at around {peak_time} with constant rcr={rcr}"
244+
)
245+
246+
coord, map = estimate_stix_flare_location(stixpy_cpd, time_range=time_range)
197247

198248
roll, solo_xyz, pointing = get_hpc_info(start_time, end_time)
199249
solo = HeliographicStonyhurst(*solo_xyz, obstime=peak_time, representation_type="cartesian")
250+
with SphericalScreen(solo, only_off_disk=True):
251+
center_hpc = coord.transform_to(Helioprojective(observer=solo))
252+
253+
data[i]["flareposition_obs_hgs_x"] = solo_xyz[0].to(u.km)
254+
data[i]["flareposition_obs_hgs_y"] = solo_xyz[1].to(u.km)
255+
data[i]["flareposition_obs_hgs_z"] = solo_xyz[2].to(u.km)
256+
data[i]["flareposition_hp_tx"] = center_hpc.Tx.to(u.arcsec)
257+
data[i]["flareposition_hp_ty"] = center_hpc.Ty.to(u.arcsec)
200258

201-
# data[i]["flare_position"] = coord.transform_to(Helioprojective(observer=solo))
202-
flare_positions.append(coord.transform_to(Helioprojective(observer=solo)))
203259
data[i]["_position_status"] = True
204260
data[i]["_position_message"] = "OK"
205261
except Exception as e:
206-
flare_positions.append(None)
262+
data[i]["_position_status"] = False
207263
data[i]["_position_message"] = f"Error: {type(e)}"
208-
264+
logger.warn(f"Error calculating flare position for flare at time {start_time} : {end_time}: {e}")
209265
else:
210266
to_remove.append(i)
211-
flare_positions.append(None)
212267

213-
data["flare_position"] = flare_positions
214268
if not keep_all_flares:
215269
data.remove_rows(to_remove)
216270

@@ -762,3 +816,44 @@ def add_peak_preview(cls, data, energies, parent, fido_client: STIXClient, img_p
762816
@classmethod
763817
def is_datasource_for(cls, *, service_type, service_subtype, ssid, **kwargs):
764818
return kwargs["level"] == "L3" and service_type == 0 and service_subtype == 0 and ssid == 8
819+
820+
821+
def longest_constant_sequence(state_array):
822+
"""Find the longest sequence where state is constant.
823+
In case of equal length, prefer the one with the lower state value."""
824+
if len(state_array) == 0:
825+
return 0, None, None
826+
827+
max_length = 0
828+
max_state = None
829+
max_start_idx = None
830+
current_idx = 0
831+
832+
for state, group in groupby(state_array):
833+
length = len(list(group))
834+
# Update if longer, OR if equal length but lower state value
835+
if length > max_length or (length == max_length and (max_state is None or state < max_state)):
836+
max_length = length
837+
max_state = state
838+
max_start_idx = current_idx
839+
current_idx += length
840+
841+
return max_length, max_start_idx, max_state
842+
843+
844+
def calculate_overlap(range1, range2):
845+
"""Calculate the overlap between two TimeRanges.
846+
Returns the overlap duration and the overlapping TimeRange, or None if no overlap."""
847+
848+
# Check if they intersect first
849+
if not range1.intersects(range2):
850+
return None
851+
852+
# Calculate intersection boundaries
853+
overlap_start = max(range1.start, range2.start)
854+
overlap_end = min(range1.end, range2.end)
855+
856+
# Create the overlapping TimeRange
857+
overlap_range = TimeRange(overlap_start, overlap_end)
858+
859+
return overlap_range

stixcore/soop/manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,8 @@ def add_soop_file_to_index(self, path, *, rebuild_index=True, **args):
529529
all_soop_file = Path(CONFIG.get("SOOP", "soop_files_download")) / f"{plan}.{version}.all.json"
530530

531531
if not all_soop_file.exists():
532+
# TODO remove ove SOOP API is working reliably
533+
return
532534
self.download_all_soops_from_api(plan, version, all_soop_file)
533535

534536
with open(all_soop_file) as f_all:

0 commit comments

Comments
 (0)