Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CxoTime.linspace on get_aca_images for > 3hour range #181

Merged
merged 9 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 55 additions & 15 deletions chandra_aca/maude_decom.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,16 @@
from struct import Struct
from struct import unpack as _unpack

import astropy.units as u
import maude
import numpy as np
from astropy.table import Table, vstack
from Chandra.Time import DateTime
from cxotime import CxoTime, CxoTimeLike

# Maude fetch limits
MAUDE_SINGLE_FETCH_LIMIT = 3.0 * u.hour
MAUDE_FETCH_LIMIT = 5 * u.day

# The following are the tables in the docstring above. They appear to be transposed,
# but the resultt agrees with level0.
Expand Down Expand Up @@ -687,8 +693,9 @@ def get_raw_aca_packets(start, stop, maude_result=None, **maude_kwargs):
{'flags': int, 'packets': [],
'TIME': np.array([]), 'MNF': np.array([]), 'MJF': np.array([])}
"""
date_start, date_stop = DateTime(start), DateTime(
stop
date_start, date_stop = (
DateTime(start),
DateTime(stop),
) # ensure input is proper date
stop_pad = 1.5 / 86400 # padding at the end in case of trailing partial ACA packets

Expand Down Expand Up @@ -1061,13 +1068,15 @@ def get_aca_packets(
combine = True
calibrate = True

date_start, date_stop = DateTime(start), DateTime(
stop
date_start, date_stop = (
DateTime(start),
DateTime(stop),
) # ensure input is proper date
if 24 * (date_stop - date_start) > 3:
if (CxoTime(stop) - CxoTime(start)) > MAUDE_SINGLE_FETCH_LIMIT:
raise ValueError(
f"Requested {24 * (date_stop - date_start)} hours of telemetry. "
"Maximum allowed is 3 hours at a time"
f"Requested {CxoTime(stop) - CxoTime(start)} of telemetry. "
f"Maximum allowed is {MAUDE_SINGLE_FETCH_LIMIT} at a time "
"(see MAUDE_SINGLE_FETCH_LIMIT)."
)

stop_pad = 0
Expand Down Expand Up @@ -1209,24 +1218,54 @@ def _get_aca_packets(
return table


def get_aca_images(start, stop, **maude_kwargs):
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()`.


Parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add documentation about the fetch intervals.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't actually know that that meant so I just fluffed out the docstring a bit.

----------
start
timestamp interpreted as a Chandra.Time.DateTime
timestamp, CxoTimeLike
stop
timestamp interpreted as a Chandra.Time.DateTime
maude_kwargs
keyword args passed to maude
timestamp, CxoTimeLike. stop - start cannot be greater than MAUDE_FETCH_LIMIT
kwargs
keyword args passed to get_aca_packets

Returns
-------
astropy.table.Table
"""
return get_aca_packets(start, stop, level0=True, **maude_kwargs)
start = CxoTime(start)
stop = CxoTime(stop)
if (stop - start) > MAUDE_FETCH_LIMIT:
raise ValueError(
f"stop - start cannot be greater than {MAUDE_FETCH_LIMIT}. "
"Set module variable MAUDE_FETCH_LIMIT if needed."
)
maude_fetch_times = CxoTime.linspace(
start, stop, step_max=MAUDE_SINGLE_FETCH_LIMIT - 1 * u.s
)
packet_stack = [
get_aca_packets(
start=istart,
stop=istop,
level0=True,
**kwargs,
)
for istart, istop in zip(
maude_fetch_times[:-1], maude_fetch_times[1:], strict=False
)
]
out = vstack(packet_stack)
out.meta["times"] = maude_fetch_times
return out


######################
Expand Down Expand Up @@ -1263,8 +1302,9 @@ def get_raw_aca_blobs(start, stop, maude_result=None, **maude_kwargs):
dict
{'blobs': [], 'names': np.array([]), 'types': np.array([])}
"""
date_start, date_stop = DateTime(start), DateTime(
stop
date_start, date_stop = (
DateTime(start),
DateTime(stop),
) # ensure input is proper date
stop_pad = 1.5 / 86400 # padding at the end in case of trailing partial ACA packets

Expand Down
54 changes: 54 additions & 0 deletions chandra_aca/tests/test_maude_decom.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import os
import pickle

import astropy.units as u
import maude
import numpy as np
import pytest
from cxotime import CxoTime

from chandra_aca import maude_decom

Expand Down Expand Up @@ -272,6 +274,58 @@ def test_partial_images():
assert np.all(table[i]["IMG"].mask == mask[table[i]["IMGTYPE"]])


def test_aca_images_chunks_1():
"""Test that a fetch longer than the maude step limit works"""
single_fetch_limit = maude_decom.MAUDE_SINGLE_FETCH_LIMIT
maude_decom.MAUDE_SINGLE_FETCH_LIMIT = 1 * u.min
start = "2023:001:00:00:01.000"
stop = "2023:001:00:05:01.000"
tstart = CxoTime(start).secs
tstop = CxoTime(stop).secs
try:
imgs = maude_decom.get_aca_images(start, stop)
finally:
maude_decom.MAUDE_SINGLE_FETCH_LIMIT = single_fetch_limit

imgs.sort(["TIME", "IMGNUM"])

for slot in range(8):
# Confirm that the data is basically contiguous
ok_slot = imgs["IMGNUM"] == slot
# Confirm no duplicates by VCDUCTR
assert len(np.unique(imgs[ok_slot]["VCDUCTR"])) == len(imgs[ok_slot])
# Confirm for these 8x8 data that there's no gap > 5 seconds
assert np.max(np.diff(imgs[ok_slot]["TIME"])) < 5

# Confirm that the returned data times are within the start stop
assert imgs["TIME"][0] >= tstart
assert imgs["TIME"][-1] <= tstop

# Confirm that the beginning and end match the expected values
assert abs(imgs[0]["TIME"] - tstart) < 2
assert abs(imgs[-1]["TIME"] - tstop) < 2

imgs_start = maude_decom.get_aca_images(start, CxoTime(start) + 60 * u.s)
imgs_stop = maude_decom.get_aca_images(CxoTime(stop) - 60 * u.s, stop)
imgs_start.sort(["TIME", "IMGNUM"])
imgs_stop.sort(["TIME", "IMGNUM"])
assert np.all(imgs[0] == imgs_start[0])
assert np.all(imgs[-1] == imgs_stop[-1])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified the PR and removed the code I had to fetch extra time and re-filter the images by TIME. And now this isn't passing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this suggest an upstream sort fix in get_aca_images?

Copy link
Member

@taldcroft taldcroft Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upstream fix would be to sort by time and slot. That's a question of API / expectations. In the current release the output is (empirically) sorted by [IMGNUM, TIME], so all the slot=0 are first and slot=7 are last. For chunked data this pattern is repeated for each chunk.

Overall I think we don't care as long as the images are sorted by TIME for a given IMGNUM. That is the only ordering that is currently guaranteed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - though we added sorting on the mica side to be out.sort(keys=["TIME", "IMGNUM"]), so maybe maude_decom.get_aca_images should just do the same for consistency.


# Check the linspace times in the returned data
used_step_max = 1 * u.min - 1 * u.s
deltas_lte = [t <= used_step_max for t in np.diff(imgs.meta["times"])]
assert np.all(deltas_lte)


def test_aca_images_chunks_2():
"""Test that a fetch longer than MAUDE_FETCH_LIMIT throws a ValueError"""
start = CxoTime("2023:001:00:00:01.000")
stop = start + (maude_decom.MAUDE_FETCH_LIMIT * 1.1)
with pytest.raises(ValueError, match="stop - start cannot be greater than"):
maude_decom.get_aca_images(start, stop)


def test_vcdu_vs_level0():
from astropy.table import Table

Expand Down
Loading