diff --git a/aperoll/utils.py b/aperoll/utils.py index d01d973..63b2fc6 100644 --- a/aperoll/utils.py +++ b/aperoll/utils.py @@ -1,11 +1,18 @@ import functools +import gzip +import json +import pickle import traceback +import maude import numpy as np from chandra_aca.transform import ( pixels_to_yagzag, yagzag_to_pixels, ) +from cxotime.cxotime import CxoTime +from kadi.commands.observations import get_detector_and_sim_offset +from Quaternion import Quat from ska_helpers import logging logger = logging.basic_logger("aperoll") @@ -25,6 +32,7 @@ class AperollException(RuntimeError): def log_exception(msg, exc, level="DEBUG"): import logging + trace = traceback.extract_tb(exc.__traceback__) level = logging.__getattribute__(level) logger.log(level, f"{msg}: {exc}") @@ -127,3 +135,148 @@ def get_camera_fov_frame(): ) return frame + + +def get_default_parameters(): + """ + Get default initial parameters from current telemetry. + """ + + msid_list = ["3TSCPOS", "AACCCDPT"] + [f"aoattqt{i}".upper() for i in range(1, 5)] + msids = maude.get_msids(msid_list) + data = {msid: msids["data"][i]["values"][-1] for i, msid in enumerate(msid_list)} + q = Quat(q=[data[f"AOATTQT{i}"] for i in range(1, 5)]) + + instrument, sim_offset = get_detector_and_sim_offset(data["3TSCPOS"]) + t_ccd = (data["AACCCDPT"] - 32) * 5 / 9 + + result = { + "date": CxoTime().date, + "attitude": q, + "ra": q.ra, + "dec": q.dec, + "roll": q.roll, + "instrument": instrument, + "sim_offset": sim_offset, + "t_ccd": t_ccd, + "obsid": 0, + "man_angle": 0, + "dither_acq_y": 16, + "dither_acq_z": 16, + "dither_guide_y": 16, + "dither_guide_z": 16, + "n_fid": 0, + "n_guide": 8, + } + + return result + + +def get_parameters_from_yoshi(filename, obsid=None): + """ + Get initial parameters from a Yoshi JSON file. + """ + + with open(filename) as fh: + contents = json.load(fh) + if obsid is not None: + contents = [obs for obs in contents if obs["obsid"] == obsid] + if not contents: + raise AperollException(f"OBSID {obsid} not found in {filename}") + + if not contents: + raise AperollException(f"No entries found in {filename}") + + yoshi_params = contents[0] # assuming there is only one entry + + yoshi_params["date"] = yoshi_params["obs_date"] + yoshi_params["ra"] = yoshi_params["ra_targ"] + yoshi_params["dec"] = yoshi_params["dec_targ"] + yoshi_params["roll"] = yoshi_params["roll_targ"] + yoshi_params["instrument"] = yoshi_params["detector"] + for key in [ + "obs_date", + "ra_targ", + "dec_targ", + "roll_targ", + "detector", + ]: + del yoshi_params[key] + + if abs(yoshi_params.get("obsid", 0)) < 38000: + yoshi_params["n_fid"] = "3" + yoshi_params["n_guide"] = "5" + else: + yoshi_params["n_fid"] = "0" + yoshi_params["n_guide"] = "8" + + att = Quat( + equatorial=(yoshi_params["ra"], yoshi_params["dec"], yoshi_params["roll"]) + ) + + default = get_default_parameters() + parameters = { + "obsid": yoshi_params.get("obsid", default.get("obsid", 0)), + "man_angle": yoshi_params.get("man_angle", default.get("man_angle", 0)), + "dither_acq_y": yoshi_params.get("dither_y", default.get("dither_acq_y", 16)), + "dither_acq_z": yoshi_params.get("dither_z", default.get("dither_acq_z", 16)), + "dither_guide_y": yoshi_params.get( + "dither_y", default.get("dither_guide_y", 16) + ), + "dither_guide_z": yoshi_params.get( + "dither_z", default.get("dither_guide_z", 16) + ), + "date": yoshi_params.get("date", default["date"]), + "attitude": att, + "ra": yoshi_params.get("ra", default["attitude"].ra), + "dec": yoshi_params.get("dec", default["attitude"].dec), + "roll": yoshi_params.get("roll", default["attitude"].roll), + "t_ccd": yoshi_params.get("t_ccd", default["t_ccd"]), + "instrument": yoshi_params.get("instrument", default["instrument"]), + "n_guide": yoshi_params["n_guide"], + "n_fid": yoshi_params["n_fid"], + } + return parameters + + +def get_parameters_from_pickle(filename, obsid=None): + """ + Get initial parameters from a proseco pickle file. + """ + open_fcn = open if filename.endswith(".pkl") else gzip.open + with open_fcn(filename, "rb") as fh: + catalogs = pickle.load(fh) + + if not catalogs: + raise AperollException(f"No entries found in {filename}") + + if obsid is None: + # this is ugly but it works whether the keys are strings of floats or ints + obsid = int(np.round(float(list(catalogs.keys())[0]))) + + if float(obsid) not in catalogs: + raise AperollException(f"OBSID {obsid} not found in {filename}") + + catalog = catalogs[float(obsid)] + + parameters = { + "obsid": obsid, + "man_angle": catalog.man_angle, + "dither_acq_y": catalog.dither_acq.y, + "dither_acq_z": catalog.dither_acq.z, + "dither_guide_y": catalog.dither_guide.y, + "dither_guide_z": catalog.dither_guide.z, + "date": CxoTime( + catalog.date + ).date, # date is not guaranteed to be a fixed type in pickle + "attitude": catalog.att, + "ra": catalog.att.ra, + "dec": catalog.att.dec, + "roll": catalog.att.roll, + "t_ccd_acq": catalog.t_ccd_acq, + "t_ccd_guide": catalog.t_ccd_guide, + "instrument": catalog.detector, + "n_guide": catalog.n_guide, + "n_fid": catalog.n_fid, + } + return parameters diff --git a/aperoll/widgets/line_edit.py b/aperoll/widgets/line_edit.py new file mode 100644 index 0000000..19ce989 --- /dev/null +++ b/aperoll/widgets/line_edit.py @@ -0,0 +1,36 @@ +from PyQt5 import QtCore as QtC +from PyQt5 import QtWidgets as QtW + + +class LineEdit(QtW.QLineEdit): + """ + A QLineEdit with a signal emitted when pressing Enter, loosing focus or calling setText. + + The signal is emitted only if the text changed. + """ + + value_changed = QtC.pyqtSignal(str) + + def __init__(self, parent): + super().__init__(parent) + self._prev_text = self.text() + + def setText(self, text, emit=False): + super().setText(text) + # in the following, emit=False so the signal is not emitted + self._check_value(emit=emit) + + def keyPressEvent(self, event): + super().keyPressEvent(event) + if event.key() == QtC.Qt.Key_Return: + self._check_value() + + def focusOutEvent(self, event): + super().focusOutEvent(event) + self._check_value() + + def _check_value(self, emit=True): + if self.text() != self._prev_text: + self._prev_text = self.text() + if emit: + self.value_changed.emit(self.text()) diff --git a/aperoll/widgets/main_window.py b/aperoll/widgets/main_window.py deleted file mode 100644 index 2f5cbcb..0000000 --- a/aperoll/widgets/main_window.py +++ /dev/null @@ -1,372 +0,0 @@ -# from PyQt5 import QtCore as QtC, QtWidgets as QtW, QtGui as QtG -import gzip -import os -import pickle -import tarfile -from pathlib import Path -from tempfile import TemporaryDirectory - -import numpy as np -import PyQt5.QtWebEngineWidgets as QtWe -import PyQt5.QtWidgets as QtW -import sparkles -from astropy import units as u -from cxotime import CxoTime -from proseco import get_aca_catalog -from PyQt5 import QtCore as QtC -from Quaternion import Quat -from ska_helpers import utils - -from aperoll.utils import logger - -from .parameters import Parameters -from .star_plot import StarPlot -from .starcat_review import StarcatReview - -STYLE = """ - -""" - - -class WebPage(QtWe.QWebEnginePage): - def __init__(self, parent=None): - super().__init__(parent) - - # trick from https://stackoverflow.com/questions/54920726/how-make-any-link-blank-open-in-same-window-using-qwebengine - def createWindow(self, _type): - page = WebPage(self) - page.urlChanged.connect(self.on_url_changed) - return page - - @QtC.pyqtSlot(QtC.QUrl) - def on_url_changed(self, url): - page = self.sender() - self.setUrl(url) - page.deleteLater() - - -class MainWindow(QtW.QMainWindow): - def __init__(self, opts=None): # noqa: PLR0915 - super().__init__() - opts = {} if opts is None else opts - opts = {k: opts[k] for k in opts if opts[k] is not None} - - self.opts = opts - - self._main = QtW.QWidget() - self.setCentralWidget(self._main) - - self.menu_bar = QtW.QMenuBar() - self.setMenuBar(self.menu_bar) - - self.fileMenu = self.menu_bar.addMenu("&File") - export_action = QtW.QAction("&Export Pickle", self) - export_action.triggered.connect(self._export_proseco) - self.fileMenu.addAction(export_action) - export_action = QtW.QAction("&Export Sparkles", self) - export_action.triggered.connect(self._export_sparkles) - self.fileMenu.addAction(export_action) - - self.web_page = None - - self.plot = StarPlot() - self.parameters = Parameters(**opts) - self.starcat_review = StarcatReview() - - layout = QtW.QVBoxLayout(self._main) - layout_2 = QtW.QHBoxLayout() - - layout.addWidget(self.parameters) - layout_2.addWidget(self.starcat_review) - layout_2.addWidget(self.plot) - layout.addLayout(layout_2) - - layout.setStretch(0, 1) # the dialog on top should not stretch much - layout.setStretch(1, 10) - self._main.setLayout(layout) - - self.plot.include_star.connect(self.parameters.include_star) - # self.plot.exclude_star.connect(self.parameters.exclude_star) - - self.parameters.do_it.connect(self._run_proseco) - self.plot.update_proseco.connect(self._run_proseco) - self.parameters.run_sparkles.connect(self._run_sparkles) - self.parameters.reset.connect(self._reset) - self.parameters.draw_test.connect(self._draw_test) - self.parameters.parameters_changed.connect(self._parameters_changed) - self.plot.attitude_changed_eq.connect(self.parameters.set_ra_dec) - - self._data = Data(self.parameters.proseco_args()) - self.outdir = Path(os.getcwd()) - - self._init() - - starcat = None - if "file" in opts: - filename = opts.get("file") - catalogs = {} - if filename.endswith(".pkl"): - with open(filename, "rb") as fh: - catalogs = pickle.load(fh) - elif filename.endswith(".pkl.gz"): - with gzip.open(filename, "rb") as fh: - catalogs = pickle.load(fh) - if catalogs: - obsids = [int(np.round(float(k))) for k in catalogs] - if "obsid" not in opts or opts["obsid"] is None: - starcat = catalogs[obsids[0]] - else: - starcat = catalogs[opts["obsid"]] - aca = starcat.get_review_table() - sparkles.core.check_catalog(aca) - - if starcat is not None: - self.plot.set_catalog(starcat) - self.starcat_review.set_catalog(aca) - # make sure the catalog is not overwritten automatically - self.plot.scene.state.auto_proseco = False - - if self.plot.scene.state.auto_proseco: - self._run_proseco() - - def closeEvent(self, event): - if self.web_page is not None: - del self.web_page - self.web_page = None - event.accept() - - def _parameters_changed(self): - proseco_args = self.parameters.proseco_args() - self.plot.set_base_attitude(proseco_args["att"]) - self._data.reset(proseco_args) - if self.plot.scene.state.auto_proseco and not self.plot.view.moving: - self._run_proseco() - - def _init(self): - if self.parameters.values: - ra, dec = self.parameters.values["ra"], self.parameters.values["dec"] - roll = self.parameters.values["roll"] - time = CxoTime(self.parameters.values["date"]) - aca_attitude = Quat( - equatorial=(float(ra / u.deg), float(dec / u.deg), roll) - ) - self.plot.set_base_attitude(aca_attitude) - self.plot.set_time(time) - self.plot.scene.state = "Proseco" - - def _reset(self): - self.parameters.set_parameters(**self.opts) - self.starcat_review.reset() - self._data.reset(self.parameters.proseco_args()) - self._init() - - def _draw_test(self): - if self.parameters.values: - ra, dec = self.parameters.values["ra"], self.parameters.values["dec"] - roll = self.parameters.values["roll"] - aca_attitude = Quat( - equatorial=(float(ra / u.deg), float(dec / u.deg), roll) - ) - dq = self.plot._base_attitude.dq(aca_attitude) - self.plot.show_test_stars( - ra_offset=dq.ra, dec_offset=dq.dec, roll_offset=dq.roll - ) - - def _run_proseco(self): - """ - Display the star catalog. - """ - if self._data.proseco: - self.starcat_review.set_catalog(self._data.proseco["aca"]) - self.plot.set_catalog(self._data.proseco["catalog"]) - - def _export_proseco(self): - """ - Save the star catalog in a pickle file. - """ - if self._data.proseco: - catalog = self._data.proseco["catalog"] - dialog = QtW.QFileDialog( - self, - "Export Pickle", - str(self.outdir / f"aperoll-obsid_{catalog.obsid:.0f}.pkl"), - ) - dialog.setAcceptMode(QtW.QFileDialog.AcceptSave) - dialog.setDefaultSuffix("pkl") - rc = dialog.exec() - if rc: - self._data.export_proseco(dialog.selectedFiles()[0]) - - def _export_sparkles(self): - """ - Save the sparkles report to a tarball. - """ - if self._data.sparkles: - catalog = self._data.proseco["catalog"] - # for some reason, the extension hidden but it works - dialog = QtW.QFileDialog( - self, - "Export Pickle", - str(self.outdir / f"aperoll-obsid_{catalog.obsid:.0f}.tgz"), - ) - dialog.setAcceptMode(QtW.QFileDialog.AcceptSave) - dialog.setDefaultSuffix(".tgz") - rc = dialog.exec() - if rc: - self._data.export_sparkles(dialog.selectedFiles()[0]) - - def _run_sparkles(self): - """ - Display the sparkles report in a web browser. - """ - if self._data.sparkles: - try: - w = QtW.QMainWindow(self) - w.resize(1400, 1000) - web = QtWe.QWebEngineView(w) - w.setCentralWidget(web) - self.web_page = WebPage() - web.setPage(self.web_page) - url = self._data.sparkles / "index.html" - web.load(QtC.QUrl(f"file://{url}")) - web.show() - w.show() - except Exception as e: - logger.warning(e) - - -class CachedVal: - def __init__(self, func): - self._func = func - self.reset() - - def reset(self): - self._value = utils.LazyVal(self._func) - - @property - def val(self): - return self._value.val - - -class Data: - def __init__(self, parameters=None) -> None: - self._proseco = CachedVal(self.run_proseco) - self._sparkles = CachedVal(self.run_sparkles) - self.parameters = parameters - self._tmp_dir = TemporaryDirectory() - self._dir = Path(self._tmp_dir.name) - - def reset(self, parameters): - self.parameters = parameters - self._proseco.reset() - self._sparkles.reset() - - @property - def proseco(self): - return self._proseco.val - - @property - def sparkles(self): - return self._sparkles.val - - def export_proseco(self, outfile): - if self.proseco: - outfile = self._dir / outfile - catalog = self.proseco["catalog"] - if catalog: - with open(outfile, "wb") as fh: - pickle.dump({catalog.obsid: catalog}, fh) - - def export_sparkles(self, outfile): - if self.sparkles: - outfile = self._dir / outfile - if self.sparkles: - dest = Path(outfile.name.replace(".tar", "").replace(".gz", "")) - with tarfile.open(outfile, "w") as tar: - for name in self.sparkles.glob("**/*"): - tar.add( - name, - arcname=dest / name.relative_to(self._dir / "sparkles"), - ) - - def run_proseco(self): - if self.parameters: - catalog = get_aca_catalog(**self.parameters) - aca = catalog.get_review_table() - sparkles.core.check_catalog(aca) - - return { - "catalog": catalog, - "aca": aca, - } - return {} - - def run_sparkles(self): - if self.proseco and self.proseco["catalog"]: - sparkles.run_aca_review( - "Exploration", - acars=[self.proseco["catalog"].get_review_table()], - report_dir=self._dir / "sparkles", - report_level="all", - roll_level="none", - ) - return self._dir / "sparkles" diff --git a/aperoll/widgets/parameters.py b/aperoll/widgets/parameters.py deleted file mode 100644 index 18aa7cb..0000000 --- a/aperoll/widgets/parameters.py +++ /dev/null @@ -1,524 +0,0 @@ -import gzip -import json -import pickle -from pprint import pformat - -import maude -import numpy as np -import Ska.Sun as sun -from astropy import units as u -from cxotime.cxotime import CxoTime -from kadi.commands.observations import get_detector_and_sim_offset -from PyQt5 import QtCore as QtC -from PyQt5 import QtWidgets as QtW -from Quaternion import Quat - -from aperoll.utils import AperollException, logger - - -class LineEdit(QtW.QLineEdit): - """ - A QLineEdit with a signal emitted when pressing Enter, loosing focus or calling setText. - - The signal is emitted only if the text changed. - """ - - value_changed = QtC.pyqtSignal(str) - - def __init__(self, parent): - super().__init__(parent) - self._prev_text = self.text() - - def setText(self, text, emit=False): - super().setText(text) - # in the following, emit=False so the signal is not emitted - self._check_value(emit=emit) - - def keyPressEvent(self, event): - super().keyPressEvent(event) - if event.key() == QtC.Qt.Key_Return: - self._check_value() - - def focusOutEvent(self, event): - super().focusOutEvent(event) - self._check_value() - - def _check_value(self, emit=True): - if self.text() != self._prev_text: - self._prev_text = self.text() - if emit: - self.value_changed.emit(self.text()) - - -def get_default_parameters(): - """ - Get default initial parameters from current telemetry. - """ - - msid_list = ["3TSCPOS", "AACCCDPT"] + [f"aoattqt{i}".upper() for i in range(1, 5)] - msids = maude.get_msids(msid_list) - data = {msid: msids["data"][i]["values"][-1] for i, msid in enumerate(msid_list)} - q = Quat(q=[data[f"AOATTQT{i}"] for i in range(1, 5)]) - - instrument, sim_offset = get_detector_and_sim_offset(data["3TSCPOS"]) - t_ccd = (data["AACCCDPT"] - 32) * 5 / 9 - - result = { - "date": CxoTime().date, - "attitude": q, - "ra": q.ra, - "dec": q.dec, - "roll": q.roll, - "instrument": instrument, - "sim_offset": sim_offset, - "t_ccd": t_ccd, - "obsid": 0, - "man_angle": 0, - "dither_acq_y": 16, - "dither_acq_z": 16, - "dither_guide_y": 16, - "dither_guide_z": 16, - "n_fid": 0, - "n_guide": 8, - } - - return result - - -def get_parameters_from_yoshi(filename, obsid=None): - """ - Get initial parameters from a Yoshi JSON file. - """ - - with open(filename) as fh: - contents = json.load(fh) - if obsid is not None: - contents = [obs for obs in contents if obs["obsid"] == obsid] - if not contents: - raise AperollException(f"OBSID {obsid} not found in {filename}") - - if not contents: - raise AperollException(f"No entries found in {filename}") - - yoshi_params = contents[0] # assuming there is only one entry - - yoshi_params["date"] = yoshi_params["obs_date"] - yoshi_params["ra"] = yoshi_params["ra_targ"] - yoshi_params["dec"] = yoshi_params["dec_targ"] - yoshi_params["roll"] = yoshi_params["roll_targ"] - yoshi_params["instrument"] = yoshi_params["detector"] - for key in [ - "obs_date", - "ra_targ", - "dec_targ", - "roll_targ", - "detector", - ]: - del yoshi_params[key] - - if abs(yoshi_params.get("obsid", 0)) < 38000: - yoshi_params["n_fid"] = "3" - yoshi_params["n_guide"] = "5" - else: - yoshi_params["n_fid"] = "0" - yoshi_params["n_guide"] = "8" - - att = Quat( - equatorial=(yoshi_params["ra"], yoshi_params["dec"], yoshi_params["roll"]) - ) - - default = get_default_parameters() - parameters = { - "obsid": yoshi_params.get("obsid", default.get("obsid", 0)), - "man_angle": yoshi_params.get("man_angle", default.get("man_angle", 0)), - "dither_acq_y": yoshi_params.get("dither_y", default.get("dither_acq_y", 16)), - "dither_acq_z": yoshi_params.get("dither_z", default.get("dither_acq_z", 16)), - "dither_guide_y": yoshi_params.get( - "dither_y", default.get("dither_guide_y", 16) - ), - "dither_guide_z": yoshi_params.get( - "dither_z", default.get("dither_guide_z", 16) - ), - "date": yoshi_params.get("date", default["date"]), - "attitude": att, - "ra": yoshi_params.get("ra", default["attitude"].ra), - "dec": yoshi_params.get("dec", default["attitude"].dec), - "roll": yoshi_params.get("roll", default["attitude"].roll), - "t_ccd": yoshi_params.get("t_ccd", default["t_ccd"]), - "instrument": yoshi_params.get("instrument", default["instrument"]), - "n_guide": yoshi_params["n_guide"], - "n_fid": yoshi_params["n_fid"], - } - return parameters - - -def get_parameters_from_pickle(filename, obsid=None): - """ - Get initial parameters from a proseco pickle file. - """ - open_fcn = open if filename.endswith(".pkl") else gzip.open - with open_fcn(filename, "rb") as fh: - catalogs = pickle.load(fh) - - if not catalogs: - raise AperollException(f"No entries found in {filename}") - - if obsid is None: - # this is ugly but it works whether the keys are strings of floats or ints - obsid = int(np.round(float(list(catalogs.keys())[0]))) - - if float(obsid) not in catalogs: - raise AperollException(f"OBSID {obsid} not found in {filename}") - - catalog = catalogs[float(obsid)] - - parameters = { - "obsid": obsid, - "man_angle": catalog.man_angle, - "dither_acq_y": catalog.dither_acq.y, - "dither_acq_z": catalog.dither_acq.z, - "dither_guide_y": catalog.dither_guide.y, - "dither_guide_z": catalog.dither_guide.z, - "date": CxoTime( - catalog.date - ).date, # date is not guaranteed to be a fixed type in pickle - "attitude": catalog.att, - "ra": catalog.att.ra, - "dec": catalog.att.dec, - "roll": catalog.att.roll, - "t_ccd_acq": catalog.t_ccd_acq, - "t_ccd_guide": catalog.t_ccd_guide, - "instrument": catalog.detector, - "n_guide": catalog.n_guide, - "n_fid": catalog.n_fid, - } - return parameters - - -class Parameters(QtW.QWidget): - do_it = QtC.pyqtSignal() - run_sparkles = QtC.pyqtSignal() - draw_test = QtC.pyqtSignal() - reset = QtC.pyqtSignal() - parameters_changed = QtC.pyqtSignal() - - def __init__(self, **kwargs): # noqa: PLR0915 - super().__init__() - self.date_edit = LineEdit(self) - self.obsid_edit = LineEdit(self) - self.ra_edit = LineEdit(self) - self.dec_edit = LineEdit(self) - self.roll_edit = LineEdit(self) - self.n_guide_edit = LineEdit(self) - self.n_fid_edit = LineEdit(self) - self.n_t_ccd_edit = LineEdit(self) - self.man_angle_edit = LineEdit(self) - self.instrument_edit = QtW.QComboBox(self) - self.instrument_edit.addItems(["ACIS-S", "ACIS-I", "HRC-S", "HRC-I"]) - self.do = QtW.QPushButton("Get Catalog") - self.reset_button = QtW.QPushButton("Reset") - self.run_sparkles_button = QtW.QPushButton("Run Sparkles") - self.draw_test_button = QtW.QPushButton("Draw Test") - self.include = { - "acq": QtW.QListWidget(self), - "guide": QtW.QListWidget(self), - } - self.exclude = { - "acq": QtW.QListWidget(self), - "guide": QtW.QListWidget(self), - } - self.dither_acq_y_edit = LineEdit(self) - self.dither_acq_z_edit = LineEdit(self) - self.dither_guide_y_edit = LineEdit(self) - self.dither_guide_z_edit = LineEdit(self) - - self.date_edit.value_changed.connect(self._values_changed) - self.obsid_edit.value_changed.connect(self._values_changed) - self.ra_edit.value_changed.connect(self._values_changed) - self.dec_edit.value_changed.connect(self._values_changed) - self.roll_edit.value_changed.connect(self._values_changed) - self.n_guide_edit.value_changed.connect(self._values_changed) - self.n_fid_edit.value_changed.connect(self._values_changed) - self.n_t_ccd_edit.value_changed.connect(self._values_changed) - self.man_angle_edit.value_changed.connect(self._values_changed) - self.instrument_edit.currentIndexChanged.connect(self._values_changed) - self.include["acq"].model().rowsInserted.connect(self._values_changed) - self.include["guide"].model().rowsInserted.connect(self._values_changed) - self.exclude["acq"].model().rowsInserted.connect(self._values_changed) - self.exclude["guide"].model().rowsInserted.connect(self._values_changed) - self.include["acq"].model().rowsRemoved.connect(self._values_changed) - self.include["guide"].model().rowsRemoved.connect(self._values_changed) - self.exclude["acq"].model().rowsRemoved.connect(self._values_changed) - self.exclude["guide"].model().rowsRemoved.connect(self._values_changed) - self.dither_acq_y_edit.value_changed.connect(self._values_changed) - self.dither_acq_z_edit.value_changed.connect(self._values_changed) - self.dither_guide_y_edit.value_changed.connect(self._values_changed) - self.dither_guide_z_edit.value_changed.connect(self._values_changed) - - layout = QtW.QHBoxLayout() - - info_group_box = QtW.QGroupBox() - info_group_box_layout = QtW.QGridLayout() - info_group_box_layout.addWidget(QtW.QLabel("OBSID"), 0, 0, 1, 1) - info_group_box_layout.addWidget(self.obsid_edit, 0, 1, 1, 2) - info_group_box_layout.addWidget(QtW.QLabel("date"), 1, 0, 1, 1) - info_group_box_layout.addWidget(self.date_edit, 1, 1, 1, 2) - info_group_box_layout.addWidget(QtW.QLabel("instrument"), 2, 0, 1, 1) - info_group_box_layout.addWidget(self.instrument_edit, 2, 1, 1, 2) - for i in range(3): - info_group_box_layout.setColumnStretch(i, 2) - - info_group_box.setLayout(info_group_box_layout) - - layout.addWidget(info_group_box) - - attitude_group_box = QtW.QWidget() - attitude_group_box_layout = QtW.QGridLayout() - attitude_group_box_layout.addWidget(QtW.QLabel("ra"), 0, 0, 1, 1) - attitude_group_box_layout.addWidget(QtW.QLabel("dec"), 1, 0, 1, 1) - attitude_group_box_layout.addWidget(QtW.QLabel("roll"), 2, 0, 1, 1) - attitude_group_box_layout.addWidget(self.ra_edit, 0, 1, 1, 1) - attitude_group_box_layout.addWidget(self.dec_edit, 1, 1, 1, 1) - attitude_group_box_layout.addWidget(self.roll_edit, 2, 1, 1, 1) - attitude_group_box.setLayout(attitude_group_box_layout) - for i in range(3): - attitude_group_box_layout.setColumnStretch(i, 10) - - info_2_group_box = QtW.QWidget() - info_2_group_box_layout = QtW.QGridLayout() - info_2_group_box_layout.addWidget(QtW.QLabel("n_guide"), 0, 0, 1, 1) - info_2_group_box_layout.addWidget(self.n_guide_edit, 0, 1, 1, 1) - info_2_group_box_layout.addWidget(QtW.QLabel("n_fid"), 0, 2, 1, 1) - info_2_group_box_layout.addWidget(self.n_fid_edit, 0, 3, 1, 1) - info_2_group_box_layout.addWidget(QtW.QLabel("t_ccd"), 1, 0, 1, 1) - info_2_group_box_layout.addWidget(self.n_t_ccd_edit, 1, 1, 1, 1) - info_2_group_box_layout.addWidget(QtW.QLabel("Man. angle"), 1, 2, 1, 1) - info_2_group_box_layout.addWidget(self.man_angle_edit, 1, 3, 1, 1) - info_2_group_box.setLayout(info_2_group_box_layout) - for i in range(4): - info_2_group_box_layout.setColumnStretch(i, 1) - - dither_group_box = QtW.QWidget() - dither_group_box_layout = QtW.QGridLayout() - dither_group_box_layout.addWidget(QtW.QLabel(""), 0, 0, 1, 4) - dither_group_box_layout.addWidget(QtW.QLabel("y"), 0, 4, 1, 4) - dither_group_box_layout.addWidget(QtW.QLabel("z"), 0, 8, 1, 4) - dither_group_box_layout.addWidget(QtW.QLabel("acq"), 1, 0, 1, 4) - dither_group_box_layout.addWidget(self.dither_acq_y_edit, 1, 4, 1, 4) - dither_group_box_layout.addWidget(self.dither_acq_z_edit, 1, 8, 1, 4) - - dither_group_box_layout.addWidget(QtW.QLabel("guide"), 2, 0, 1, 4) - dither_group_box_layout.addWidget(self.dither_guide_y_edit, 2, 4, 1, 4) - dither_group_box_layout.addWidget(self.dither_guide_z_edit, 2, 8, 1, 4) - dither_group_box.setLayout(dither_group_box_layout) - - tab_2 = QtW.QTabWidget() - tab_2.addTab(attitude_group_box, "Attitude") - tab_2.addTab(dither_group_box, "Dither") - tab_2.addTab(info_2_group_box, "Other") - tab_2.setCurrentIndex(0) - layout.addWidget(tab_2) - - tab = QtW.QTabWidget() - tab.addTab(self.include["acq"], "Include Acq.") - tab.addTab(self.exclude["acq"], "Exclude Acq.") - tab.addTab(self.include["guide"], "Include Guide") - tab.addTab(self.exclude["guide"], "Exclude Guide") - tab.setCurrentIndex(0) - layout.addWidget(tab) - - controls_group_box = QtW.QGroupBox() - controls_group_box_layout = QtW.QVBoxLayout() - controls_group_box_layout.addWidget(self.do) - controls_group_box_layout.addWidget(self.run_sparkles_button) - controls_group_box_layout.addWidget(self.reset_button) - controls_group_box.setLayout(controls_group_box_layout) - - layout.addWidget(controls_group_box) - - self.setLayout(layout) - - self.do.clicked.connect(self._do_it) - self.draw_test_button.clicked.connect(self._draw_test) - self.run_sparkles_button.clicked.connect(self.run_sparkles) - self.reset_button.clicked.connect(self.reset) - - self.set_parameters(**kwargs) - - def set_parameters(self, **kwargs): - if "file" in kwargs and ( - kwargs["file"].endswith(".pkl") or kwargs["file"].endswith(".pkl.gz") - ): - params = get_parameters_from_pickle( - kwargs["file"], obsid=kwargs.get("obsid", None) - ) - elif "file" in kwargs and kwargs["file"].endswith(".json"): - params = get_parameters_from_yoshi( - kwargs["file"], obsid=kwargs.get("obsid", None) - ) - else: - params = get_default_parameters() - # obsid is a command-line argument, so I set it here - if "obsid" in kwargs: - params["obsid"] = kwargs["obsid"] - - logger.debug(pformat(params)) - self.obsid_edit.setText(f"{params['obsid']}") - self.man_angle_edit.setText(f"{params['man_angle']}") - self.dither_acq_y_edit.setText(f"{params['dither_acq_y']}") - self.dither_acq_z_edit.setText(f"{params['dither_acq_z']}") - self.dither_guide_y_edit.setText(f"{params['dither_guide_y']}") - self.dither_guide_z_edit.setText(f"{params['dither_guide_z']}") - self.date_edit.setText(kwargs.get("date", params["date"])) - self.ra_edit.setText(f"{params['ra']:.5f}") - self.dec_edit.setText(f"{params['dec']:.5f}") - self.roll_edit.setText(f"{params['roll']:.5f}") - self.n_guide_edit.setText(f"{params['n_guide']}") - self.n_fid_edit.setText(f"{params['n_fid']}") - self.n_t_ccd_edit.setText(f"{params['t_ccd']:.2f}") - self.instrument_edit.setCurrentText(params["instrument"]) - - self.values = self._validate() - - def _draw_test(self): - self.values = self._validate() - if self.values: - self.draw_test.emit() - - def _values_changed(self): - # values are empty if validation fails, but the signal is still emitted to notify anyone - # that the values have changed - self.values = self._validate(quiet=True) - self.parameters_changed.emit() - - def _validate(self, quiet=False): - try: - n_fid = int(self.n_fid_edit.text()) - n_guide = int(self.n_guide_edit.text()) - obsid = int(self.obsid_edit.text()) - assert self.date_edit.text() != "", "No date" - assert self.ra_edit.text() != "", "No RA" - assert self.dec_edit.text() != "", "No dec" - assert n_fid + n_guide == 8, "n_fid + n_guide != 8" - ra = float(self.ra_edit.text()) * u.deg - dec = float(self.dec_edit.text()) * u.deg - time = CxoTime(self.date_edit.text()) - if self.roll_edit.text() == "": - roll = sun.nominal_roll(ra, dec, time) - else: - roll = float(self.roll_edit.text()) - return { - "date": self.date_edit.text(), - "ra": ra, - "dec": dec, - "roll": roll, - "n_guide": n_guide, - "n_fid": n_fid, - "t_ccd": float(self.n_t_ccd_edit.text()), - "instrument": self.instrument_edit.currentText(), - "obsid": obsid, - "exclude_ids_acq": [ - int(self.exclude["acq"].item(i).text()) - for i in range(self.exclude["acq"].count()) - ], - "include_ids_acq": [ - int(self.include["acq"].item(i).text()) - for i in range(self.include["acq"].count()) - ], - "exclude_ids_guide": [ - int(self.exclude["guide"].item(i).text()) - for i in range(self.exclude["guide"].count()) - ], - "include_ids_guide": [ - int(self.include["guide"].item(i).text()) - for i in range(self.include["guide"].count()) - ], - "dither_acq": ( - float(self.dither_acq_y_edit.text()), - float(self.dither_acq_z_edit.text()), - ), - "dither_guide": ( - float(self.dither_guide_y_edit.text()), - float(self.dither_guide_z_edit.text()), - ), - "man_angle": float(self.man_angle_edit.text()), - } - except Exception as e: - if not quiet: - logger.warning(e) - return {} - - def _do_it(self): - self.values = self._validate() - if self.values: - self.do_it.emit() - - def set_ra_dec(self, ra, dec, roll): - self.ra_edit.setText(f"{ra:.8f}", emit=True) - self.dec_edit.setText(f"{dec:.8f}", emit=True) - self.roll_edit.setText(f"{roll:.8f}", emit=True) - - def include_star(self, star, type, include): - if include is True: - self._include_star(star, type, True) - self._exclude_star(star, type, False) - elif include is False: - self._include_star(star, type, False) - self._exclude_star(star, type, True) - else: - self._include_star(star, type, include=False) - self._exclude_star(star, type, exclude=False) - - def _include_star(self, star, type, include): - items = self.include[type].findItems(f"{star}", QtC.Qt.MatchExactly) - if include: - if not items: - self.include[type].addItem(f"{star}") - else: - for it in items: - self.include[type].takeItem(self.include[type].row(it)) - - def _exclude_star(self, star, type, exclude): - items = self.exclude[type].findItems(f"{star}", QtC.Qt.MatchExactly) - if exclude: - if not items: - self.exclude[type].addItem(f"{star}") - else: - for it in items: - self.exclude[type].takeItem(self.exclude[type].row(it)) - - def proseco_args(self): - obsid = self.values["obsid"] - ra, dec = self.values["ra"], self.values["dec"] - roll = self.values["roll"] - time = CxoTime(self.values["date"]) - - aca_attitude = Quat(equatorial=(float(ra / u.deg), float(dec / u.deg), roll)) - - args = { - "obsid": obsid, - "att": aca_attitude, - "date": time, - "n_fid": self.values["n_fid"], - "n_guide": self.values["n_guide"], - "dither_acq": self.values["dither_acq"], - "dither_guide": self.values["dither_guide"], - "t_ccd_acq": self.values["t_ccd"], - "t_ccd_guide": self.values["t_ccd"], - "man_angle": self.values["man_angle"], - "detector": self.values["instrument"], - "sim_offset": 0, # docs say this is optional, but it does not seem to be - "focus_offset": 0, # docs say this is optional, but it does not seem to be - "dyn_bgd_n_faint": 2, - } - - for key in [ - "exclude_ids_guide", - "include_ids_guide", - "exclude_ids_acq", - "include_ids_acq", - ]: - if self.values[key]: - args[key] = self.values[key] - - return args diff --git a/aperoll/widgets/proseco_view.py b/aperoll/widgets/proseco_view.py index f29eb7a..30497ca 100644 --- a/aperoll/widgets/proseco_view.py +++ b/aperoll/widgets/proseco_view.py @@ -7,18 +7,18 @@ from Quaternion import Quat from aperoll.proseco_data import ProsecoData -from aperoll.utils import logger +from aperoll.utils import ( + get_default_parameters, + get_parameters_from_pickle, + get_parameters_from_yoshi, + logger, +) from aperoll.widgets.attitude_widget import ( AttitudeWidget, QuatRepresentation, ) from aperoll.widgets.error_message import ErrorMessage -from aperoll.widgets.parameters import ( - LineEdit, - get_default_parameters, - get_parameters_from_pickle, - get_parameters_from_yoshi, -) +from aperoll.widgets.line_edit import LineEdit from aperoll.widgets.proseco_params import ProsecoParams from aperoll.widgets.star_plot import StarPlot from aperoll.widgets.starcat_review import StarcatReview diff --git a/aperoll/widgets/star_plot.py b/aperoll/widgets/star_plot.py index d8e40b9..2012bea 100644 --- a/aperoll/widgets/star_plot.py +++ b/aperoll/widgets/star_plot.py @@ -788,7 +788,7 @@ def set_centroids(self, centroids): def main(): - from aperoll.widgets.parameters import get_default_parameters + from aperoll.utils import get_default_parameters params = get_default_parameters()