diff --git a/chandra_aca/maude_decom.py b/chandra_aca/maude_decom.py index d4fd2c2a..1c102f78 100644 --- a/chandra_aca/maude_decom.py +++ b/chandra_aca/maude_decom.py @@ -147,6 +147,7 @@ """ import copy +import itertools from struct import Struct from struct import unpack as _unpack @@ -945,7 +946,7 @@ def _aca_packets_to_table(aca_packets, dtype=None): if name in aca_packet: array[name][i] = aca_packet[name] - table = Table(array, masked=True) + table = Table(array) if img: table["IMG"] = img for i, aca_packet in enumerate(aca_packets): @@ -1315,11 +1316,79 @@ def get_aca_images(start: CxoTimeLike, stop: CxoTimeLike, **kwargs): """ Fetch ACA image telemetry - Fetch ACA image telemetry from MAUDE and return it as an astropy Table. With the default - settings and no additional kwargs, this calls `get_aca_packets()` in a configuration that - uses MAUDE frames, combines image data, and sets the TIME associated with each image to the - midpoint of the integration time during which that pixel data was collected (matches CXC L0 - times). See `get_aca_packets()`. + Fetch ACA image telemetry from MAUDE and return it as an astropy Table. With the + default settings and no additional kwargs, this calls `get_aca_packets()` in a + configuration that uses MAUDE frames, combines image data, and sets the TIME + associated with each image to the midpoint of the integration time during which that + pixel data was collected (matches CXC L0 times). See `get_aca_packets()`. + + The 'IMG' column is always Nx8x8 and masked, where the mask is a per-pixel mask that + indicates missing data for 4x4 or 6x6 images. The units of 'IMG' are DN. + + For queries including 4x4 data, the 'BGDRMS', 'TEMPCCD', 'TEMPHOUS', 'TEMPPRIM', + 'TEMPSEC', and 'BGDSTAT' columns will be masked since they are not present in the + 4x4 image data. + + There are three different specifiers of the image row/col location: + - IMGROW0_8x8/IMGCOL0_8x8: the row/col of the lower-left pixel of the 8x8 masked + image. This is generally the most useful. + - IMGROW0/IMGCOL0: the row/col of the lower-left pixel of the actual 4x4, 6x6, or + 8x8 image data. For 6x6 this corresponds to the mouse-bitten corner pixel. + - IMGROW_A1/IMGCOL_A1: the row/col of the A1 pixel in telemetry (see ACA EQ-spec). + + The full list of columns is:: + + name dtype unit + --------------------- ------- ----------- + TIME float64 CXC seconds + VCDUCTR uint32 + MJF uint32 + MNF uint32 + IMGNUM uint32 + COMMCNT uint8 + COMMPROG uint8 + GLBSTAT uint8 + IMGFUNC uint32 + IMGTYPE uint8 + IMGSCALE uint16 + IMGROW0 int16 + IMGCOL0 int16 + INTEG float64 s + BGDAVG uint16 DN + BGDRMS uint16 DN + TEMPCCD float32 degC + TEMPHOUS float32 degC + TEMPPRIM float32 degC + TEMPSEC float32 degC + BGDSTAT uint8 + HIGH_BGD bool + RAM_FAIL bool + ROM_FAIL bool + POWER_FAIL bool + CAL_FAIL bool + COMM_CHECKSUM_FAIL bool + RESET bool + SYNTAX_ERROR bool + COMMCNT_SYNTAX_ERROR bool + COMMCNT_CHECKSUM_FAIL bool + COMMPROG_REPEAT uint8 + IMGFID bool + IMGSTAT uint8 + SAT_PIXEL bool + DEF_PIXEL bool + QUAD_BOUND bool + COMMON_COL bool + MULTI_STAR bool + ION_RAD bool + IMGROW_A1 int16 + IMGCOL_A1 int16 + IMGROW0_8X8 int16 + IMGCOL0_8X8 int16 + END_INTEG_TIME float64 + AAPIXTLM str4 + AABGDTYP str4 + IMG float64 DN + IMG_VCDUCTR int64 Parameters @@ -1335,6 +1404,9 @@ def get_aca_images(start: CxoTimeLike, stop: CxoTimeLike, **kwargs): ------- astropy.table.Table """ + # This is strictly for testing, hence undocumented. + set_times_metadata = kwargs.pop("set_times_metadata", False) + start = CxoTime(start) stop = CxoTime(stop) if (stop - start) > MAUDE_FETCH_LIMIT: @@ -1352,12 +1424,19 @@ def get_aca_images(start: CxoTimeLike, stop: CxoTimeLike, **kwargs): level0=True, **kwargs, ) - for istart, istop in zip( - maude_fetch_times[:-1], maude_fetch_times[1:], strict=False - ) + for istart, istop in itertools.pairwise(maude_fetch_times) ] out = vstack(packet_stack) - out.meta["times"] = maude_fetch_times + if set_times_metadata: + out.meta["times"] = maude_fetch_times + + # Remove mask from columns where no values are masked. IMG is excepted because + # the mask reflects the presence of 4x4 or 6x6 images, not entirely missing data + # per row. + for col in out.itercols(): + if not np.any(col.mask) and col.name != "IMG": + out[col.name] = col.data.data + return out diff --git a/chandra_aca/tests/test_maude_decom.py b/chandra_aca/tests/test_maude_decom.py index acfbed06..5bfc1e1c 100755 --- a/chandra_aca/tests/test_maude_decom.py +++ b/chandra_aca/tests/test_maude_decom.py @@ -283,7 +283,8 @@ def test_aca_images_chunks_1(): tstart = CxoTime(start).secs tstop = CxoTime(stop).secs try: - imgs = maude_decom.get_aca_images(start, stop) + # Include the "times" metadata in the returned images table for testing + imgs = maude_decom.get_aca_images(start, stop, set_times_metadata=True) finally: maude_decom.MAUDE_SINGLE_FETCH_LIMIT = single_fetch_limit @@ -1287,3 +1288,47 @@ def test_end_integ_time(combine): rtol=0, ) ) + + +def test_get_aca_image_masked_columns_1(): + """Get images over a time covering end of maneuver and start of dwell + + This has fids (8x8), guides (6x6) and null images (4x4). + """ + start = "2018:024:23:05:00" + stop = "2018:024:23:10:00" + imgs = maude_decom.get_aca_images(start, stop) + # 4x4, 6x6, and 8x8 images + assert set(imgs["IMGTYPE"]) == {0, 1, 4} + # Null, tracking, and search + assert set(imgs["IMGFUNC"]) == {0, 1, 3} + cols_masked = {col.info.name for col in imgs.itercols() if hasattr(col, "mask")} + assert cols_masked == { + "BGDRMS", + "TEMPCCD", + "TEMPHOUS", + "TEMPPRIM", + "TEMPSEC", + "BGDSTAT", + "IMG", + } + + +def test_get_aca_image_masked_columns_2(): + """Only 6x6 and 8x8 so only IMG is masked""" + start = "2018:024:23:15:00" + stop = "2018:024:23:20:00" + imgs = maude_decom.get_aca_images(start, stop) + assert set(imgs["IMGTYPE"]) == {1, 4} + cols_masked = {col.info.name for col in imgs.itercols() if hasattr(col, "mask")} + assert cols_masked == {"IMG"} + + +def test_get_aca_image_masked_columns_3(): + """Only 8x8 so only IMG masked""" + start = "2024:001:00:00:00" + stop = "2024:001:00:01:00" + imgs = maude_decom.get_aca_images(start, stop) + assert set(imgs["IMGTYPE"]) == {4} + cols_masked = {col.info.name for col in imgs.itercols() if hasattr(col, "mask")} + assert cols_masked == {"IMG"}