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

Header fixes and LSSTCamSim support #473

Merged
merged 11 commits into from
May 23, 2024
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.fits binary
*.fits.fz binary
*.fits.gz binary
6 changes: 6 additions & 0 deletions data/LsstCamSim_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
tree_rings_file_name: tree_ring_parameters_2018-04-26.txt
vignetting_file_name: LSSTCam_vignetting_data.json
telescope_format: LSST_%s.yaml
bias_levels_file: LSSTCam_bias_levels_run_13421.json
camera_name: LsstCam
ndets: 189
2 changes: 1 addition & 1 deletion imsim/bandpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def RubinBandpass(
if (camera is None) != (det_name is None):
raise ValueError("Must provide both camera and det_name if using one.")
match camera:
case 'LsstCam':
case 'LsstCam' | 'LsstCamSim' :
camera = 'lsstCam'
case 'LsstComCamSim':
camera = 'comCamSim'
Expand Down
2 changes: 1 addition & 1 deletion imsim/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def get_camera(camera='LsstCam'):
-------
lsst.afw.cameraGeom.Camera
"""
valid_cameras = ('LsstCam', 'LsstCamImSim', 'LsstComCamSim')
valid_cameras = ('LsstCam', 'LsstCamSim', 'LsstCamImSim', 'LsstComCamSim')
if camera not in valid_cameras:
raise ValueError('Invalid camera: %s', camera)
if camera not in _camera_cache:
Expand Down
10 changes: 7 additions & 3 deletions imsim/ccd.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,11 @@ def parse(item, type, default):
# Now construct the image header
image.header['MJD'] = mjd
image.header['MJD-OBS'] = mjd_obs, 'Start of exposure'
# NOTE: Should this day be the current day,
# or the day at the time of the most recent noon?
dayobs = astropy.time.Time(mjd_obs, format='mjd').strftime('%Y%m%d')
# Subtract half-day offset from mjd-obs for dayobs calculation
# following Rubin convention. See
# https://github.com/lsst/astro_metadata_translator/blob/w.2024.19/python/astro_metadata_translator/translator.py#L1065
# and Appendix A of https://docushare.lsst.org/docushare/dsweb/Get/LSE-400
dayobs = astropy.time.Time(mjd_obs - 0.5, format='mjd').strftime('%Y%m%d')
image.header['DAYOBS'] = dayobs
image.header['SEQNUM'] = seqnum
image.header['CONTRLLR'] = 'S', 'simulated data'
Expand All @@ -192,6 +194,8 @@ def parse(item, type, default):
image.header['AMSTART'] = airmass
image.header['AMEND'] = airmass # wrong, does anyone care?
image.header['FOCUSZ'] = parse('focusZ', float, 0.0)
image.header['ALTITUDE'] = parse('altitude', float, 'N/A')
image.header['AZIMUTH'] = parse('azimuth', float, 'N/A')

# If there's anything left in header_vals, add it to the header.
for k in header_vals:
Expand Down
5 changes: 3 additions & 2 deletions imsim/opsim_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ def _read_opsim_db(self):
self.meta[key] = value

# Determine the daily sequence number for this exposure by
# counting the number of snaps since int(observationStartMJD).
t0 = int(self.meta['observationStartMJD'])
# counting the number of snaps since int(observationStartMJD)
# with a half-day offset to account for Rubin's dayobs definition.
t0 = int(self.meta['observationStartMJD']) - 0.5
sql = f'''select numExposures from observations where
{t0} <= observationStartMJD and
observationId < {self.visit}'''
Expand Down
21 changes: 18 additions & 3 deletions imsim/readout.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ def get_primary_hdu(eimage, lsst_num, camera_name=None,
telcode = 'MC'
elif camera_name == 'LsstComCamSim' :
phdu.header['FILTER'] = ComCam_filter_map.get(band, None)
phdu.header['TELESCOP'] = SIMONYI_TELESCOPE
phdu.header['INSTRUME'] = 'ComCamSim'
phdu.header['RAFTBAY'] = raft
phdu.header['CCDSLOT'] = sensor
Expand All @@ -270,16 +269,31 @@ def get_primary_hdu(eimage, lsst_num, camera_name=None,
telcode = 'CC'
else:
phdu.header['FILTER'] = LSSTCam_filter_map.get(band, None)
phdu.header['INSTRUME'] = 'LSSTCam'
if camera_name == 'LsstCamSim':
phdu.header['INSTRUME'] = 'LSSTCamSim'
else:
phdu.header['INSTRUME'] = 'LSSTCam'
phdu.header['RAFTBAY'] = raft
phdu.header['CCDSLOT'] = sensor
phdu.header['RA'] = ratel
phdu.header['DEC'] = dectel
phdu.header['ROTCOORD'] = 'sky'
phdu.header['ROTPA'] = rotang
telcode = 'MC'
dayobs = eimage.header['DAYOBS']
seqnum = eimage.header['SEQNUM']
contrllr = eimage.header['CONTRLLR']
phdu.header['TELESCOP'] = SIMONYI_TELESCOPE
Copy link
Member

Choose a reason for hiding this comment

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

Maybe factor out some of the other common keywords too? E.g., 'RAFTBAY', 'CCDSLOT', 'RA', 'DEC', 'ROTCOORD', 'ROTPA', ...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In order to do that more cleanly, I'd like to get rid of support for LsstCamImSim, otherwise I suspect the number of if statements will actually increase. In fact, in addition to getting rid of LsstCamImSim support, I'd like to support just *Sim cameras explicitly, e.g., LsstCamSim and LsstComCamSim, so also removing LsstCam as an option. I'll post an issue with this proposal.

phdu.header['TELCODE'] = telcode
phdu.header['RASTART'] = ratel
phdu.header['DECSTART'] = dectel
phdu.header['ELSTART'] = eimage.header['ALTITUDE']
phdu.header['AZSTART'] = eimage.header['AZIMUTH']
if eimage.header['IMGTYPE'] == 'SKYEXP':
phdu.header['RADESYS'] = 'ICRS'
phdu.header['TRACKSYS'] = 'RADEC'
else:
Copy link
Member

Choose a reason for hiding this comment

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

When does this get used? Simulated flats?

Copy link
Member

Choose a reason for hiding this comment

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

(by this, I mean TRACKSYS = LOCAL).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, for anything not on-sky, specifically any calibration frames.

phdu.header['TRACKSYS'] = 'LOCAL'
phdu.header['OBSID'] = f"{telcode}_{contrllr}_{dayobs}_{seqnum:06d}"
phdu.header['MJD-OBS'] = mjd_obs
phdu.header['HASTART'] = eimage.header['HASTART']
Expand All @@ -288,9 +302,10 @@ def get_primary_hdu(eimage, lsst_num, camera_name=None,
phdu.header['DATE-END'] = Time(mjd_end, format='mjd', scale='tai').to_value('isot')
phdu.header['AMSTART'] = eimage.header['AMSTART']
phdu.header['AMEND'] = eimage.header['AMEND']
phdu.header['ORIGIN'] = "imSim"
phdu.header['IMSIMVER'] = __version__
phdu.header['PKG00000'] = 'throughputs'
phdu.header['VER00000'] = '1.4'
phdu.header['VER00000'] = '1.9'
phdu.header['CHIPID'] = det_name
phdu.header['FOCUSZ'] = eimage.header['FOCUSZ']

Expand Down
22 changes: 0 additions & 22 deletions tests/data/eimage_00449053-1-r-R22_S11-det094.fits

This file was deleted.

Binary file not shown.
3 changes: 3 additions & 0 deletions tests/test_object_positions.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ def test_output_catalog():
assert data['realized_flux'] > 0.99 * data['phot_flux']
# fft_flux is 0 when object was photon shot.
assert data['fft_flux'] == 0.
output_dir = "fits_LsstCam"
if os.path.isdir(output_dir):
shutil.rmtree(output_dir)


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion tests/test_raw_file_writing.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_raw_file_writing(self):
This is mostly an operational test that the raw files can be
written from a galsim image.
"""
eimage_file = str(DATA_DIR / 'eimage_00449053-1-r-R22_S11-det094.fits')
eimage_file = str(DATA_DIR / 'eimage_00449053-1-r-R22_S11-det094.fits.gz')
eimage = galsim.fits.read(eimage_file)
eimage.header = galsim.FitsHeader(file_name=eimage_file)

Expand Down
22 changes: 21 additions & 1 deletion tests/test_readout.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ImageSourceTestCase(unittest.TestCase):
"TestCase class for ImageSource."

def setUp(self):
self.eimage_file = str(DATA_DIR / 'eimage_00449053-1-r-R22_S11-det094.fits')
self.eimage_file = str(DATA_DIR / 'eimage_00449053-1-r-R22_S11-det094.fits.gz')
instcat_file = str(DATA_DIR / 'tiny_instcat.txt')
self.image = galsim.fits.read(self.eimage_file)
self.image.header = galsim.FitsHeader(file_name=self.eimage_file)
Expand Down Expand Up @@ -94,11 +94,31 @@ def test_raw_file_headers(self):
"Test contents of raw file headers."
outfile = 'raw_file_test.fits'
self.readout.writeFile(outfile, self.readout_config, self.config, self.logger)
# Some required keywords that were noted as missing for OR3.
# See https://github.com/LSSTDESC/imSim/issues/457.
expected_keywords = [
"RA",
"DEC",
"RASTART",
"DECSTART",
"ROTPA",
"ROTCOORD",
"HASTART",
"ELSTART",
"AZSTART",
"AMSTART",
"TRACKSYS",
"RADESYS",
"ORIGIN",
"TELCODE"
]
with fits.open(outfile) as hdus:
self.assertEqual(hdus[0].header['IMSIMVER'], imsim.__version__)
# Test added_keywords are included correctly
self.assertEqual(hdus[0].header['TESTKEY1'], 'TESTVAL1')
self.assertEqual(hdus[0].header['SOMEMATH'], '3')
for keyword in expected_keywords:
hdus[0].header[keyword]
os.remove(outfile)

def test_no_opsim(self):
Expand Down
Loading