diff --git a/README.md b/README.md index fff9c65..0d2c5ba 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,10 @@ Astronomical Observation Tools (Astrobs Tools) is composed of: - SCOPE: a code to get the sky coordinate limits from observation constraints; -- EQUATOR: a software to organize targets before observations. - +- EQUATOR: a software to organize targets before observations; +and the following extractors: +- HPMS: High Proper Motion Stars (Simbad Extractor); +- NGC: New General Catalogue (VizieR Extractor); ## Requirements @@ -57,24 +59,19 @@ Then, select the action you wish to perform. Available commands (not case sensitive): - `help`, `h`, `?`: show this page -- `quit`, `exit`, `q`: quit the current code -(WARNING: this does not save the current state!) +- `quit`, `exit`, `q`: quit the current code (WARNING: this does not save the current state!) - `write`, `save`: write the current table in a file (no options available yet) -- `read [filename]`, `open [filename]`: loads the file "filename" -in the current table (no additional options available yet) +- `read [filename]`, `open [filename]`, `load`: loads the file "filename" in the current table (no additional options available yet) - `calibration`, `calib`: adds a calibration in the target list - `simbad [object name]`, `object [object name]`: add an object from simbad -- `search [ra] [dec] [radius]`, `region [ra] [dec] [radius]`: -search a region centred on the ra/dec coordinates, -with a given radius (coordinates should be expressed as -`12h30m30s`, `90d30m30s` or `90.555d`) +- `search [ra] [dec] [radius]`, `region [ra] [dec] [radius]`: search a region centred on the ra/dec coordinates, with a given radius (ra is given in hour, dec in degree and the radius is any specified unit,, e.g. `search 01:03:40 35:40:20 30'`) - `manual [name] -s [seq]`, `add [name] -s [seq]`: manually add a target (only the name and the sequence are available for now) -- `sidereal`, `st`: computes the sidereal time for each target - `sequence`, `seq`: computes the sequence order for each target - `check`: check if all targets are in the observation field +- plot, graph: creates a graph with all the targets observation date and time General actions: - `cancel`, `back`: cancels the current action @@ -82,3 +79,7 @@ General actions: - `no`, `n`, `0`, or anything else: no - `all`, `*`: select all - `done`, `ok`: confirm, save the current state and quit + +## Extractors (HPMS, NGC) + +Extractors are small codes to query databases in order to extract a table of potential targets. Using them is often straightforward, and their result is always written in the `Output/` directory. diff --git a/Source/ASTROBS_SELECT_v1.py b/Source/ASTROBS_SELECT_v1.py index 047209a..1ac49de 100644 --- a/Source/ASTROBS_SELECT_v1.py +++ b/Source/ASTROBS_SELECT_v1.py @@ -1,10 +1,14 @@ import SCOPE_v2_2 import EQUATOR_v1_1 +import HPMS_v1 +import NGC_v1 QUIT = ["QUIT", "EXIT", "Q"] DONE = ["DONE", "OK"] SCOPE = ["SCOPE", "S", "1"] EQUATOR = ["EQUATOR", "E", "2"] +HPMS = ["HPMS", "H", "3"] +NGC = ["NGC", "N", "4"] stop = False first = True @@ -28,6 +32,14 @@ while not stop: + "\tEQUATOR: " + "\033[0m" + "Equator Queries simbAd to create Tables of Objects") + print("\033[32m" + + "\tHPMS: " + + "\033[0m" + + "High Proper Motion Stars (Simbad Extractor)") + print("\033[32m" + + "\tNGC: " + + "\033[0m" + + "New General Catalogue (VizieR Extractor)") choice = input("\033[32m" + "Choice: " + "\033[0m") if choice.upper() in DONE + ["", " "]: @@ -44,6 +56,18 @@ while not stop: +"\033[0m") first = True EQUATOR_v1_1.main() + elif choice.upper() in HPMS: + print("\033[35m" + + "{:^80}".format("=== [ HPMS (Simbad Extractor) ] ===") + +"\033[0m") + first = True + HPMS_v1.main() + elif choice.upper() in NGC: + print("\033[35m" + + "{:^80}".format("=== [ NGC (VizieR Extractor) ] ===") + +"\033[0m") + first = True + NGC_v1.main() elif choice.upper() in QUIT: stop = True else: diff --git a/Source/EQUATOR_v1_1.py b/Source/EQUATOR_v1_1.py index c55378c..4fc74d2 100644 --- a/Source/EQUATOR_v1_1.py +++ b/Source/EQUATOR_v1_1.py @@ -50,7 +50,7 @@ def main(): if ans.upper() in QUIT: stop = True toolbox.resolve_input(ans, targets, config) - targets.pprint() + targets.pprint_all() return 0 if __name__ == "__main__": diff --git a/Source/HPMS_v1.py b/Source/HPMS_v1.py new file mode 100644 index 0000000..db6545b --- /dev/null +++ b/Source/HPMS_v1.py @@ -0,0 +1,104 @@ +import os +from astroquery.simbad import Simbad +from astrobs_v1_toolbox import read_cfg + +OUT_DIR = "./Output/" +QUIT = ["QUIT", "EXIT", "Q"] + +def get_HPMS(filename: str, + directory: str = OUT_DIR, + extension: str = ".cfg", + pm_max: float = 800.0, + pm_min: float = 30.0, + mag_max: float = 8.5, + mag_min: float = 13.0) -> tuple: + config = read_cfg(filename, directory, extension) + date = config["SUN_SET"][:10] + constraint = config["CONSTRAINT"] + loc = config["LOCATION"] + + query_hpms_high = """SELECT TOP 20 + main_id, + ra, + dec, + pmra, + pmdec, + SQRT(pmra*pmra + pmdec*pmdec) as "PM", + filter, + flux + FROM basic JOIN flux ON oid=oidref + WHERE {where} + AND SQRT(pmra*pmra + pmdec*pmdec) > {pm} + AND flux > {mag_max} AND flux < {mag_min} + AND filter='V' + ORDER BY "PM" DESC; + """.format(where = constraint, + pm = pm_max, + mag_max = mag_max, + mag_min = mag_min) + + query_hpms_low = """SELECT TOP 20 + main_id, + ra, + dec, + pmra, + pmdec, + SQRT(pmra*pmra + pmdec*pmdec) as "PM", + filter, + flux + FROM basic JOIN flux ON oid=oidref + WHERE {where} + AND SQRT(pmra*pmra + pmdec*pmdec) > {pm} + AND flux > {mag_max} AND flux < {mag_min} + AND filter='V' + ORDER BY "PM" ASC; + """.format(where = constraint, + pm = pm_min, + mag_max = mag_max, + mag_min = mag_min) + + hpms_high = Simbad.query_tap(query_hpms_high) + hpms_low = Simbad.query_tap(query_hpms_low) + + hpms_high.write("{}{}_{}_hpms_high.xml".format(directory, date, loc), + format="votable", + overwrite=True) + hpms_low.write("{}{}_{}_hpms_low.xml".format(directory, date, loc), + format="votable", + overwrite=True) + print("\033[36m" + "High Proper Motion Stars: Highest 20" + "\033[0m") + hpms_high.pprint() + + print("\033[36m" + "High Proper Motion Stars: Limiting 20" + "\033[0m") + hpms_low.pprint() + return 0 + +def main(directory: str = OUT_DIR, + extension: str = ".cfg") -> tuple: + filelist = [fname for fname in os.listdir(directory) if extension in fname] + print("\033[32m" + + "Select a configuration" + + "\033[0m") + for i in range(len(filelist)): + print("\033[32m" + + "\t{}. ".format(i+1) + + "\033[0m" + + "{}".format(filelist[i][:-4])) + try: + answer = input("\033[32m" + "Choice: " + "\033[0m") + if answer in QUIT: + return 0 + selection = int(answer) - 1 + filename = filelist[selection][:-4] + except Exception as e: + print("\033[91m" + + ("Error! Input value not recognized, " + "selected the last location instead") + + "\033[0m") + selection = -1 + filename = filelist[selction][:-4] + get_HPMS(filename) + return None + +if __name__ == "__main__": + main() diff --git a/Source/NGC_v1.py b/Source/NGC_v1.py new file mode 100644 index 0000000..827f89e --- /dev/null +++ b/Source/NGC_v1.py @@ -0,0 +1,107 @@ +import os +from astroquery.vizier import Vizier +from astrobs_v1_toolbox import read_cfg, create_table +import astropy.coordinates as coord +import astropy.units as u + +OUT_DIR = "./Output/" +QUIT = ["QUIT", "EXIT", "Q"] + +obj_type_list = """\t\033[32m1. \033[0mOpen Cluster +\t\033[32m2. \033[0mGlobular Cluster +\t\033[32m3. \033[0mDiffuse Nebula +\t\033[32m4. \033[0mPlanetary Nebula 9 Object in Small Magellanic Cloud +\t\033[32m5. \033[0mGalaxy +\t\033[32m6. \033[0mCluster associated with nebulosity +\t\033[32m7. \033[0mNon existent +\t\033[32m8. \033[0mObject in Large Magellanic Cloud +\t\033[32m0. \033[0mUnverified southern object +\t\033[32m(blank). \033[0mAny""" + + +def get_NGC(filename: str, + obj_type: int = None, + directory: str = OUT_DIR, + extension: str = ".cfg"): + if obj_type == None: + obj_type = ">0" + + Vizier.clear_cache() + config = read_cfg(filename, directory, extension) + date = config["SUN_SET"][:10] + loc = config["LOCATION"] + + NGC = Vizier(catalog="VII/1B", + columns=["NGC", "Type", "_RAJ2000", "_DEJ2000", "+Mag"]) + objects = NGC.query_constraints(RA1975 = config["RA_CONST"], + DE1975 = config["DE_CONST"], + Mag = "<999", + Type = str(obj_type))[0] + table = create_table() + for i in range(len(objects)): + mag = "Mag: {}".format(objects["Mag"][i]) + ra = "{}".format(coord.Angle(objects["_RAJ2000"][i], + unit=u.degree).to_string(u.hourangle, + sep=":", + pad=True)) + dec = "{}".format(coord.Angle(objects["_DEJ2000"][i], + unit=u.degree).to_string(u.degree, + alwayssign=True, + sep=":", + pad=True)) + name = "NGC {}".format(objects["NGC"][i]) + table.add_row({"seq": 0, + "name": name, + "main_id": name, + "ra": ra, + "dec": dec, + "notes": mag}) + table.write("{}{}_{}_ngc.xml".format(directory, date, loc), + format="votable", + overwrite=True) + table.pprint() + return None + +def main(directory: str = OUT_DIR, + extension: str = ".cfg") -> tuple: + filelist = [fname for fname in os.listdir(directory) if extension in fname] + print("\033[32m" + + "Select a configuration" + + "\033[0m") + for i in range(len(filelist)): + print("\033[32m" + + "\t{}. ".format(i+1) + + "\033[0m" + + "{}".format(filelist[i][:-4])) + try: + answer = input("\033[32m" + "Choice: " + "\033[0m") + if answer in QUIT: + return 0 + selection = int(answer) - 1 + filename = filelist[selection][:-4] + except Exception as e: + print("\033[91m" + + ("Error! Input value not recognized, " + "selected the last location instead") + + "\033[0m") + selection = -1 + filename = filelist[selction][:-4] + print("\033[32m" + + "Select a type of object" + + "\033[0m") + print("\033[32m" + obj_type_list + "\033[32m") + try: + answer = input("\033[32m" + "Choice: " + "\033[0m") + if answer in QUIT: + return 0 + if answer in [" ", ""]: + obj_type = None + obj_type = int(answer) + except Exception as e: + obj_type = None + + get_NGC(filename, obj_type) + return 0 + +if __name__ == "__main__": + main() diff --git a/Source/SCOPE_v2_2.py b/Source/SCOPE_v2_2.py index 6162f0f..3a38da2 100755 --- a/Source/SCOPE_v2_2.py +++ b/Source/SCOPE_v2_2.py @@ -257,6 +257,26 @@ def main(): else: comp_symb = "||" comp_text = "OR" + + constraint = ("((ra < {} {} ra > {})" + " AND " + "(dec < {} AND dec > {}))").format( + a_west.degree, + comp_text, + a_east.degree, + dec_upper.degree, + dec_lower.degree) + ra_const = "< " \ + + a_west.to_string(unit=u.hourangle, sep=":", pad=True) \ + + " {} ".format(comp_symb) \ + + "> " \ + + a_east.to_string(unit=u.hourangle, sep=":", pad=True) + de_const = "< " \ + + dec_upper.to_string(unit=u.degree, sep=":", pad=True) \ + + " && " \ + + "> " \ + + dec_lower.to_string(unit=u.degree, sep=":", pad=True) + # * Output print("") print("\033[36m" @@ -266,11 +286,11 @@ def main(): print("\033[36m" + "\tlat: " + "\033[0m" - + obs_lat.to_string()) + + obs_lat.to_string(unit=u.degree)) print("\033[36m" + "\tlon: " + "\033[0m" - + obs_lon.to_string()) + + obs_lon.to_string(unit=u.degree)) print("\033[36m" + "Observation" + "\033[0m") @@ -285,63 +305,66 @@ def main(): print("\033[36m" + "\tbegin sidereal time: " + "\033[0m" - + st.to_string(unit=u.hour)) + + st.to_string(unit=u.hourangle, + sep=":", + pad=True)) print("\033[36m" + "\t end sidereal time: " + "\033[0m" - + (st + obs_sky_rotation).wrap_at(24*u.h).to_string(unit=u.hour)) + + (st + obs_sky_rotation).wrap_at(24*u.h).to_string(unit=u.hour, + sep=":", + pad=True)) print("\033[36m" + "\t sky rotation: " + "\033[0m" - + obs_sky_rotation.to_string(unit=u.hour)) + + obs_sky_rotation.to_string(unit=u.hourangle, + sep=":", + pad=True)) print("\033[36m" + "Sky coordinates window" + "\033[0m") print("\033[36m" + "\t east ra: " + "\033[0m" - + a_east.to_string(unit=u.hour)) + + a_east.to_string(unit=u.hourangle, + sep=":", + pad=True)) print("\033[36m" + "\t west ra: " + "\033[0m" - + a_west.to_string(unit=u.hour)) + + a_west.to_string(unit=u.hourangle, + sep=":", + pad=True)) print("\033[36m" + "\tupper dec: " + "\033[0m" - + dec_upper.to_string(unit=u.degree)) + + dec_upper.to_string(unit=u.degree, + sep=":", + pad=True, + alwayssign=True)) print("\033[36m" + "\tlower dec: " + "\033[0m" - + dec_lower.to_string(unit=u.degree)) + + dec_lower.to_string(unit=u.degree, + sep=":", + pad=True, + alwayssign=True)) print("\033[36m" + "Simbad query constraints: " + "\033[0m" - + "WHERE (ra < {} {} ra > {}) AND (dec < {} AND dec > {}))".format( - a_west.degree, - comp_text, - a_east.degree, - dec_upper.degree, - dec_lower.degree)) + + "WHERE " + + constraint) print("\033[36m" + "Vizier query constraints:" + "\033[0m") print("\033[36m" + "\tRA: " + "\033[0m" - + "< " - + a_west.to_string(unit=u.hour, sep=":") - + " {} ".format(comp_symb) - + "> " - + a_east.to_string(unit=u.hour, sep=":")) + + ra_const) print("\033[36m" + "\tDE: " + "\033[0m" - + "< " - + dec_upper.to_string(unit=u.degree, sep=":") - + " && " - + "> " - + dec_lower.to_string(unit=u.degree, sep=":")) - + + de_const) print("") print("\033[32m" + "Write output to file ? [yes/no]" @@ -357,9 +380,9 @@ def main(): file.write("LOCATION: ") file.write(obs_short + "\n") file.write("LAT: ") - file.write(obs_lat.to_string() + "\n") + file.write(obs_lat.to_string(unit=u.degree) + "\n") file.write("LON: ") - file.write(obs_lon.to_string() + "\n") + file.write(obs_lon.to_string(unit=u.degree) + "\n") file.write("SUN_SET: ") file.write(sun_set.to_string() + "\n") file.write("SUN_SET_CIVIL: ") @@ -381,18 +404,39 @@ def main(): file.write("OBS_END: ") file.write(obs_end_time.to_string() + "\n") file.write("ST_BEGIN: ") - file.write(st.to_string(unit=u.hour) + "\n") + file.write(st.to_string(unit=u.hourangle, + sep=":", + pad=True) + "\n") file.write("ST_END: ") file.write((st + obs_sky_rotation).wrap_at( - 24*u.h).to_string(unit=u.hour) + "\n") + 24*u.h).to_string(unit=u.hourangle, + sep=":", + pad=True) + "\n") file.write("WINDOW_EAST: ") - file.write(a_east.to_string(unit=u.hour) + "\n") + file.write(a_east.to_string(unit=u.hourangle, + sep=":", + pad=True) + "\n") file.write("WINDOW_WEST: ") - file.write(a_west.to_string(unit=u.hour) + "\n") + file.write(a_west.to_string(unit=u.hourangle, + sep=":", + pad=True) + "\n") file.write("WINDOW_UPPER: ") - file.write(dec_upper.to_string(unit=u.degree) + "\n") + file.write(dec_upper.to_string(unit=u.degree, + sep=":", + pad=True, + alwayssign=True) + "\n") file.write("WINDOW_LOWER: ") - file.write(dec_lower.to_string(unit=u.degree) + "\n") + file.write(dec_lower.to_string(unit=u.degree, + sep=":", + pad=True, + alwayssign=True) + "\n") + file.write("CONSTRAINT: ") + file.write(constraint + "\n") + file.write("RA_CONST: ") + file.write(ra_const + "\n") + file.write("DE_CONST: ") + file.write(de_const + "\n") + print("\033[34m" + "File saved in the {} directory ".format(OUT_DIR) + "with the name {}_{}.cfg".format(obs_date, obs_short) diff --git a/Source/astrobs_v1_toolbox.py b/Source/astrobs_v1_toolbox.py index 69e6699..c1808e9 100644 --- a/Source/astrobs_v1_toolbox.py +++ b/Source/astrobs_v1_toolbox.py @@ -5,6 +5,7 @@ @ Observatory of Strasbourg """ import numpy as np +import matplotlib.pyplot as plt import astropy.time as time import astropy.coordinates as coord import astropy.units as u @@ -18,7 +19,7 @@ DONE = ["DONE", "OK"] QUIT = ["QUIT", "EXIT", "Q"] CANCEL = ["CANCEL", "BACK", "UNDO"] WRITE = ["WRITE", "SAVE"] -READ = ["READ", "OPEN"] +READ = ["READ", "OPEN", "LOAD"] CALIB = ["CALIB", "CALIBRATION"] SIMBAD = ["SIMBAD", "OBJECT"] REGION = ["SEARCH", "REGION"] @@ -26,9 +27,12 @@ MANUAL = ["MANUAL", "ADD"] ST = ["SIDEREAL", "ST"] SEQ = ["SEQUENCE", "SEQ"] CHECK = ["CHECK"] +PLOT = ["PLOT", "GRAPH"] HELP = ["HELP", "H", "?"] +FIGURE_DPI = 100 OUT_DIR = "./Output/" + DEFAULT_CONFIG = """# Default config LOCATION: None LAT: 0d00m00s @@ -45,10 +49,11 @@ OBS_BEGIN: 1970-01-01 12:00:00.000 OBS_END: 1970-01-01 12:00:00.000 ST_BEGIN: 12h00m0s ST_END: 12h00m00s -WINDOW_EAST: 0h00m00s -WINDOW_WEST: 23h59m59s -WINDOW_UPPER: 90d00m00s -WINDOW_LOWER: -90d00m00s +WINDOW_EAST: 00:00:00.0 +WINDOW_WEST: 23:59:59.9 +WINDOW_UPPER: +90:00:00.0 +WINDOW_LOWER: -90:00:00.0 +CONSTRAINT: ' ' LIKE ' ' """ COLS = ["seq", @@ -56,10 +61,6 @@ COLS = ["seq", "main_id", "ra", "dec", - "n_exp", - "t_exp", - "st_begin", - "st_end", "notes"] UNITS = ["", # SEQ @@ -67,10 +68,6 @@ UNITS = ["", # SEQ "", # MAIN_ID "h", # RA "deg", # DEC - "", # N_EXP - "s", # T_EXP - "h", # ST_BEGIN - "deg", # ST_END ""] TYPES = [np.int_, # SEQ @@ -78,10 +75,6 @@ TYPES = [np.int_, # SEQ np.str_(" west: print("\033[93m" @@ -213,7 +206,7 @@ def select_obj(table: Table, elif answer in HELP: print("") print_help() - elif answer in ALL: + elif answer.upper() in ALL: for i in range(len(line)): name = line[i]["name"] while name in table["name"]: @@ -247,10 +240,6 @@ def add_manual(table: Table, main_id: np.str_ = "", ra: coord.angles.core.Angle = None, dec: coord.angles.core.Angle = None, - n_exp: np.int_ = 0, - t_exp: u.quantity.Quantity = 0*u.s, - obj_begin: coord.angles.core.Angle = None, - obj_end: coord.angles.core.Angle = None, notes: np.str_ = "") -> Table: """ This function allows the user to add manually an entry to the table. @@ -270,34 +259,25 @@ def add_manual(table: Table, obj_end_str = "" if type(ra) == coord.angles.core.Angle: - ra_str = ra.to_string(unit=u.hour) + ra_str = ra.to_string(unit=u.hourangle, + sep=":", + pad=True) elif type(ra) == str: ra_str = ra if type(dec) == coord.angles.core.Angle: - dec_str = dec.to_string(unit=u.degree) + dec_str = dec.to_string(unit=u.degree, + sep=":", + pad=True, + alwayssign=True) elif type(dec) == str: dec_str = dec - if type(obj_begin) == coord.angles.core.Angle: - obj_begin_str = obj_begin.to_string() - elif type(obj_begin) == str: - obj_begin_str = obj_begin - - if type(obj_end) == coord.angles.core.Angle: - obj_end_str = obj_end.to_string() - elif type(obj_end) == str: - obj_end_str = obj_end - table.add_row([seq, name, main_id, ra_str, dec_str, - n_exp, - t_exp.to_value(u.s), - obj_begin_str, - obj_end_str, notes]) check_window(table, config, -1) return table @@ -306,8 +286,6 @@ def add_calib(table: Table, config: dict = None, seq: np.int_ = 0, name: np.str_ = "CALIB", - n_exp: np.int_ = 0, - t_exp: u.quantity.Quantity = 0*u.s, notes: np.str_ = "") -> Table: """ Add a calibration line to the table. @@ -329,8 +307,6 @@ def add_calib(table: Table, "CALIBRATION", "", "", - n_exp, - t_exp.to_value(u.s), "", "", notes]) @@ -342,10 +318,6 @@ def add_simbad(table: Table, config: dict, seq: np.int_ = 0, name: np.str_ = "", - n_exp: np.int_ = 0, - t_exp: u.quantity.Quantity = 0*u.s, - obj_begin: coord.angles.core.Angle = None, - obj_end: coord.angles.core.Angle = None, notes: np.str_ = "") -> Table: """ Add a target imported from Simbad using astroquery.Simbad: @@ -377,38 +349,26 @@ def add_simbad(table: Table, except KeyError: ra = coord.Angle(obj["RA"][i], unit=u.degree) dec = coord.Angle(obj["DEC"][i], unit=u.degree) - ra_str = ra.to_string(unit=u.hour) - dec_str = dec.to_string(unit=u.degree) - obj_begin_str = "" - obj_end_str = "" - - if type(obj_begin) == coord.angles.core.Angle: - obj_begin_str = obj_begin.to_string() - elif type(obj_begin) == str: - obj_begin_str = obj_begin - - if type(obj_end) == coord.angles.core.Angle: - obj_end_str = obj_end.to_string() - elif type(obj_end) == str: - obj_end_str = obj_end - + ra_str = ra.to_string(unit=u.hourangle, + sep=":", + pad=True) + dec_str = dec.to_string(unit=u.degree, + sep=":", + pad=True, + alwayssign=True) table.add_row([seq, name, main_id, ra_str, dec_str, - n_exp, - t_exp.to_value(u.s), - obj_begin_str, - obj_end_str, notes]) name = "" check_window(table, config) return table -def read_cfg(filename:str, - directory:str = OUT_DIR, - extension:str = ".cfg") -> dict: +def read_cfg(filename: str, + directory: str = OUT_DIR, + extension: str = ".cfg") -> dict: """ Read the configuration file @params: @@ -450,31 +410,6 @@ def read_cfg(filename:str, for i in range(len(params_index))} return config -def set_st_window(table: Table, - config: dict): - """ - This function computes the sidereal time window of observation for all - targets in the table, assuming an observation around the meridian. - @params: - - table: the target table - - config: the configuration dictionary - @return: - - table: the updated table - """ - before = coord.Angle(config["ST_BEGIN"]) \ - - coord.Angle(config["WINDOW_EAST"]) - after = coord.Angle(config["WINDOW_WEST"]) \ - - coord.Angle(config["ST_END"]) - for i in range(len(table)): - if not table[i]["main_id"] in SPECIAL: - table[i]["st_begin"] = (coord.Angle(table[i]["ra"]) \ - - before).wrap_at(24*u.h).to_string() - table[i]["st_end"] = (coord.Angle(table[i]["ra"]) \ - + after).wrap_at(24*u.h).to_string() - else: - continue - return table - def set_seq(table: str, config: dict, stack: bool = False): @@ -485,12 +420,13 @@ def set_seq(table: str, @params: - table: the target table - config: the configuration dictionary - - stack: if True, the first target is indexed with 1 (the last target is indexed with -1, if necessary) + - stack: if True, the first target is indexed with 1 + (the last target is indexed with -1, if necessary) @returns: - table: the updated table """ - window_east = coord.Angle(config["WINDOW_EAST"]).degree - window_west = coord.Angle(config["WINDOW_WEST"]).degree + window_east = coord.Angle(config["WINDOW_EAST"], unit=u.hourangle).degree + window_west = coord.Angle(config["WINDOW_WEST"], unit=u.hourangle).degree dupl_seq = np.argwhere( np.unique(table["seq"], return_counts=True)[1] > 1).flatten() index_dupl = np.argwhere(np.any( @@ -508,23 +444,13 @@ def set_seq(table: str, order_table = table[index] table["seq"][index] = np.full(len(index), 0) order_table.add_columns( - [coord.Angle(order_table["ra"]).degree], - names=["RA_DEG"]) - if window_east <= window_west: - order_table.sort("RA_DEG") - else : - w1 = np.argwhere( - order_table["RA_DEG"] > (window_east + window_west)/2) - w2 = np.argwhere( - order_table["RA_DEG"] <= (window_east + window_west)/2) - order = np.zeros(len(order_table)) - order[w1] = 1 - order[w2] = 2 - order_table.add_columns( - [order], - names=["ORDER"]) - order_table.sort("RA_DEG") - order_table.sort("ORDER") + [coord.Angle(order_table["ra"], unit=u.hourangle).degree], + names=["ra_deg"]) + w_before = np.argwhere(order_table["ra_deg"] < window_east) + + order_table["ra_deg"][w_before] += 360 + order_table.sort("ra_deg") + i = 0 seq = 0 while i < len(order_table): @@ -654,28 +580,33 @@ def read_table(filename: str, format=format_) for i in range(len(data)): line = data[i] + if "seq" in line.columns: seq = int(line["seq"]) + else: seq=0 if "name" in line.columns: name = line["name"] else: name = "{}_{}".format(filename, i) if "main_id" in line.columns: main_id = line["main_id"] else: main_id = name if "ra" in line.columns: - if "h" not in str(line["ra"]): - ra = coord.Angle("{}d".format(line["ra"])).to_string(unit=u.hour) + if ":" not in str(line["ra"]) \ + and str(line["ra"]).upper() not in ["", " ", "NONE"]: + ra = coord.Angle("{}d".format(line["ra"])).to_string( + unit=u.hourangle, + sep=":", + pad=True + ) else: ra = line["ra"] else: ra = "" if "dec" in line.columns: - if "d" not in str(line["dec"]): - dec = coord.Angle("{}d".format(line["dec"])).to_string(unit=u.degree) + if ":" not in str(line["dec"]) \ + and str(line["dec"]).upper() not in ["", " ", "NONE"]: + dec = coord.Angle("{}d".format(line["dec"])).to_string( + unit=u.degree, + sep=":", + pad=True, + alwayssign=True + ) else: dec = line["dec"] else: dec = "" - if "n_exp" in line.columns: n_exp = line["n_exp"] - else: n_exp = 0 - if "t_exp" in line.columns: t_exp = line["t_exp"] - else: t_exp = 0. - if "st_begin" in line.columns: st_begin = line["st_begin"] - else: st_begin = "" - if "st_end" in line.columns: st_end = line["st_end"] - else: st_end = "" if "notes" in line.columns: notes = line["notes"] else: notes = "" while name in table["name"]: @@ -685,15 +616,11 @@ def read_table(filename: str, if col not in COLS: notes += " & {}: {}".format(col, line[col]) - table.add_row({"seq": 0, + table.add_row({"seq": seq, "name": name, "main_id": main_id, "ra": ra, "dec": dec, - "n_exp": n_exp, - "t_exp": t_exp, - "st_begin": st_begin, - "st_end": st_end, "notes": notes}) except Exception as e: @@ -701,8 +628,136 @@ def read_table(filename: str, + ("Error! File cannot be loaded " "from the {} directory").format(directory) + "\033[0m") + print(e) return create_table() return table + +def make_plot(table: Table, + config: dict) -> int: + """ + Make an observation plot of the targets + @params: + - table: the target table + - config: the configuration dictionary + @returns: + - 0 + """ + def get_timestamp(datetime, ref): + return (datetime - ref)/np.timedelta64(1, 's') + def get_time(timestamp, ref): + return timestamp*np.timedelta64(1, 's') + ref + + if "YII_light_1" in plt.style.available: plt.style.use("YII_light_1") + plt.rcParams["figure.dpi"] = FIGURE_DPI + plt.rcParams["figure.facecolor"] = "#121212" + plt.rcParams['axes.facecolor'] = '#216576' + COLOR = 'DDDDDD' + plt.rcParams['text.color'] = COLOR + plt.rcParams['axes.labelcolor'] = COLOR + plt.rcParams['xtick.color'] = COLOR + plt.rcParams['ytick.color'] = COLOR + + fig, ax = plt.subplots(1) + ax.set_title("Location: {}".format(config["LOCATION"])) + + sun_set = np.datetime64(config["SUN_SET"]) + sun_civil = np.datetime64(config["SUN_SET_CIVIL"]) + sun_nautical = np.datetime64(config["SUN_SET_NAUTICAL"]) + sun_astronomical = np.datetime64(config["SUN_SET_ASTRONOMICAL"]) + sun_rastronomical = np.datetime64(config["SUN_RISE_ASTRONOMICAL"]) + sun_rnautical = np.datetime64(config["SUN_RISE_NAUTICAL"]) + sun_rcivil = np.datetime64(config["SUN_RISE_CIVIL"]) + sun_rise = np.datetime64(config["SUN_RISE"]) + + obs_begin = np.datetime64(config["OBS_BEGIN"]) + obs_end = np.datetime64(config["OBS_END"]) + + timestamp_set = get_timestamp(sun_set, obs_begin) + timestamp_civil = get_timestamp(sun_civil, obs_begin) + timestamp_nautical = get_timestamp(sun_nautical, obs_begin) + timestamp_astronomical = get_timestamp(sun_astronomical, obs_begin) + timestamp_rastronomical = get_timestamp(sun_rastronomical, obs_begin) + timestamp_rnautical = get_timestamp(sun_rnautical, obs_begin) + timestamp_rcivil = get_timestamp(sun_rcivil, obs_begin) + timestamp_rise = get_timestamp(sun_rise, obs_begin) + + timestamp_begin = get_timestamp(obs_begin, obs_begin) + timestamp_end = get_timestamp(obs_end, obs_begin) + + st_begin = coord.Angle(config["ST_BEGIN"], unit=u.hourangle).degree + st_end = coord.Angle(config["ST_END"], unit=u.hourangle).degree + window_east = coord.Angle(config["WINDOW_EAST"], unit=u.hourangle).degree + window_west = coord.Angle(config["WINDOW_WEST"], unit=u.hourangle).degree + + if st_end < st_begin: + st_end += 360 + + slope = (timestamp_end - timestamp_begin)/(st_end - st_begin) + + def compute_timestamp(st, a=slope, sb=st_begin, hb=timestamp_begin): + return (st-sb) * a + hb + def compute_st(timestamp, a=slope, sb=st_begin, hb=timestamp_begin): + return (timestamp-hb) / a + sb + + N = len(table) + + # Night times + ax.axvspan(sun_set, sun_rise, color="k", alpha=0.2) + ax.axvspan(sun_civil, sun_rcivil, color="k", alpha=0.4) + ax.axvspan(sun_nautical, sun_rnautical, color="k", alpha=0.6) + ax.axvspan(sun_astronomical, sun_rastronomical, color="k", alpha=0.8) + + # Observation time + ax.axvspan(obs_begin, obs_end, color="C0", alpha=0.3) + + # Observation range + if st_begin < window_east: + st_begin += 360 + if window_west < st_end: + window_west += 360 + a_before = st_begin - window_east + a_after = window_west - st_end + + plot_colors = ["#ED1C24", "#E8BD0F"] + N_colors = len(plot_colors) + N_same = 4 + + # Targets + for i in range(N): + ra = coord.Angle(table["ra"][i], unit=u.hourangle).degree + if ra > window_west: + ra -= 360 + ra_before = ra + a_before + ra_after = ra - a_after + + h = compute_timestamp(ra) + h_before = compute_timestamp(ra_before) + h_after = compute_timestamp(ra_after) + + time_ra = get_time(h, obs_begin) + time_before = get_time(h_before, obs_begin) + time_after = get_time(h_after, obs_begin) + + color_index = (i // N_same) % N_colors + color = plot_colors[color_index] + + ax.plot([time_before, time_after], [i,i], color=color, marker="|") + ax.scatter([time_ra], [i], s=5, color=color) + + ax.text(time_after, i, table["name"][i] + " ", + horizontalalignment="right", verticalalignment="center") + ra_text = table["ra"][i] + dec_text = table["dec"][i] + ax.text(time_before, i, " {}{}".format(ra_text, dec_text)) + + ax.set_xlabel("Observation date and time (UTC)") + ax.set_yticks(range(N), table["seq"]) + + plt.show(block=False) + + # obs_time = np.arange(sun_set, sun_rise, dtype="datetime64[m]") + return 0 + def print_help(): hilfe = ("\033[36m" "Help will be always given to those who ask for it [1].\n" @@ -712,7 +767,7 @@ def print_help(): "(WARNING: this does not save the current state!)\n" "\t- write, save: write the current table in a file " "(no options available yet)\n" - "\t- read [filename], open [filename]: " + "\t- read [filename], open [filename], load [filename]: " "loads the file \"filename\" " "in the current table (no additional options available yet)\n" "\t- calibration, calib: adds a calibration in the target list\n" @@ -720,16 +775,18 @@ def print_help(): "add an object from simbad\n" "\t- search [ra] [dec] [radius], region [ra] [dec] [radius]: " "search a region centred on the ra/dec coordinates, " - "with a given radius (coordinates should be expressed as " - "12h30m30s, 90d30m30s or 90.555d)\n" - "\t- manual [name] [seq (optional)], " - "add [name] [seq (optional)]: " + "with a given radius (ra is given in hour, dec in degree " + "and the radius in any specified unit, " + "e.g. search 01:03:40 +35:40:20 30\')\n" + "\t- manual [name] -s [seq (optional)], " + "add [name] -s [seq (optional)]: " "manually add a target (only the name the sequence are " "available for now)\n" - "\t- sidereal, st: computes the sidereal time for each target\n" "\t- sequence, seq: computes the sequence order for each " "target\n" "\t- check: check if all targets are in the observation field\n" + "\t- plot, graph: creates a graph with all the targets " + "observation date and time" "\n" "General actions: \n" "\t- cancel, back: cancels the current action\n" @@ -742,9 +799,10 @@ def print_help(): "\033[0m") print(hilfe) return None + def resolve_input(text: str, table: Table, - config: dict): + config: dict) -> Table: """ Resolve the input to execute the expected function in an interactive way @@ -772,7 +830,9 @@ def resolve_input(text: str, select_obj(table, swap_table, config) elif args[0].upper() in REGION: name = " ".join(args[1]+" "+args[2]) - region = coord.SkyCoord(ra=args[1], dec=args[2]) + ra = coord.Angle(args[1], unit=u.hourangle) + dec = coord.Angle(args[2], unit=u.degree) + region = coord.SkyCoord(ra=ra, dec=dec) radius = coord.Angle(args[3]) obj = Simbad.query_region(region, radius) swap_table = add_simbad(swap_table, obj, config) @@ -791,12 +851,12 @@ def resolve_input(text: str, add_manual(table, config, seq, name) elif args[0].upper() in CALIB: # TODO ajouter args add_calib(table, config) - elif args[0].upper() in ST: - set_st_window(table, config) elif args[0].upper() in SEQ: # FIXME marche bien une fois mais pas deux ?? set_seq(table, config) elif args[0].upper() in CHECK: check_window(table, config) + elif args[0].upper() in PLOT: + make_plot(table, config) elif args[0].upper() in HELP: print_help() elif args[0].upper() in QUIT: diff --git a/requirements.txt b/requirements.txt index aa5a2d0..2bb6e43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,16 +4,24 @@ astroquery==0.4.7 beautifulsoup4==4.12.3 certifi==2024.8.30 charset-normalizer==3.4.0 +contourpy==1.3.1 +cycler==0.12.1 +fonttools==4.55.0 html5lib==1.1 idna==3.10 jaraco.classes==3.4.0 jaraco.context==6.0.1 jaraco.functools==4.1.0 keyring==25.5.0 +kiwisolver==1.4.7 +matplotlib==3.9.3 more-itertools==10.5.0 numpy==2.1.3 packaging==24.2 +pillow==11.0.0 pyerfa==2.0.1.5 +pyparsing==3.2.0 +python-dateutil==2.9.0.post0 pyvo==1.6 PyYAML==6.0.2 requests==2.32.3