diff --git a/src/nro45data/psw/ms2/filler/__init__.py b/src/nro45data/psw/ms2/filler/__init__.py index 74d9905..b790f9d 100644 --- a/src/nro45data/psw/ms2/filler/__init__.py +++ b/src/nro45data/psw/ms2/filler/__init__.py @@ -8,7 +8,7 @@ from .antenna import fill_antenna from .data_description import fill_data_description from .feed import fill_feed -from .field import _fill_field_columns, _get_field_columns +from .field import fill_field from .observation import _fill_observation_columns, _get_observation_columns from .polarization import _fill_polarization_columns, _get_polarization_columns from .main import fill_main @@ -26,11 +26,6 @@ LOG = logging.getLogger(__name__) -def fill_field(msfile: str, hdu: BinTableHDU): - columns = _get_field_columns(hdu) - _fill_field_columns(msfile, columns) - - def fill_observation(msfile: str, hdu: BinTableHDU): columns = _get_observation_columns(hdu) _fill_observation_columns(msfile, columns) diff --git a/src/nro45data/psw/ms2/filler/field.py b/src/nro45data/psw/ms2/filler/field.py index 7476cda..13fffec 100644 --- a/src/nro45data/psw/ms2/filler/field.py +++ b/src/nro45data/psw/ms2/filler/field.py @@ -1,11 +1,13 @@ +from __future__ import annotations + import logging -from typing import TYPE_CHECKING +from typing import Generator, TYPE_CHECKING import numpy as np -from .._casa import open_table +from .._casa import datestr2mjd from .._casa import convert_str_angle_to_rad -from .utils import fix_nrow_to +from .utils import fill_ms_table if TYPE_CHECKING: from astropy.io.fits.hdu.BinTableHDU import BinTableHDU @@ -13,7 +15,18 @@ LOG = logging.getLogger(__name__) -def _get_field_columns(hdu: "BinTableHDU") -> dict: +def _get_field_row(hdu: BinTableHDU) -> Generator[dict, None, dict]: + """Provide field row information. + + Args: + hdu: NRO45m psw data in the form of BinTableHDU object. + + Yields: + Dictionary containing field row information. + + Returns: + Dictionary containing data-dependent column keywords. + """ # NAME field_name = hdu.header["OBJECT"].strip() LOG.debug("field_name: %s", field_name) @@ -25,8 +38,11 @@ def _get_field_columns(hdu: "BinTableHDU") -> dict: # use start time of the observation history_cards = hdu.header["HISTORY"] start_time_card = [x for x in history_cards if x.startswith("NEWSTAR START-TIME")] - field_time = float(start_time_card[0].split("=")[-1].strip(" '")) - LOG.debug("field_time: %s", field_time) + sstr = start_time_card[0].split("=")[-1].strip(" '") + LOG.debug("sstr: %s", sstr) + datestr = sstr[0:4] + "/" + sstr[4:6] + "/" + sstr[6:8] + " " + sstr[8:10] + ":" + sstr[10:12] + ":" + sstr[12:14] + LOG.debug("formatted sstr: %s", datestr) + field_time = datestr2mjd(datestr) - 9 * 3600 # NUM_POLY num_poly = 0 @@ -62,12 +78,11 @@ def _get_field_columns(hdu: "BinTableHDU") -> dict: # FLAG_ROW flag_row = False - columns = { + row = { "NAME": field_name, "CODE": field_code, "TIME": field_time, "NUM_POLY": num_poly, - "FIELD_EPOCH": field_epoch, "DELAY_DIR": delay_dir, "PHASE_DIR": phase_dir, "REFERENCE_DIR": reference_dir, @@ -75,24 +90,22 @@ def _get_field_columns(hdu: "BinTableHDU") -> dict: "FLAG_ROW": flag_row, } - return columns - - -def _fill_field_columns(msfile: str, columns: dict): - with open_table(msfile + "/FIELD", read_only=False) as tb: - fix_nrow_to(1, tb) - - tb.putcell("NAME", 0, columns["NAME"]) - tb.putcell("CODE", 0, columns["CODE"]) - tb.putcell("TIME", 0, columns["TIME"]) - tb.putcell("NUM_POLY", 0, columns["NUM_POLY"]) - tb.putcell("DELAY_DIR", 0, columns["DELAY_DIR"]) - colkeywords = tb.getcolkeywords("DELAY_DIR") - colkeywords["MEASINFO"]["Ref"] = columns["FIELD_EPOCH"] - tb.putcolkeywords("DELAY_DIR", colkeywords) - tb.putcell("PHASE_DIR", 0, columns["PHASE_DIR"]) - tb.putcolkeywords("PHASE_DIR", colkeywords) - tb.putcell("REFERENCE_DIR", 0, columns["REFERENCE_DIR"]) - tb.putcolkeywords("REFERENCE_DIR", colkeywords) - tb.putcell("SOURCE_ID", 0, columns["SOURCE_ID"]) - tb.putcell("FLAG_ROW", 0, columns["FLAG_ROW"]) + yield row + + column_keywords = { + "DELAY_DIR": {"MEASINFO": {"Ref": field_epoch}}, + "PHASE_DIR": {"MEASINFO": {"Ref": field_epoch}}, + "REFERENCE_DIR": {"MEASINFO": {"Ref": field_epoch}} + } + + return column_keywords # noqa + + +def fill_field(msfile: str, hdu: BinTableHDU): + """Fill MS FIELD table. + + Args: + msfile: Name of MS file. + hdu: NRO45m psw data in the form of BinTableHDU object. + """ + fill_ms_table(msfile, hdu, "FIELD", _get_field_row) diff --git a/src/nro45data/psw/ms2/filler/utils.py b/src/nro45data/psw/ms2/filler/utils.py index 7660f82..eed5a7a 100644 --- a/src/nro45data/psw/ms2/filler/utils.py +++ b/src/nro45data/psw/ms2/filler/utils.py @@ -2,7 +2,7 @@ import logging import pprint -from typing import Any, Callable, Optional, TYPE_CHECKING +from typing import Callable, TYPE_CHECKING import numpy as np @@ -281,8 +281,7 @@ def fill_ms_table( msfile: str, hdu: BinTableHDU, table_name: str, - row_generator: Callable, - column_keywords: Optional[dict[str, dict[str, Any]]] = None + row_generator: Callable ): """Fill MS table. @@ -291,10 +290,6 @@ def fill_ms_table( hdu: NRO45m psw data in the form of BinTableHDU object. table_name: Name of subtable or "MAIN" for MAIN table. row_generator: Generator to yield table row. - column_keywords: Optional information to update column - keywords. Defaults to None. - Key is column name and value is a dictionary of - column keywords to be updated. """ if table_name.upper() == "MAIN": table_path = msfile @@ -302,25 +297,35 @@ def fill_ms_table( table_path = f"{msfile}/{table_name.upper()}" with open_table(table_path, read_only=False) as tb: - # update table column keywords - if column_keywords is None: - column_keywords = {} - - for colname, colkeywords_new in column_keywords.items(): - colkeywords = tb.getcolkeywords(colname) - for key, value_new in colkeywords_new.items(): - if key in colkeywords and isinstance(value_new, dict): - colkeywords[key].update(value_new) - else: - colkeywords[key] = value_new - tb.putcolkeywords(colname, colkeywords) - - # fill table rows - for row_id, row in enumerate(row_generator(hdu)): - if tb.nrows() <= row_id: - tb.addrows(tb.nrows() - row_id + 1) - - for key, value in row.items(): - LOG.debug("row %d key %s", row_id, key) - tb.putcell(key, row_id, value) - LOG.debug("%s table %d row %s", table_name, row_id, row) + # iterator should provide row dictionary in order, + # and then, if necessary, column keywords dictionary + # should be returned additionally + iterator = enumerate(row_generator(hdu)) + try: + # fill table rows + while True: + row_id, row = next(iterator) + if tb.nrows() <= row_id: + tb.addrows(tb.nrows() - row_id + 1) + + for key, value in row.items(): + LOG.debug("row %d key %s", row_id, key) + tb.putcell(key, row_id, value) + LOG.debug("%s table %d row %s", table_name, row_id, row) + + except StopIteration as s: + # update column keywords if necessary + column_keywords = s.value + LOG.debug("return value: %s", s.value) + if isinstance(column_keywords, dict): + LOG.debug("column_keywords: %s", column_keywords) + for colname, colkeywords_new in column_keywords.items(): + colkeywords = tb.getcolkeywords(colname) + for key, value_new in colkeywords_new.items(): + if key in colkeywords and isinstance(value_new, dict): + LOG.debug("updating column keyword %s: %s, %s", colname, key, value_new) + colkeywords[key].update(value_new) + else: + LOG.debug("adding column keyword %s: %s, %s", colname, key, value_new) + colkeywords[key] = value_new + tb.putcolkeywords(colname, colkeywords) diff --git a/tests/ms2_filler/test_forest.py b/tests/ms2_filler/test_forest.py index f3057c3..f00c814 100644 --- a/tests/ms2_filler/test_forest.py +++ b/tests/ms2_filler/test_forest.py @@ -104,6 +104,31 @@ def test_forest_ms2_structure(msfile): assert receptor_angle.shape == (2,) assert np.all(receptor_angle == 0.0) + with open_table(os.path.join(msfile, "FIELD")) as tb: + assert tb.nrows() == 1 + assert tb.getcell("NAME", 0) == "NML-Tau" + assert tb.getcell("CODE", 0) == "" + # start time: 2024/09/30 17:49:19 + field_time = mjd2datetime(tb.getcell("TIME", 0)) + time_expected = datetime.datetime(2024, 9, 30, 17, 49, 19, tzinfo=datetime.timezone.utc) + assert abs((field_time - time_expected).total_seconds()) < 1e-3 + assert tb.getcell("NUM_POLY", 0) == 0 + # RA = 03:53:28.860 + # DEC = +11:24:22.40 + # Epoch = J2000 + ra_expected = 1.0187530477122202 # rad + dec_expected = 2.9861419948788317 # rad + for col in ["DELAY_DIR", "PHASE_DIR", "REFERENCE_DIR"]: + meas_info = tb.getcolkeyword(col, "MEASINFO") + assert "Ref" in meas_info + assert meas_info["Ref"] == "J2000" + direction = tb.getcell(col, 0) + assert direction.shape == (1, 2) + assert abs(direction[0, 0] - ra_expected) / ra_expected < 1e-6 + assert abs(direction[0, 1] - dec_expected) / dec_expected < 1e-6 + assert tb.getcell("SOURCE_ID", 0) == 0 + assert tb.getcell("FLAG_ROW", 0) is False + with open_table(os.path.join(msfile, "STATE")) as tb: intents_map = dict((i, v) for i, v in enumerate(tb.getcol("OBS_MODE"))) diff --git a/tests/ms2_filler/test_h40.py b/tests/ms2_filler/test_h40.py index 97ab56b..3437bf6 100644 --- a/tests/ms2_filler/test_h40.py +++ b/tests/ms2_filler/test_h40.py @@ -106,6 +106,31 @@ def test_h40_ms2_structure(msfile): assert receptor_angle.shape == (2,) assert np.all(receptor_angle == 0.0) + with open_table(os.path.join(msfile, "FIELD")) as tb: + assert tb.nrows() == 1 + assert tb.getcell("NAME", 0) == "NML-Tau" + assert tb.getcell("CODE", 0) == "" + # start time: 2024/09/25 15:58:54 + field_time = mjd2datetime(tb.getcell("TIME", 0)) + time_expected = datetime.datetime(2024, 9, 25, 15, 58, 54, tzinfo=datetime.timezone.utc) + assert abs((field_time - time_expected).total_seconds()) < 1e-3 + assert tb.getcell("NUM_POLY", 0) == 0 + # RA = 03:53:28.860 + # DEC = +11:24:22.40 + # Epoch = J2000 + ra_expected = 1.0187530477122202 # rad + dec_expected = 2.9861419948788317 # rad + for col in ["DELAY_DIR", "PHASE_DIR", "REFERENCE_DIR"]: + meas_info = tb.getcolkeyword(col, "MEASINFO") + assert "Ref" in meas_info + assert meas_info["Ref"] == "J2000" + direction = tb.getcell(col, 0) + assert direction.shape == (1, 2) + assert abs(direction[0, 0] - ra_expected) / ra_expected < 1e-6 + assert abs(direction[0, 1] - dec_expected) / dec_expected < 1e-6 + assert tb.getcell("SOURCE_ID", 0) == 0 + assert tb.getcell("FLAG_ROW", 0) is False + with open_table(os.path.join(msfile, "STATE")) as tb: intents_map = dict((i, v) for i, v in enumerate(tb.getcol("OBS_MODE"))) diff --git a/tests/ms2_filler/test_z45.py b/tests/ms2_filler/test_z45.py index b800f6c..7c2fe47 100644 --- a/tests/ms2_filler/test_z45.py +++ b/tests/ms2_filler/test_z45.py @@ -105,6 +105,31 @@ def test_z45_ms2_structure(msfile): assert receptor_angle.shape == (2,) assert np.all(receptor_angle == 0.0) + with open_table(os.path.join(msfile, "FIELD")) as tb: + assert tb.nrows() == 1 + assert tb.getcell("NAME", 0) == "NML-Tau" + assert tb.getcell("CODE", 0) == "" + # start time: 2024/09/30 18:00:32 + field_time = mjd2datetime(tb.getcell("TIME", 0)) + time_expected = datetime.datetime(2024, 9, 30, 18, 0, 32, tzinfo=datetime.timezone.utc) + assert abs((field_time - time_expected).total_seconds()) < 1e-3 + assert tb.getcell("NUM_POLY", 0) == 0 + # RA = 03:53:28.860 + # DEC = +11:24:22.40 + # Epoch = J2000 + ra_expected = 1.0187530477122202 # rad + dec_expected = 2.9861419948788317 # rad + for col in ["DELAY_DIR", "PHASE_DIR", "REFERENCE_DIR"]: + meas_info = tb.getcolkeyword(col, "MEASINFO") + assert "Ref" in meas_info + assert meas_info["Ref"] == "J2000" + direction = tb.getcell(col, 0) + assert direction.shape == (1, 2) + assert abs(direction[0, 0] - ra_expected) / ra_expected < 1e-6 + assert abs(direction[0, 1] - dec_expected) / dec_expected < 1e-6 + assert tb.getcell("SOURCE_ID", 0) == 0 + assert tb.getcell("FLAG_ROW", 0) is False + with open_table(os.path.join(msfile, "STATE")) as tb: intents_map = dict((i, v) for i, v in enumerate(tb.getcol("OBS_MODE")))