Skip to content

Commit

Permalink
Merge pull request #745 from StingraySoftware/allow_empty_lightcurves
Browse files Browse the repository at this point in the history
Allow empty lightcurves
  • Loading branch information
matteobachetti committed Aug 24, 2023
2 parents 38ca23b + 5f7f551 commit 5bdef91
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 47 deletions.
1 change: 1 addition & 0 deletions docs/changes/745.feature.rst
@@ -0,0 +1 @@
Allow the creation of empty light curves.
2 changes: 1 addition & 1 deletion docs/notebooks
90 changes: 58 additions & 32 deletions stingray/lightcurve.py
Expand Up @@ -211,8 +211,8 @@ class Lightcurve(StingrayTimeseries):

def __init__(
self,
time,
counts,
time=None,
counts=None,
err=None,
input_counts=True,
gti=None,
Expand All @@ -234,7 +234,39 @@ def __init__(
if other_kw != {}:
warnings.warn(f"Unrecognized keywords: {list(other_kw.keys())}")

self._time = None
self._mask = None
self._counts = None
self._counts_err = None
self._countrate = None
self._countrate_err = None
self._meanrate = None
self._meancounts = None
self._bin_lo = None
self._bin_hi = None
self._n = None
self.mission = mission
self.instr = instr
self.header = header
self.dt = dt

self.input_counts = input_counts
self.low_memory = low_memory

self.mjdref = mjdref

if time is None or len(time) == 0:
warnings.warn("No time values passed to Lightcurve object!")
return

if counts is None or np.size(time) != np.size(counts):
raise StingrayError(
"Empty or invalid counts array. Time and counts array should have the same length."
"If you are providing event data, please use Lightcurve.make_lightcurve()"
)

time, mjdref = interpret_times(time, mjdref=mjdref)
self.mjdref = mjdref

time = np.asarray(time)
counts = np.asarray(counts)
Expand All @@ -245,12 +277,6 @@ def __init__(
if not skip_checks:
time, counts, err = self.initial_optional_checks(time, counts, err, gti=gti)

if time.size != counts.size:
raise StingrayError("time and counts array are not " "of the same length!")

if time.size <= 1:
raise StingrayError("A single or no data points can not create " "a lightcurve!")

if err_dist.lower() not in valid_statistics:
# err_dist set can be increased with other statistics
raise StingrayError(
Expand All @@ -265,16 +291,21 @@ def __init__(
"Sorry for the inconvenience."
)

self.mjdref = mjdref
self._time = time

if dt is None:
if dt is None and time.size > 1:
logging.info(
"Computing the bin time ``dt``. This can take "
"time. If you know the bin time, please specify it"
" at light curve creation"
)
dt = np.median(np.diff(self._time))
elif dt is None and time.size == 1:
warnings.warn(
"Only one time bin and no dt specified. Setting dt=1. "
"Please specify dt if you want to use a different value"
)
dt = 1.0

self.dt = dt

Expand All @@ -296,22 +327,6 @@ def __init__(
if gti is not None:
self._gti = np.asarray(gti)

self._mask = None
self._counts = None
self._counts_err = None
self._countrate = None
self._countrate_err = None
self._meanrate = None
self._meancounts = None
self._bin_lo = None
self._bin_hi = None
self._n = None
self.mission = mission
self.instr = instr
self.header = header

self.input_counts = input_counts
self.low_memory = low_memory
if input_counts:
self._counts = np.asarray(counts)
self._counts_err = err
Expand Down Expand Up @@ -1862,9 +1877,6 @@ def split_by_gti(self, gti=None, min_points=2):
start = start_bins[i]
stop = stop_bins[i]

if np.isclose(stop - start, 1):
logging.warning("Segment with a single time bin! Ignoring this segment!")
continue
if (stop - start) < min_points:
continue

Expand Down Expand Up @@ -1912,6 +1924,10 @@ def apply_mask(self, mask, inplace=False):
array_attrs = self.array_attrs()

self._mask = self._n = None
if isinstance(self.dt, Iterable):
new_dt = self.dt[mask]
else:
new_dt = self.dt
if inplace:
new_ev = self
# If they don't exist, they get set
Expand All @@ -1923,10 +1939,19 @@ def apply_mask(self, mask, inplace=False):
self._counts = self._counts[mask]
if self._counts_err is not None:
self._counts_err = self._counts_err[mask]
new_ev.dt = new_dt
else:
new_ev = Lightcurve(
time=self.time[mask], counts=self.counts[mask], skip_checks=True, gti=self.gti
)
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message="Some functionalities of Stingray Lightcurve.*"
)
new_ev = Lightcurve(
time=self.time[mask],
counts=self.counts[mask],
skip_checks=True,
gti=self.gti,
dt=new_dt,
)
if self._counts_err is not None:
new_ev.counts_err = self.counts_err[mask]
for attr in self.meta_attrs():
Expand All @@ -1939,6 +1964,7 @@ def apply_mask(self, mask, inplace=False):
"time",
"counts",
"counts_err",
"dt",
"_time",
"_counts",
"_counts_err",
Expand Down
52 changes: 38 additions & 14 deletions stingray/tests/test_lightcurve.py
Expand Up @@ -81,6 +81,29 @@ def setup_class(cls):
cls.lc = Lightcurve(times, counts, gti=cls.gti)
cls.lc_lowmem = Lightcurve(times, counts, gti=cls.gti, low_memory=True)

def test_empty_lightcurve(self):
with pytest.warns(UserWarning, match="No time values passed to Lightcurve object!"):
Lightcurve()

with pytest.warns(UserWarning, match="No time values passed to Lightcurve object!"):
Lightcurve([], [])

def test_bad_counts_lightcurve(self):
with pytest.raises(StingrayError, match="Empty or invalid counts array. "):
Lightcurve([1])

with pytest.raises(StingrayError, match="Empty or invalid counts array. "):
Lightcurve([1], [3, 4])

def test_single_time_no_dt_lightcurve(self):
with pytest.warns(UserWarning, match="Only one time bin and no dt specified. "):
lc = Lightcurve([1], [2])
assert lc.dt == 1

def test_single_time_with_dt_lightcurve(self):
lc = Lightcurve([1], [2], dt=5)
assert lc.dt == 5

@pytest.mark.skipif("not _IS_WINDOWS")
def test_warn_on_windows(self):
with pytest.warns(UserWarning) as record:
Expand Down Expand Up @@ -662,12 +685,6 @@ def test_slicing(self):
assert np.allclose(lc[:].counts_err, np.array([2, 2, 2, 2]) / 10)
assert lc[:].err_dist == lc.err_dist

def test_slicing_index_error(self):
lc = Lightcurve(self.times, self.counts)

with pytest.raises(StingrayError):
lc_new = lc[1:2]

def test_index(self):
lc = Lightcurve(self.times, self.counts)

Expand Down Expand Up @@ -915,7 +932,7 @@ def test_consecutive_gaps(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=UserWarning)
lc_test = Lightcurve(test_time, test_counts)
slc = lc_test.split(1.5)
slc = lc_test.split(1.5, min_points=2)

assert np.allclose(slc[0].time, [1, 2, 3])
assert np.allclose(slc[1].time, [9, 10, 11])
Expand Down Expand Up @@ -1126,25 +1143,31 @@ def test_io_with_hdf5(self):
os.remove("lc.hdf5")

def test_split_lc_by_gtis(self):
times = [1, 2, 3, 4, 5, 6, 7, 8]
counts = [1, 1, 1, 1, 2, 3, 3, 2]
bg_counts = [0, 0, 0, 1, 0, 1, 2, 0]
bg_ratio = [0.1, 0.1, 0.1, 0.2, 0.1, 0.2, 0.2, 0.1]
frac_exp = [1, 0.5, 1, 1, 1, 0.5, 0.5, 1]
gti = [[0.5, 4.5], [5.5, 7.5]]
times = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
counts = [1, 1, 1, 1, 2, 3, 3, 2, 3, 3]
bg_counts = [0, 0, 0, 1, 0, 1, 2, 0, 0, 1]
bg_ratio = [0.1, 0.1, 0.1, 0.2, 0.1, 0.2, 0.2, 0.1, 0.1, 0.2]
frac_exp = [1, 0.5, 1, 1, 1, 0.5, 0.5, 1, 1, 1]
gti = [[0.5, 4.5], [5.5, 7.5], [8.5, 9.5]]

lc = Lightcurve(
times, counts, gti=gti, bg_counts=bg_counts, bg_ratio=bg_ratio, frac_exp=frac_exp
)
list_of_lcs = lc.split_by_gti()
list_of_lcs = lc.split_by_gti(min_points=0)
assert len(list_of_lcs) == 3

lc0 = list_of_lcs[0]
lc1 = list_of_lcs[1]
lc2 = list_of_lcs[2]
assert np.allclose(lc0.time, [1, 2, 3, 4])
assert np.allclose(lc1.time, [6, 7])
assert np.allclose(lc2.time, [9])
assert np.allclose(lc0.counts, [1, 1, 1, 1])
assert np.allclose(lc1.counts, [3, 3])
assert np.allclose(lc1.counts, [3])
assert np.allclose(lc0.gti, [[0.5, 4.5]])
assert np.allclose(lc1.gti, [[5.5, 7.5]])
assert np.allclose(lc2.gti, [[8.5, 9.5]])
# Check if new attributes are also splited accordingly
assert np.allclose(lc0.bg_counts, [0, 0, 0, 1])
assert np.allclose(lc1.bg_counts, [1, 2])
Expand All @@ -1161,6 +1184,7 @@ def test_split_lc_by_gtis_minpoints(self):

lc = Lightcurve(times, counts, gti=gti)
list_of_lcs = lc.split_by_gti(min_points=min_points)
assert len(list_of_lcs) == 2
lc0 = list_of_lcs[0]
lc1 = list_of_lcs[1]
assert np.allclose(lc0.time, [1, 2, 3])
Expand Down

0 comments on commit 5bdef91

Please sign in to comment.