Skip to content

Commit 278e733

Browse files
joyvelasquezjoyvelasquez
andauthored
Update CCD contam file from IDL geny type file to sav (#374)
* Updated ccd contam data up too-2025-10-07 06:14 * adding sav file-made in IDL * Mirrored script to test sav file -ccd data. * Fixed NameError * updated methods * Updated effective area testing methods using sav file * Remove deprecated effective_area_sav.py file * Applied tools to code * Migrate XRT contamination data from GENY to SAV format - Replaced CCD contamination .geny with .sav using scipy.io.readsav - Implemented lazy-loading via cached_property for both CCD and filter contamination - Validated contamination interpolation and observation-date checks - Verified all 635 tests pass (effective area, filter combos, and IDL comparisons) * Applied ruff - due to pre-commit not passing --------- Co-authored-by: joyvelasquez <[email protected]>
1 parent 9d0e2c0 commit 278e733

File tree

4 files changed

+75
-30
lines changed

4 files changed

+75
-30
lines changed
89 Bytes
Binary file not shown.
11.4 KB
Binary file not shown.

xrtpy/response/effective_area.py

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,15 @@
4747
"C-poly/Ti-poly": 13,
4848
}
4949

50+
5051
_ccd_contam_filename = (
51-
Path(__file__).parent.absolute() / "data" / "xrt_contam_on_ccd.geny"
52+
Path(__file__).parent.absolute() / "data" / "xrt_contam_on_ccd.sav"
5253
)
5354

5455
_filter_contam_filename = (
5556
Path(__file__).parent.absolute() / "data" / "xrt_contam_on_filter.geny"
5657
)
5758

58-
_ccd_contam_file = scipy.io.readsav(_ccd_contam_filename)
59-
_filter_contam_file = scipy.io.readsav(_filter_contam_filename)
60-
61-
# CCD contam geny files keys for time and date.
62-
_ccd_contamination_file_time = astropy.time.Time(
63-
_ccd_contam_file["p1"], format="utime", scale="utc"
64-
)
65-
_ccd_contamination = _ccd_contam_file["p2"]
66-
67-
# Filter contam geny files keys for time and date.
68-
_filter_contamination_file_time = astropy.time.Time(
69-
_filter_contam_file["p1"], format="utime", scale="utc"
70-
)
71-
_filter_contamination = _filter_contam_file["p2"]
72-
7359

7460
class ParsedFilter(NamedTuple):
7561
filter1: str
@@ -213,9 +199,14 @@ def observation_date(self, date):
213199

214200
modified_time_path = Path(_ccd_contam_filename).stat().st_mtime
215201
modified_time = astropy.time.Time(modified_time_path, format="unix")
216-
latest_available_ccd_data = _ccd_contamination_file_time[-1].datetime.strftime(
217-
"%Y/%m/%d"
218-
)
202+
# REMOVE
203+
# latest_available_ccd_data = _ccd_contamination_file_time[-1].datetime.strftime(
204+
# "%Y/%m/%d"
205+
# )
206+
latest_available_ccd_data = self._ccd_contamination_file_time[
207+
-1
208+
].datetime.strftime("%Y/%m/%d")
209+
219210
modified_time_datetime = datetime.datetime.fromtimestamp(
220211
modified_time_path
221212
).strftime("%Y/%m/%d")
@@ -233,6 +224,40 @@ def observation_date(self, date):
233224

234225
self._observation_date = observation_date
235226

227+
@cached_property
228+
def _filter_contam_file(self):
229+
return scipy.io.readsav(_filter_contam_filename)
230+
231+
@cached_property
232+
def _filter_contamination_file_time(self):
233+
return astropy.time.Time(
234+
self._filter_contam_file["p1"], format="utime", scale="utc"
235+
)
236+
237+
@cached_property
238+
def _filter_contamination(self):
239+
return self._filter_contam_file["p2"]
240+
241+
@cached_property
242+
def ccd_contam_data(self):
243+
return scipy.io.readsav(_ccd_contam_filename)
244+
245+
@cached_property
246+
def _ccd_contamination_file_time(self):
247+
"""
248+
CCD contamination time axis from the .sav file.
249+
"""
250+
return astropy.time.Time(
251+
self.ccd_contam_data["p1"], format="utime", scale="utc"
252+
)
253+
254+
@cached_property
255+
def _ccd_contamination(self):
256+
"""
257+
CCD contamination thickness values from the .sav file.
258+
"""
259+
return self.ccd_contam_data["p2"]
260+
236261
@property
237262
def contamination_on_CCD(self):
238263
"""
@@ -259,12 +284,14 @@ def contamination_on_CCD(self):
259284
If the observation date is outside the range of the available contamination data.
260285
"""
261286
interpolater = scipy.interpolate.interp1d(
262-
_ccd_contamination_file_time.utime, _ccd_contamination, kind="linear"
287+
self._ccd_contamination_file_time.utime,
288+
self._ccd_contamination,
289+
kind="linear",
263290
)
264291
return interpolater(self.observation_date.utime)
265292

266293
@staticmethod
267-
def _get_filter_wheel_number(filter_name: str) -> int: # *********
294+
def _get_filter_wheel_number(filter_name: str) -> int:
268295
"""Returns 0 if the filter is in FW1, 1 if in FW2. Raises error if unknown."""
269296
if filter_name in index_mapping_to_fw1_name:
270297
return 0
@@ -310,7 +337,7 @@ def _get_filter_index(filter_name: str) -> int:
310337
@property
311338
def filter_data_dates_to_seconds(self):
312339
"""Returns the contamination file time axis in seconds (utime)."""
313-
return _filter_contamination_file_time.utime
340+
return self._filter_contamination_file_time.utime
314341

315342
@property
316343
def filter_observation_date_to_seconds(self):
@@ -325,7 +352,7 @@ def _filter1_data(self):
325352
if self._filter1_wheel == 0
326353
else index_mapping_to_fw2_name.get(self.filter1_name)
327354
)
328-
return _filter_contamination[filter1_index][self._filter1_wheel]
355+
return self._filter_contamination[filter1_index][self._filter1_wheel]
329356

330357
@property
331358
def _filter2_data(self):
@@ -337,15 +364,17 @@ def _filter2_data(self):
337364
if self._filter2_wheel == 0
338365
else index_mapping_to_fw2_name.get(self.filter2_name)
339366
)
340-
return _filter_contamination[filter2_index][self._filter2_wheel]
367+
return self._filter_contamination[filter2_index][self._filter2_wheel]
341368

342369
@property
343370
def contamination_on_filter1(self) -> u.angstrom:
344371
"""
345372
Interpolates contamination layer thickness on filter1.
346373
"""
347374
interpolater = scipy.interpolate.interp1d(
348-
_filter_contamination_file_time.utime, self._filter1_data, kind="linear"
375+
self._filter_contamination_file_time.utime,
376+
self._filter1_data,
377+
kind="linear",
349378
)
350379
return interpolater(self.observation_date.utime)
351380

@@ -363,7 +392,9 @@ def contamination_on_filter2(self) -> u.Quantity | None:
363392
return None
364393

365394
interpolater = scipy.interpolate.interp1d(
366-
_filter_contamination_file_time.utime, self._filter2_data, kind="linear"
395+
self._filter_contamination_file_time.utime,
396+
self._filter2_data,
397+
kind="linear",
367398
)
368399
return interpolater(self.observation_date.utime)
369400

@@ -386,7 +417,6 @@ def n_DEHP_attributes(self):
386417
@cached_property
387418
def n_DEHP_wavelength(self):
388419
"""(Diethylhexylphthalate) Wavelength given in Angstrom (Å)."""
389-
390420
# Convert wavelength values from nanometers to Angstroms
391421
wavelength_str = [
392422
self.n_DEHP_attributes[i][0] for i in range(2, len(self.n_DEHP_attributes))

xrtpy/response/tests/test_effective_area.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@
6060
]
6161

6262

63+
def test_ccd_contam_data_loads():
64+
eff = EffectiveAreaFundamental(
65+
"Al-poly", datetime(year=2013, month=9, day=22, hour=22, minute=1, second=1)
66+
)
67+
data = eff.ccd_contam_data
68+
assert isinstance(data, dict)
69+
assert "p1" in data
70+
assert "p2" in data
71+
72+
6373
@pytest.mark.parametrize("channel_name", channel_names)
6474
def test_channel_name(channel_name):
6575
channel = Channel(channel_name)
@@ -78,14 +88,19 @@ def test_EffectiveArea_filter_name(name):
7888
@pytest.mark.parametrize("name", channel_names)
7989
def test_EffectiveArea_contamination_on_CCD(name, date):
8090
instance = EffectiveAreaFundamental(name, date)
81-
assert 0 <= instance.contamination_on_CCD <= 1206
91+
assert np.all(
92+
(instance.contamination_on_CCD >= 0) & (instance.contamination_on_CCD <= 1206)
93+
)
8294

8395

8496
@pytest.mark.parametrize("date", valid_dates)
8597
@pytest.mark.parametrize("name", channel_single_filter_names)
8698
def test_EffectiveArea_contamination_on_filter1(name, date):
8799
instance = EffectiveAreaFundamental(name, date)
88-
assert 0 <= instance.contamination_on_filter1 <= 2901
100+
assert np.all(
101+
(instance.contamination_on_filter1 >= 0)
102+
& (instance.contamination_on_filter1 <= 2901)
103+
)
89104

90105

91106
@pytest.mark.parametrize("date", valid_dates)
@@ -94,7 +109,7 @@ def test_EffectiveArea_contamination_on_filter2(name, date):
94109
instance = EffectiveAreaFundamental(name, date)
95110
if instance.is_combo:
96111
result = instance.contamination_on_filter2
97-
assert result is None or (0 <= result <= 2901)
112+
assert result is None or np.all((result >= 0) & (result <= 2901))
98113
else:
99114
assert instance.contamination_on_filter2 is None
100115

0 commit comments

Comments
 (0)