From 4199c67e50b6aa7be62510af6809effc6c1c977c Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Wed, 15 Oct 2025 14:36:43 -0400 Subject: [PATCH 1/6] First draft bright object / jupiter checks --- starcheck/src/lib/Ska/Starcheck/Obsid.pm | 21 ++ starcheck/src/starcheck.pl | 1 + starcheck/utils.py | 239 +++++++++++++++++++++++ 3 files changed, 261 insertions(+) diff --git a/starcheck/src/lib/Ska/Starcheck/Obsid.pm b/starcheck/src/lib/Ska/Starcheck/Obsid.pm index ec8fb0b2..e09d5f1c 100644 --- a/starcheck/src/lib/Ska/Starcheck/Obsid.pm +++ b/starcheck/src/lib/Ska/Starcheck/Obsid.pm @@ -1023,6 +1023,25 @@ sub check_for_srdcs { } } +############################################################################################# +sub check_bright_objects { +############################################################################################# + my $self = shift; + # pass the proseco parameters do this check in Python + my $bright_data = call_python("utils.check_bright_objects", + [ $self->{'proseco_args'} ]); + #use Data::Dumper; + #print Dumper $bright_data; + for my $warn_type (qw(warn fyi orange_warn yellow_warn)) { + if (exists $bright_data->{$warn_type}) { + for my $warn (@{ $bright_data->{$warn_type} }) { + push @{ $self->{$warn_type} }, $warn; + } + } + } +} + + ############################################################################################# sub check_sim_position { ############################################################################################# @@ -3105,6 +3124,8 @@ sub proseco_args { %proseco_args = ( obsid => $self->{obsid}, date => $targ_cmd->{stop_date}, + duration => $self->{obs_tstop} - $self->{obs_tstart}, + target_name => ($self->{TARGET_NAME}) ? $self->{TARGET_NAME} : $self->{SS_OBJECT}, att => [ 0 + $targ_cmd->{q1}, 0 + $targ_cmd->{q2}, diff --git a/starcheck/src/starcheck.pl b/starcheck/src/starcheck.pl index b90e0cb6..25512f56 100755 --- a/starcheck/src/starcheck.pl +++ b/starcheck/src/starcheck.pl @@ -648,6 +648,7 @@ sub json_obsids { $obs{$obsid}->check_bright_perigee($radmon); $obs{$obsid}->check_guide_count(); $obs{$obsid}->check_for_srdcs(\@bs); + $obs{$obsid}->check_bright_objects(); } # Make sure there is only one star catalog per obsid diff --git a/starcheck/utils.py b/starcheck/utils.py index 40bedc25..958aab15 100644 --- a/starcheck/utils.py +++ b/starcheck/utils.py @@ -584,3 +584,242 @@ def vehicle_filter_backstop(backstop_file, outfile): ] # Write the filtered commands to the output file write_backstop(filtered_cmds, outfile) + + +def check_for_planets(date0, duration, att, observer_position="earth", tol=2.25): + import astropy.units as u + from chandra_aca.planets import get_planet_angular_sep + + att = Quaternion.Quat(q=att) + + date0 = CxoTime(date0) + planets = ("venus", "mars", "jupiter", "saturn") + has_planet = dict.fromkeys(planets, False) + + for planet in planets: + # First check if planet is within 2 deg of aimpoint using Earth as the + # reference point (without fetching Chandra ephemeris). These values are + # accurate to better than 0.25 deg. + sep = get_planet_angular_sep( + planet, + ra=att.ra, + dec=att.dec, + time=date0 + ([0, 0.5, 1] * u.s) * duration, + observer_position=observer_position, + ) + if np.all(sep > tol): + continue + + has_planet[planet] = True + + return has_planet + + +class PlanetPositionTable(Table): + @classmethod + def empty(cls): + return cls({"time": [], "row": [], "col": []}) + + +import astropy.units as u +from cxotime import CxoTimeLike +from Quaternion import QuatLike + + +# This is a modified version of get_jupiter_position from proseco +def get_planet_position( + planet: str, + date: CxoTimeLike, + duration: float, + att: QuatLike, +) -> PlanetPositionTable: + """ + Get the position of planet on the ACA CCD. + + Parameters + ---------- + planet : str + The planet name. Supports + date : CxoTimeLike + The start date of the observation (acquisition time) + duration : float + The duration of the observation in seconds. + att : Quaternion or Quat-compatible + The attitude Quaternion. + + Returns + ------- + PlanetPositionTable + A table with columns 'time', 'row', 'col' for the times when Jupiter + is on the CCD. If Jupiter is never on the CCD this is a table with zero rows. + """ + date0 = CxoTime(date) + att = Quaternion.Quat(att) + # dates is a 1-d array with a minimum of 2 points (the end points) + dates = CxoTime.linspace(date0, date0 + duration * u.s, step_max=1000 * u.s) + times = dates.secs + + from cheta.comps import ephem_stk + + chandra_ephem = ephem_stk.get_ephemeris_stk(start=dates[0], stop=dates[-1]) + + # Get Jupiter positions using chandra_aca.planets + ephem = { + key: np.interp(times, chandra_ephem["time"], chandra_ephem[key]) + for key in ["x", "y", "z"] + } + # shape (len(dates), 3), units km + from chandra_aca import planets + + pos_earth = planets.get_planet_barycentric("earth", dates) + + chandra_eci = np.array( + [ + ephem["x"] / 1000, # convert m from get_ephemeris_stk to km, + ephem["y"] / 1000, + ephem["z"] / 1000, + ] + ).transpose() + eci = planets.get_planet_eci(planet, dates, pos_observer=pos_earth + chandra_eci) + + from chandra_aca.transform import eci_to_radec, radec_to_yagzag, yagzag_to_pixels + + # Convert ECI position to RA, Dec => yag, zag => row, col + ra, dec = eci_to_radec(eci) + yag, zag = radec_to_yagzag(ra, dec, att) + row, col = yagzag_to_pixels(yag, zag, allow_bad=True) + + # Row/col limit in pixels to check for bright object + ccd_limit = 512 + + # Limit data to entries on the CCD + ok = (np.abs(row) <= ccd_limit) & (np.abs(col) <= ccd_limit) + out = PlanetPositionTable( + { + "time": times[ok], + "row": row[ok], + "col": col[ok], + } + ) + return out + + +def get_proseco_catalog(**kw): + # Note that the fid ids in starcheck are 1-6 + # ACIS, 7-10 HRC-I 11-14 HRC-S. Proseco just uses indexes 1-6 so + # subtract off the offsets. + fid_ids = np.array(kw["fid_ids"]) + if kw["detector"] == "HRC-I": + fid_offset = 6 + elif kw["detector"] == "HRC-S": + fid_offset = 10 + else: + fid_offset = 0 + fid_ids -= fid_offset + + args = { + "obsid": int(kw["obsid"]), + "att": Quaternion.normalize(kw["att"]), + "duration": kw["duration"], + "target_name": kw["target_name"], + "date": kw["date"], + "n_acq": kw["n_acq"], + "n_guide": kw["n_guide"], + "man_angle": kw["man_angle"], + "t_ccd_acq": kw["t_ccd_acq"], + "t_ccd_guide": kw["t_ccd_guide"], + "dither_acq": ACABox(kw["dither_acq"]), + "dither_guide": ACABox(kw["dither_guide"]), + "include_ids_acq": kw["include_ids_acq"], + "include_halfws_acq": kw["include_halfws_acq"], + "detector": kw["detector"], + "sim_offset": kw["sim_offset"], + "include_ids_guide": kw["include_ids_guide"], + "include_ids_fid": list(fid_ids), + "n_fid": len(kw["fid_ids"]), + "focus_offset": 0, + } + if "monitors" in kw: + args["monitors"] = kw["monitors"] + aca = get_aca_catalog(**args) + return aca + + +def run_jupiter_checks(proseco_args): + proseco_args["target_name"] = "Jupiter" + aca = get_proseco_catalog(**proseco_args) + acar = aca.get_review_table() + from sparkles.checks import check_run_jupiter_checks + from sparkles.messages import MessagesList + + msgs = MessagesList(check_run_jupiter_checks(acar)) + return { + "warn": [w["text"] for w in msgs == "critical"], + "orange_warn": [w["text"] for w in msgs == "caution"], + "yellow_warn": [w["text"] for w in msgs == "warning"], + "fyi": [w["text"] for w in msgs == "info"], + } + + +def check_bright_objects(proseco_args): + # Do a rough check with Earth ephemeris and padded tolerance of 2.25 deg + has_planet = check_for_planets( + proseco_args["date"], + proseco_args["duration"], + proseco_args["att"], + observer_position="earth", + tol=2.25, + ) + if not any(has_planet.values()): + return {} + + # Do the more detailed check with chandra ephemeris + has_planet = check_for_planets( + proseco_args["date"], + proseco_args["duration"], + proseco_args["att"], + observer_position="chandra", + tol=2, + ) + + import collections + + msgs = collections.defaultdict(list) + + if has_planet["venus"]: + msgs["warn"].extend( + ["Bright object alert: Venus within 2 deg of ACA center\n"] + ) + + for planet in ("mars", "jupiter", "saturn"): + if has_planet[planet]: + pos = get_planet_position( + planet, + proseco_args["date"], + proseco_args["duration"], + proseco_args["att"], + ) + + if len(pos) == 0: + msgs["warn"].extend( + [ + f"Bright object alert: {planet.title()} within 2 deg of ACA center, ", + "but not on CCD\n", + ] + ) + elif planet not in proseco_args["target_name"].lower(): + msgs["orange_warn"].extend( + [ + f"Bright object alert: {planet.title()} on CCD but not in target name\n" + ] + ) + + if planet in ("mars", "saturn"): + if len(pos) > 0: + msgs["fyi"].extend([f"Bright object: {planet.title()} on CCD\n"]) + + if planet == "jupiter": + jmsgs = run_jupiter_checks(proseco_args) + for key in jmsgs: + msgs[key].extend(jmsgs[key]) + return msgs From 5f730ad991e3ef5cdf6bb132e5b32c82b9239cfc Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Thu, 16 Oct 2025 11:09:26 -0400 Subject: [PATCH 2/6] Combine rough and detailed check for planets --- starcheck/utils.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/starcheck/utils.py b/starcheck/utils.py index 958aab15..85ee7802 100644 --- a/starcheck/utils.py +++ b/starcheck/utils.py @@ -586,7 +586,7 @@ def vehicle_filter_backstop(backstop_file, outfile): write_backstop(filtered_cmds, outfile) -def check_for_planets(date0, duration, att, observer_position="earth", tol=2.25): +def check_for_planets(date0, duration, att, tol=2.0): import astropy.units as u from chandra_aca.planets import get_planet_angular_sep @@ -597,15 +597,22 @@ def check_for_planets(date0, duration, att, observer_position="earth", tol=2.25) has_planet = dict.fromkeys(planets, False) for planet in planets: - # First check if planet is within 2 deg of aimpoint using Earth as the - # reference point (without fetching Chandra ephemeris). These values are - # accurate to better than 0.25 deg. sep = get_planet_angular_sep( planet, ra=att.ra, dec=att.dec, time=date0 + ([0, 0.5, 1] * u.s) * duration, - observer_position=observer_position, + observer_position="earth", + ) + if np.all(sep > tol + 0.25): + continue + + sep = get_planet_angular_sep( + planet, + ra=att.ra, + dec=att.dec, + time=date0 + ([0, 0.5, 1] * u.s) * duration, + observer_position="chandra", ) if np.all(sep > tol): continue @@ -762,18 +769,6 @@ def run_jupiter_checks(proseco_args): def check_bright_objects(proseco_args): - # Do a rough check with Earth ephemeris and padded tolerance of 2.25 deg - has_planet = check_for_planets( - proseco_args["date"], - proseco_args["duration"], - proseco_args["att"], - observer_position="earth", - tol=2.25, - ) - if not any(has_planet.values()): - return {} - - # Do the more detailed check with chandra ephemeris has_planet = check_for_planets( proseco_args["date"], proseco_args["duration"], @@ -782,14 +777,15 @@ def check_bright_objects(proseco_args): tol=2, ) + if not any(has_planet.values()): + return {} + import collections msgs = collections.defaultdict(list) if has_planet["venus"]: - msgs["warn"].extend( - ["Bright object alert: Venus within 2 deg of ACA center\n"] - ) + msgs["warn"].extend(["Bright object alert: Venus within 2 deg of ACA center\n"]) for planet in ("mars", "jupiter", "saturn"): if has_planet[planet]: From 4927eb10780cd74b0f7dbcb5fccea31cabcbde22 Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Thu, 16 Oct 2025 13:59:26 -0400 Subject: [PATCH 3/6] Minor cleanup --- starcheck/utils.py | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/starcheck/utils.py b/starcheck/utils.py index 85ee7802..9608cedf 100644 --- a/starcheck/utils.py +++ b/starcheck/utils.py @@ -1,3 +1,4 @@ +import collections import logging import os import warnings @@ -633,7 +634,6 @@ def empty(cls): from Quaternion import QuatLike -# This is a modified version of get_jupiter_position from proseco def get_planet_position( planet: str, date: CxoTimeLike, @@ -666,31 +666,11 @@ def get_planet_position( dates = CxoTime.linspace(date0, date0 + duration * u.s, step_max=1000 * u.s) times = dates.secs - from cheta.comps import ephem_stk - - chandra_ephem = ephem_stk.get_ephemeris_stk(start=dates[0], stop=dates[-1]) - - # Get Jupiter positions using chandra_aca.planets - ephem = { - key: np.interp(times, chandra_ephem["time"], chandra_ephem[key]) - for key in ["x", "y", "z"] - } - # shape (len(dates), 3), units km - from chandra_aca import planets - - pos_earth = planets.get_planet_barycentric("earth", dates) - - chandra_eci = np.array( - [ - ephem["x"] / 1000, # convert m from get_ephemeris_stk to km, - ephem["y"] / 1000, - ephem["z"] / 1000, - ] - ).transpose() - eci = planets.get_planet_eci(planet, dates, pos_observer=pos_earth + chandra_eci) - + from chandra_aca.planets import get_planet_chandra from chandra_aca.transform import eci_to_radec, radec_to_yagzag, yagzag_to_pixels + eci = get_planet_chandra(planet, times, ephem_source="stk") + # Convert ECI position to RA, Dec => yag, zag => row, col ra, dec = eci_to_radec(eci) yag, zag = radec_to_yagzag(ra, dec, att) @@ -753,11 +733,13 @@ def get_proseco_catalog(**kw): def run_jupiter_checks(proseco_args): + from sparkles.checks import check_run_jupiter_checks + from sparkles.messages import MessagesList + + # Override the target name to be Jupiter to run the checks proseco_args["target_name"] = "Jupiter" aca = get_proseco_catalog(**proseco_args) acar = aca.get_review_table() - from sparkles.checks import check_run_jupiter_checks - from sparkles.messages import MessagesList msgs = MessagesList(check_run_jupiter_checks(acar)) return { @@ -773,15 +755,12 @@ def check_bright_objects(proseco_args): proseco_args["date"], proseco_args["duration"], proseco_args["att"], - observer_position="chandra", tol=2, ) if not any(has_planet.values()): return {} - import collections - msgs = collections.defaultdict(list) if has_planet["venus"]: From c582532d0f47cd7560f2f8706460ad7b684fc6a9 Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Wed, 22 Oct 2025 13:53:03 -0400 Subject: [PATCH 4/6] Refine logic for check and warns --- starcheck/utils.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/starcheck/utils.py b/starcheck/utils.py index 9608cedf..c5ff886d 100644 --- a/starcheck/utils.py +++ b/starcheck/utils.py @@ -782,19 +782,19 @@ def check_bright_objects(proseco_args): "but not on CCD\n", ] ) - elif planet not in proseco_args["target_name"].lower(): - msgs["orange_warn"].extend( - [ - f"Bright object alert: {planet.title()} on CCD but not in target name\n" - ] - ) - - if planet in ("mars", "saturn"): - if len(pos) > 0: + else: + if planet not in proseco_args["target_name"].lower(): + msgs["orange_warn"].extend( + [ + f"Bright object alert: {planet.title()} on CCD but not in target name\n" + ] + ) + + if planet in ("mars", "saturn"): msgs["fyi"].extend([f"Bright object: {planet.title()} on CCD\n"]) - if planet == "jupiter": - jmsgs = run_jupiter_checks(proseco_args) - for key in jmsgs: - msgs[key].extend(jmsgs[key]) + if planet == "jupiter": + jmsgs = run_jupiter_checks(proseco_args) + for key in jmsgs: + msgs[key].extend(jmsgs[key]) return msgs From 541fbcc109edbafb6cfb43fa05a1448ce030a577 Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Thu, 29 Jan 2026 12:03:41 -0500 Subject: [PATCH 5/6] WIP: let obsids work in historic loads --- starcheck/src/lib/Ska/Starcheck/Obsid.pm | 68 ++++++++++++++++++++---- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/starcheck/src/lib/Ska/Starcheck/Obsid.pm b/starcheck/src/lib/Ska/Starcheck/Obsid.pm index e09d5f1c..0f54ba71 100644 --- a/starcheck/src/lib/Ska/Starcheck/Obsid.pm +++ b/starcheck/src/lib/Ska/Starcheck/Obsid.pm @@ -310,10 +310,29 @@ sub set_maneuver { ################################################################################## my $self = shift; my $mm = shift; + my $ps = shift; my $n = 1; my $c; my $found; + my $dot_obsid = $self->{dot_obsid}; + # If date before "2020", get the ER obsids from the processing summary + # If the $dot_obsid does not begin with a number + if ($dot_obsid !~ /^\d/ && ($self->{date} lt '2020:001:00:00:00.000')) { + # The processing summary has lines that look like this + # 'P080200 CAL 2017:013:03:00:49.827 2017:013:03:00:59.827 000:00:00:10.000 OBSID = 50385 {Perigee Attitude} + # For each line like that, I want to extract the obsid as the up to 5 digit number after OBSID and I + # want to extract the "dot_obsid" as the first 5 characters of the line (P0802 in this case) + foreach my $ps_line (@$ps) { + if ($ps_line =~ /^\s*(\S{5}).*OBSID\s*=\s*(\d{1,5})/) { + if ($1 eq $dot_obsid) { + $dot_obsid = $2; + last; + } + } + } + } + while ($c = find_command($self, "MP_TARGQUAT", $n++)) { $found = 0; foreach my $m (@{$mm}) { @@ -321,7 +340,7 @@ sub set_maneuver { # where manvr_dest is either the final_obsid of a maneuver or the eventual destination obsid # of a segmented maneuver - if ( ($manvr_obsid eq $self->{dot_obsid}) + if ( ($manvr_obsid eq $dot_obsid) && abs($m->{q1} - $c->{Q1}) < 1e-7 && abs($m->{q2} - $c->{Q2}) < 1e-7 && abs($m->{q3} - $c->{Q3}) < 1e-7) @@ -383,6 +402,11 @@ sub set_maneuver { sprintf("Did not find match in maneuvers for MP_TARGQUAT at $c->{date}\n") unless ($found); + unless ($found) { + # throw error and quit + exit(1); + } + } } @@ -1024,18 +1048,38 @@ sub check_for_srdcs { } ############################################################################################# -sub check_bright_objects { +sub check_planets{ ############################################################################################# my $self = shift; + my $c = find_command($self, 'MP_STARCAT'); + # Skip this check if the # pass the proseco parameters do this check in Python - my $bright_data = call_python("utils.check_bright_objects", + my $bright_data = call_python("utils.run_sparkles_planet_checks", [ $self->{'proseco_args'} ]); - #use Data::Dumper; - #print Dumper $bright_data; for my $warn_type (qw(warn fyi orange_warn yellow_warn)) { if (exists $bright_data->{$warn_type}) { for my $warn (@{ $bright_data->{$warn_type} }) { - push @{ $self->{$warn_type} }, $warn; + # If the warning has an idx in it and an id, update the text to match the starcheck + # version of the catalog + if ($warn =~ /idx (\d+) id (\d+)/) { + my $idx = $1; + my $star_id = '---'; + if (defined $c) { + for my $i (1 .. 16) { + if ($c->{"TYPE$i"} ne 'NUL') { + $idx--; + if ($idx == 0) { + $star_id = $c->{"GS_ID$i"} if (defined $c->{"GS_ID$i"}); + last; + } + } + } + $star_id = $c->{"GS_ID$idx"} if (defined $c->{"GS_ID$idx"}); + } + $warn =~ s/IDX=$idx/STAR_ID=$star_id/; + } + + push @{ $self->{$warn_type} }, "$warn\n"; } } } @@ -1122,7 +1166,14 @@ sub check_star_catalog { my $is_er = ($self->{obsid} =~ /^\d+$/ && $self->{obsid} >= $ER_MIN_OBSID); my $min_guide = $is_science ? 5 : 6; # Minimum number of each object type my $min_acq = $is_science ? 4 : 5; - my $min_fid = 3; + my $target_name = ""; + if (defined $self->{TARGET_NAME}) { + $target_name = $self->{TARGET_NAME}; + } + if (defined $self->{SS_OBJECT}) { + $target_name = $self->{SS_OBJECT}; + } + my $min_fid = ($target_name =~ /Venus/) ? 2 : 3; ######################################################################## my @warn = (); @@ -1170,7 +1221,7 @@ sub check_star_catalog { # Global checks on star/fid numbers # ACA-005 ACA-006 ACA-007 ACA-008 ACA-044 - + print STDERR "$target_name\n"; push @warn, "Too Few Fid Lights\n" if (@{ $self->{fid} } < $min_fid && $is_science); push @warn, "Too Many Fid Lights\n" if ( (@{ $self->{fid} } > 0 && $is_er) @@ -3149,7 +3200,6 @@ sub proseco_args { n_fid => scalar(@fid_ids), acq_indexes => \@acq_indexes ); - return \%proseco_args; } From 8ee2dae04650213b7334057ee5e2b7e2289d3a03 Mon Sep 17 00:00:00 2001 From: Jean Connelly Date: Thu, 29 Jan 2026 12:03:49 -0500 Subject: [PATCH 6/6] WIP --- starcheck/src/starcheck.pl | 4 +- starcheck/utils.py | 162 +++---------------------------------- 2 files changed, 13 insertions(+), 153 deletions(-) diff --git a/starcheck/src/starcheck.pl b/starcheck/src/starcheck.pl index 25512f56..8da3c4f2 100755 --- a/starcheck/src/starcheck.pl +++ b/starcheck/src/starcheck.pl @@ -437,7 +437,7 @@ $obs{$obsid}->set_obsid(\%guidesumm); # Commanded obsid $obs{$obsid}->set_target(); $obs{$obsid}->set_star_catalog(); - $obs{$obsid}->set_maneuver($mm); + $obs{$obsid}->set_maneuver($mm, \@ps); $obs{$obsid}->set_files( $STARCHECK, $backstop, @@ -648,7 +648,7 @@ sub json_obsids { $obs{$obsid}->check_bright_perigee($radmon); $obs{$obsid}->check_guide_count(); $obs{$obsid}->check_for_srdcs(\@bs); - $obs{$obsid}->check_bright_objects(); + $obs{$obsid}->check_planets(); } # Make sure there is only one star catalog per obsid diff --git a/starcheck/utils.py b/starcheck/utils.py index c5ff886d..f6e2fd01 100644 --- a/starcheck/utils.py +++ b/starcheck/utils.py @@ -587,109 +587,13 @@ def vehicle_filter_backstop(backstop_file, outfile): write_backstop(filtered_cmds, outfile) -def check_for_planets(date0, duration, att, tol=2.0): - import astropy.units as u - from chandra_aca.planets import get_planet_angular_sep - - att = Quaternion.Quat(q=att) - - date0 = CxoTime(date0) - planets = ("venus", "mars", "jupiter", "saturn") - has_planet = dict.fromkeys(planets, False) - - for planet in planets: - sep = get_planet_angular_sep( - planet, - ra=att.ra, - dec=att.dec, - time=date0 + ([0, 0.5, 1] * u.s) * duration, - observer_position="earth", - ) - if np.all(sep > tol + 0.25): - continue - - sep = get_planet_angular_sep( - planet, - ra=att.ra, - dec=att.dec, - time=date0 + ([0, 0.5, 1] * u.s) * duration, - observer_position="chandra", - ) - if np.all(sep > tol): - continue - - has_planet[planet] = True - - return has_planet - - -class PlanetPositionTable(Table): - @classmethod - def empty(cls): - return cls({"time": [], "row": [], "col": []}) - import astropy.units as u from cxotime import CxoTimeLike from Quaternion import QuatLike +from chandra_aca.planets import (get_planet_chandra_ccd_position, get_planet_mag_states) -def get_planet_position( - planet: str, - date: CxoTimeLike, - duration: float, - att: QuatLike, -) -> PlanetPositionTable: - """ - Get the position of planet on the ACA CCD. - - Parameters - ---------- - planet : str - The planet name. Supports - date : CxoTimeLike - The start date of the observation (acquisition time) - duration : float - The duration of the observation in seconds. - att : Quaternion or Quat-compatible - The attitude Quaternion. - - Returns - ------- - PlanetPositionTable - A table with columns 'time', 'row', 'col' for the times when Jupiter - is on the CCD. If Jupiter is never on the CCD this is a table with zero rows. - """ - date0 = CxoTime(date) - att = Quaternion.Quat(att) - # dates is a 1-d array with a minimum of 2 points (the end points) - dates = CxoTime.linspace(date0, date0 + duration * u.s, step_max=1000 * u.s) - times = dates.secs - - from chandra_aca.planets import get_planet_chandra - from chandra_aca.transform import eci_to_radec, radec_to_yagzag, yagzag_to_pixels - - eci = get_planet_chandra(planet, times, ephem_source="stk") - - # Convert ECI position to RA, Dec => yag, zag => row, col - ra, dec = eci_to_radec(eci) - yag, zag = radec_to_yagzag(ra, dec, att) - row, col = yagzag_to_pixels(yag, zag, allow_bad=True) - - # Row/col limit in pixels to check for bright object - ccd_limit = 512 - - # Limit data to entries on the CCD - ok = (np.abs(row) <= ccd_limit) & (np.abs(col) <= ccd_limit) - out = PlanetPositionTable( - { - "time": times[ok], - "row": row[ok], - "col": col[ok], - } - ) - return out - def get_proseco_catalog(**kw): # Note that the fid ids in starcheck are 1-6 @@ -729,19 +633,24 @@ def get_proseco_catalog(**kw): if "monitors" in kw: args["monitors"] = kw["monitors"] aca = get_aca_catalog(**args) + # Let's write this out to a pickle file for debugging + + with open(f"aca_debug_{args['obsid']}.pkl", "wb") as f: + import pickle + pickle.dump(aca, f) return aca -def run_jupiter_checks(proseco_args): - from sparkles.checks import check_run_jupiter_checks + + +def run_sparkles_planet_checks(proseco_args): + from sparkles.checks import check_planets from sparkles.messages import MessagesList - # Override the target name to be Jupiter to run the checks - proseco_args["target_name"] = "Jupiter" aca = get_proseco_catalog(**proseco_args) acar = aca.get_review_table() - msgs = MessagesList(check_run_jupiter_checks(acar)) + msgs = MessagesList(check_planets(acar)) return { "warn": [w["text"] for w in msgs == "critical"], "orange_warn": [w["text"] for w in msgs == "caution"], @@ -749,52 +658,3 @@ def run_jupiter_checks(proseco_args): "fyi": [w["text"] for w in msgs == "info"], } - -def check_bright_objects(proseco_args): - has_planet = check_for_planets( - proseco_args["date"], - proseco_args["duration"], - proseco_args["att"], - tol=2, - ) - - if not any(has_planet.values()): - return {} - - msgs = collections.defaultdict(list) - - if has_planet["venus"]: - msgs["warn"].extend(["Bright object alert: Venus within 2 deg of ACA center\n"]) - - for planet in ("mars", "jupiter", "saturn"): - if has_planet[planet]: - pos = get_planet_position( - planet, - proseco_args["date"], - proseco_args["duration"], - proseco_args["att"], - ) - - if len(pos) == 0: - msgs["warn"].extend( - [ - f"Bright object alert: {planet.title()} within 2 deg of ACA center, ", - "but not on CCD\n", - ] - ) - else: - if planet not in proseco_args["target_name"].lower(): - msgs["orange_warn"].extend( - [ - f"Bright object alert: {planet.title()} on CCD but not in target name\n" - ] - ) - - if planet in ("mars", "saturn"): - msgs["fyi"].extend([f"Bright object: {planet.title()} on CCD\n"]) - - if planet == "jupiter": - jmsgs = run_jupiter_checks(proseco_args) - for key in jmsgs: - msgs[key].extend(jmsgs[key]) - return msgs