diff --git a/doc/versionHistory.rst b/doc/versionHistory.rst index b6c671a..e27a256 100644 --- a/doc/versionHistory.rst +++ b/doc/versionHistory.rst @@ -6,6 +6,13 @@ Version History ################## +------------- +0.10.0 +------------- + +* Add comcam support. +* Add new comcam configuration files. + ------------- 0.9.0 ------------- diff --git a/policy/config/input/telescope/lsstComCamLargePert.yaml b/policy/config/input/telescope/lsstComCamLargePert.yaml new file mode 100644 index 0000000..3d5481a --- /dev/null +++ b/policy/config/input/telescope/lsstComCamLargePert.yaml @@ -0,0 +1,69 @@ +telescope: + camera: LsstComCam + file_name: + type: FormattedStr + format: ComCam_%s.yaml + items: + - *band + rotTelPos: *rtp # Set the camera rotator angle using the value defined above. + # Many kinds of perturbations are possible. + # See https://github.com/LSSTDESC/imSim/blob/main/imsim/telescope_loader.py for details + # Some examples below + perturbations: + # Perturb optics directly. Note that some of these are degenerate with the + # fea.aos_dof.dof parameters below. For the closed-loop feedback simulation, it + # probably makes more sense to use the dof parameters. + M2: # Optics to perturb + shift: [1.0e-6, 0.0, 0.0] # x,y,z Shift in meters + rotX: 4 arcsec # Rotation around x axis + Zernike: # (annular by default; you can override R_inner or R_outer though) + idx: [4, 5, 6, 7, 8] # Zernike indices + val: [4.e-7, -2.e-7, 1.0e-7, 0.4e-7, -0.9e-7] # Zernike values in meters + M3: + Zernike: # (annular by default; you can override R_inner or R_outer though) + idx: [4, 5, 6, 7, 8] # Zernike indices + val: [3.1e-7, -3.6e-7, -0.3e-7, 1.7e-7, 0.3e-7] # Zernike values in meters + # There's a whole suite of FEA perturbations available (same as ts_phosim) + # Details in the telescope_loader.py file linked above. + fea: + m1m3_gravity: # gravitational flexure of M1M3 + zenith: *zenith + m1m3_temperature: + m1m3_TBulk: 0.1 # Celsius + m1m3_TxGrad: 0.01 # Kelvin/meter + m1m3_TyGrad: 0.01 # Kelvin/meter + m1m3_TzGrad: 0.01 # Kelvin/meter + m1m3_TrGrad: 0.01 # Kelvin/meter + # LUT correction to counteract m1m3_gravity + m1m3_lut: + # Note that you aren't _required_ to use the same zenith angle here as above, though + # it's usually a good idea. + zenith: *zenith + error: 0.05 # fractional actuator random error to apply to LUT correction. + seed: 11 # random seed for above error + m2_gravity: + zenith: *zenith + m2_temperature: + m2_TzGrad: 0.01 # Kelvin/meter + m2_TrGrad: 0.01 # Kelvin/meter + camera_gravity: + zenith: *zenith + rotation: *rtp + camera_temperature: + camera_TBulk: 0.1 # Celsius + + # And finally, AOS degrees of freedom in standard order and units. + # which means: + # 0: M2 dz (mm) + # 1,2: M2 dx,dy (mm) + # 3,4: M2 rx,ry (arcsec) + # 5: camera dz (mm) + # 6,7: camera dx,dy (mm) + # 8,9: camera rx,ry (arcsec) + # 10-29: M1M3 bending modes (mm) + # 30-49: M2 bending modes (mm) + + # Eval string here is compact for the demo. For simulating the closed loop, you probably + # will use a 50-element list here. + aos_dof: + dof: $[0.0]*50 diff --git a/policy/config/input/telescope/lsstComCamNoPert.yaml b/policy/config/input/telescope/lsstComCamNoPert.yaml new file mode 100644 index 0000000..bf687ac --- /dev/null +++ b/policy/config/input/telescope/lsstComCamNoPert.yaml @@ -0,0 +1,70 @@ +telescope: + camera: LsstComCam + file_name: + type: FormattedStr + format: ComCam_%s.yaml + items: + - *band + rotTelPos: *rtp # Set the camera rotator angle using the value defined above. + # Many kinds of perturbations are possible. + # See https://github.com/LSSTDESC/imSim/blob/main/imsim/telescope_loader.py for details + # Some examples below + # perturbations: + # # Perturb optics directly. Note that some of these are degenerate with the + # # fea.aos_dof.dof parameters below. For the closed-loop feedback simulation, it + # # probably makes more sense to use the dof parameters. + # M2: # Optics to perturb + # shift: [1.0e-6, 0.0, 0.0] # x,y,z Shift in meters + # rotX: 4 arcsec # Rotation around x axis + # Zernike: # (annular by default; you can override R_inner or R_outer though) + # idx: [4, 6] # Zernike indices + # val: [1.e-8, 0.1e-8] # Zernike values in meters + # M3: + # Zernike: + # # coefs: $[0.0]*23 # You can also set Zernike coefs like this... + # idx: [6] + # val: [1e-6] + # There's a whole suite of FEA perturbations available (same as ts_phosim) + # Details in the telescope_loader.py file linked above. + fea: + m1m3_gravity: # gravitational flexure of M1M3 + zenith: *zenith + m1m3_temperature: + m1m3_TBulk: 0.1 # Celsius + m1m3_TxGrad: 0.01 # Kelvin/meter + m1m3_TyGrad: 0.01 # Kelvin/meter + m1m3_TzGrad: 0.01 # Kelvin/meter + m1m3_TrGrad: 0.01 # Kelvin/meter + # LUT correction to counteract m1m3_gravity + m1m3_lut: + # Note that you aren't _required_ to use the same zenith angle here as above, though + # it's usually a good idea. + zenith: *zenith + error: 0.05 # fractional actuator random error to apply to LUT correction. + seed: 11 # random seed for above error + m2_gravity: + zenith: *zenith + m2_temperature: + m2_TzGrad: 0.01 # Kelvin/meter + m2_TrGrad: 0.01 # Kelvin/meter + camera_gravity: + zenith: *zenith + rotation: *rtp + camera_temperature: + camera_TBulk: 0.1 # Celsius + + # And finally, AOS degrees of freedom in standard order and units. + # which means: + # 0: M2 dz (mm) + # 1,2: M2 dx,dy (mm) + # 3,4: M2 rx,ry (arcsec) + # 5: camera dz (mm) + # 6,7: camera dx,dy (mm) + # 8,9: camera rx,ry (arcsec) + # 10-29: M1M3 bending modes (mm) + # 30-49: M2 bending modes (mm) + + # Eval string here is compact for the demo. For simulating the closed loop, you probably + # will use a 50-element list here. + aos_dof: + dof: $[0.0]*50 diff --git a/policy/config/lsstComCamLargePertPointer.yaml b/policy/config/lsstComCamLargePertPointer.yaml new file mode 100644 index 0000000..fce512e --- /dev/null +++ b/policy/config/lsstComCamLargePertPointer.yaml @@ -0,0 +1,12 @@ +# Default LSSTCam configuration files +input: + atm_psf: '{TS_IMSIM_DIR}/policy/config/input/atm_psf/atmPsfDefault.yaml' + sky_model: '{TS_IMSIM_DIR}/policy/config/input/sky_model/skyModelDefault.yaml' + telescope: '{TS_IMSIM_DIR}/policy/config/input/telescope/lsstComCamLargePert.yaml' + vignetting: '{TS_IMSIM_DIR}/policy/config/input/vignetting/lsstCamDefault.yaml' +gal: '{TS_IMSIM_DIR}/policy/config/gal/starDefault.yaml' +image: '{TS_IMSIM_DIR}/policy/config/image/lsstCamDefault.yaml' +psf: '{TS_IMSIM_DIR}/policy/config/psf/psfDefault.yaml' +stamp: '{TS_IMSIM_DIR}/policy/config/stamp/lsstCamDefault.yaml' +output: '{TS_IMSIM_DIR}/policy/config/output/lsstComCamDefault.yaml' +opd: '{TS_IMSIM_DIR}/policy/config/opd/lsstCamDefault.yaml' diff --git a/policy/config/lsstComCamNoPertPointer.yaml b/policy/config/lsstComCamNoPertPointer.yaml new file mode 100644 index 0000000..e9aa48d --- /dev/null +++ b/policy/config/lsstComCamNoPertPointer.yaml @@ -0,0 +1,12 @@ +# Default LSSTCam configuration files +input: + atm_psf: '{TS_IMSIM_DIR}/policy/config/input/atm_psf/atmPsfDefault.yaml' + sky_model: '{TS_IMSIM_DIR}/policy/config/input/sky_model/skyModelDefault.yaml' + telescope: '{TS_IMSIM_DIR}/policy/config/input/telescope/lsstComCamNoPert.yaml' + vignetting: '{TS_IMSIM_DIR}/policy/config/input/vignetting/lsstCamDefault.yaml' +gal: '{TS_IMSIM_DIR}/policy/config/gal/starDefault.yaml' +image: '{TS_IMSIM_DIR}/policy/config/image/lsstCamDefault.yaml' +psf: '{TS_IMSIM_DIR}/policy/config/psf/psfDefault.yaml' +stamp: '{TS_IMSIM_DIR}/policy/config/stamp/lsstCamDefault.yaml' +output: '{TS_IMSIM_DIR}/policy/config/output/lsstComCamDefault.yaml' +opd: '{TS_IMSIM_DIR}/policy/config/opd/lsstCamDefault.yaml' diff --git a/policy/config/output/lsstComCamDefault.yaml b/policy/config/output/lsstComCamDefault.yaml new file mode 100644 index 0000000..633d399 --- /dev/null +++ b/policy/config/output/lsstComCamDefault.yaml @@ -0,0 +1,46 @@ +output: + type: LSST_CCD + nproc: 9 # Change this to work on multiple CCDs at once. + nfiles: 9 # Comcam + + header: + mjd: *mjd + seqnum: *seqnum + + camera: LsstComCam + + exptime: $exptime + + cosmic_ray_rate: 0.0 # The rate of cosmic rays per second in a sensor. + + det_num: + type: List + items: $list(range(9)) + + + file_name: + type: FormattedStr + format : raw_%s-%1d-%s-%s-det%03d.fits.fz + items: + - *obsid + - 0 # snap + - *band + - $det_name # A value stored in the dict by LSST_CCD + - "@output.det_num" + + readout: + # Convert from e-image to realized amp images + readout_time: 2. + dark_current: 0.02 + bias_level: 1000. + pcti: 1.e-6 + scti: 1.e-6 + file_name: + type: FormattedStr + format : amp_%s-%1d-%s-%s-det%03d.fits.fz + items: + - *obsid + - 0 + - *band + - $det_name + - "@output.det_num" diff --git a/python/lsst/ts/imsim/closed_loop_task.py b/python/lsst/ts/imsim/closed_loop_task.py index 551e562..d4bd137 100644 --- a/python/lsst/ts/imsim/closed_loop_task.py +++ b/python/lsst/ts/imsim/closed_loop_task.py @@ -146,7 +146,7 @@ def _set_sky_sim_based_on_opd_field_pos( ) opd_metr = OpdMetrology() - if inst_name in ["lsst", "lsstfam"]: + if inst_name in ["lsst", "lsstfam", "comcam"]: field_x, field_y = list(), list() camera = get_camera(inst_name) for name in self.get_sensor_name_list_of_fields(inst_name): @@ -342,6 +342,8 @@ def get_cam_type_and_inst_name(self, inst: str) -> tuple[CamType, str]: return CamType.LsstCam, "lsst" elif inst == "lsstfam": return CamType.LsstFamCam, "lsstfam" + elif inst == "comcam": + return CamType.ComCam, "comcam" else: raise ValueError(f"This instrument ({inst}) is not supported.") @@ -505,7 +507,7 @@ def _run_sim( turn_off_sky_background=turn_off_sky_background, turn_off_atmosphere=turn_off_atmosphere, ) - elif cam_type == CamType.LsstFamCam: + elif cam_type in [CamType.LsstFamCam, CamType.ComCam]: for focus_z in [-1.5, 1.5]: obs_metadata.seq_num += 1 obs_metadata.focus_z = focus_z @@ -529,7 +531,7 @@ def _run_sim( ) if self.use_ccd_img: - if cam_type in [CamType.LsstCam, CamType.LsstFamCam]: + if cam_type in [CamType.LsstCam, CamType.LsstFamCam, CamType.ComCam]: list_of_wf_err = self._calc_wf_err_from_img( obs_metadata, butler_root_path=butler_root_path, @@ -692,7 +694,7 @@ def _generate_images( self.imsim_cmpt.write_yaml_and_run_imsim( imsim_config_path, imsim_config_yaml ) - elif inst_name == "lsstfam": + elif inst_name in ["lsstfam", "comcam"]: if self.use_ccd_img: # Run once for OPD imsim_opd_config_path = os.path.join( @@ -836,7 +838,10 @@ def run_wep( estimation pipeline for each sensor. """ - butler_inst_name = "Cam" + if inst_name in ["lsst", "lsstfam"]: + butler_inst_name = "Cam" + elif inst_name == "comcam": + butler_inst_name = "ComCam" if pipeline_file is None: pipeline_yaml = f"{inst_name}Pipeline.yaml" pipeline_yaml_path = os.path.join(butler_root_path, pipeline_yaml) @@ -875,6 +880,14 @@ def run_wep( f"--register-dataset-types --output-run ts_imsim_{seq_num} -p {pipeline_yaml_path} -d " f'"visit.seq_num IN ({seq_num-1}, {seq_num})" -j {num_pro}' ) + elif inst_name == "comcam": + runProgram( + f"pipetask run -b {butler_root_path} " + f"-i refcats,LSST{butler_inst_name}/raw/all,LSST{butler_inst_name}/calib/unbounded " + f"--instrument lsst.obs.lsst.Lsst{butler_inst_name} " + f"--register-dataset-types --output-run ts_imsim_{seq_num} -p {pipeline_yaml_path} -d " + f'"visit.seq_num IN ({seq_num-1}, {seq_num})" -j {num_pro}' + ) # Need to redefine butler because the database changed. butler = dafButler.Butler(butler_root_path) @@ -933,7 +946,10 @@ def write_wep_configuration( Filter type name: ref (or ''), u, g, r, i, z, or y. """ - butler_inst_name = "Cam" + if inst_name in ["lsst", "lsstfam"]: + butler_inst_name = "Cam" + elif inst_name == "comcam": + butler_inst_name = "ComCam" # Remap reference filter filter_type_name = self.map_filter_ref_to_g(filter_type_name) @@ -1128,9 +1144,14 @@ def generate_butler(self, butler_root_path: str, inst_name: str) -> None: runProgram(f"butler create {butler_root_path}") - self.log.debug("Registering LsstCam") + if inst_name in ["lsst", "lsstfam"]: + butler_inst_name = "Cam" + elif inst_name == "comcam": + butler_inst_name = "ComCam" + + self.log.debug(f"Registering Lsst{butler_inst_name}") runProgram( - f"butler register-instrument {butler_root_path} lsst.obs.lsst.LsstCam" + f"butler register-instrument {butler_root_path} lsst.obs.lsst.Lsst{butler_inst_name}" ) def generate_ref_catalog( @@ -1204,10 +1225,17 @@ def ingest_data(self, butler_root_path: str, inst_name: str) -> None: output_img_dir = self.imsim_cmpt.output_img_dir files = " ".join(glob(os.path.join(output_img_dir, "amp*"))) - if inst_name in ["lsst", "lsstfam"]: + if inst_name in ["lsst", "lsstfam", "comcam"]: runProgram(f"butler ingest-raws {butler_root_path} {files}") - runProgram(f"butler define-visits {butler_root_path} lsst.obs.lsst.LsstCam") + if inst_name in ["lsst", "lsstfam"]: + butler_inst_name = "Cam" + elif inst_name == "comcam": + butler_inst_name = "ComCam" + + runProgram( + f"butler define-visits {butler_root_path} lsst.obs.lsst.Lsst{butler_inst_name}" + ) def erase_directory_content(self, target_dir: str) -> None: """Erase the directory content. @@ -1244,7 +1272,7 @@ def set_default_parser(parser: ArgumentParser) -> ArgumentParser: "--inst", type=str, default="lsst", - help="Instrument to use: currently lsst or lsstfam. (default: lsst)", + help="Instrument to use: currently lsst, lsstfam, comcam. (default: lsst)", ) parser.add_argument( diff --git a/python/lsst/ts/imsim/opd_metrology.py b/python/lsst/ts/imsim/opd_metrology.py index db74820..5c0e0cb 100644 --- a/python/lsst/ts/imsim/opd_metrology.py +++ b/python/lsst/ts/imsim/opd_metrology.py @@ -122,7 +122,7 @@ def set_wgt_and_field_xy_of_gq(self, inst_name: str) -> None: ---------- inst_name : `str` Instrument name. - Valid options are 'lsst' or 'lsstfam. + Valid options are 'lsst', 'lsstfam', or 'comcam'. Raises ------ @@ -150,9 +150,11 @@ def set_wgt_and_field_xy_of_gq(self, inst_name: str) -> None: # Normalize weights self.wt = wgt_values / np.sum(wgt_values) + camera = get_camera(inst_name) if inst_name == "lsstfam": - camera = get_camera(inst_name) self.sensor_ids = np.arange(189) + elif inst_name == "comcam": + self.sensor_ids = np.arange(9) else: raise ValueError(f"Instrument {inst_name} is not supported in OPD mode.") @@ -333,7 +335,6 @@ def calc_gq_value(self, value_list: list[float] | np.ndarray) -> float: ValueError Length of wt ratio != length of value list. """ - # Check the lengths of weighting ratio and value list are the same if len(self.wt) != len(value_list): raise ValueError("Length of wt ratio != length of value list.") diff --git a/python/lsst/ts/imsim/utils/utility.py b/python/lsst/ts/imsim/utils/utility.py index 55f2f76..80761ff 100644 --- a/python/lsst/ts/imsim/utils/utility.py +++ b/python/lsst/ts/imsim/utils/utility.py @@ -34,7 +34,7 @@ import numpy as np import yaml from lsst.afw import cameraGeom -from lsst.obs.lsst import LsstCam +from lsst.obs.lsst import LsstCam, LsstComCam from lsst.utils import getPackageDir from numpy import ndarray @@ -95,9 +95,11 @@ def get_camera(inst_name: str) -> cameraGeom.Camera: # Check the input if (inst_name == "lsstfam") or (inst_name == "lsst"): return LsstCam().getCamera() + elif inst_name == "comcam": + return LsstComCam().getCamera() else: raise ValueError( - f"This instrument name ({inst_name}) is not supported. Must be 'lsstfam' or 'lsst'." + f"This instrument name ({inst_name}) is not supported. Must be 'lsstfam', 'lsst', or 'comcam'." ) diff --git a/tests/test_closed_loop_task.py b/tests/test_closed_loop_task.py index 9898454..b55f6ec 100644 --- a/tests/test_closed_loop_task.py +++ b/tests/test_closed_loop_task.py @@ -130,6 +130,18 @@ def test_get_cam_type_and_inst_name_lsst(self): self.assertEqual(cam_type, CamType.LsstCam) self.assertEqual(inst_name, "lsst") + def test_get_cam_type_and_inst_name_fam(self): + cam_type, inst_name = self.closed_loop_task.get_cam_type_and_inst_name( + "lsstfam" + ) + self.assertEqual(cam_type, CamType.LsstFamCam) + self.assertEqual(inst_name, "lsstfam") + + def test_get_cam_type_and_inst_name_comcam(self): + cam_type, inst_name = self.closed_loop_task.get_cam_type_and_inst_name("comcam") + self.assertEqual(cam_type, CamType.ComCam) + self.assertEqual(inst_name, "comcam") + def test_get_cam_type_and_inst_name_err(self): self.assertRaises( ValueError, self.closed_loop_task.get_cam_type_and_inst_name, "noThisInst" diff --git a/tests/test_opd_metrology.py b/tests/test_opd_metrology.py index 3bd6ffa..fffd832 100644 --- a/tests/test_opd_metrology.py +++ b/tests/test_opd_metrology.py @@ -58,6 +58,15 @@ def test_set_wgt_and_field_xy_of_gq_lsst_fam(self): wgt = self.metr.wt self.assertEqual(len(wgt), 189) + def test_set_wgt_and_field_xy_of_gq_com_cam(self): + self.metr.set_wgt_and_field_xy_of_gq("comcam") + + field_x = self.metr.field_x + self.assertEqual(len(field_x), 9) + + wgt = self.metr.wt + self.assertEqual(len(wgt), 9) + def test_set_wgt_and_field_xy_of_gq_err(self): self.assertRaises( RuntimeError, self.metr.set_wgt_and_field_xy_of_gq, "NoThisInstName" diff --git a/tests/utils/test_utility.py b/tests/utils/test_utility.py index fb8c33c..6a80c3d 100644 --- a/tests/utils/test_utility.py +++ b/tests/utils/test_utility.py @@ -23,7 +23,7 @@ import unittest from lsst.afw import cameraGeom -from lsst.obs.lsst import LsstCam +from lsst.obs.lsst import LsstCam, LsstComCam from lsst.ts.imsim.utils import ( ModifiedEnvironment, get_camera, @@ -58,6 +58,10 @@ def test_get_camera(self): self.assertIsInstance(lsst_cam, cameraGeom.Camera) self.assertEqual(lsst_cam.getName(), LsstCam.getCamera().getName()) + com_cam = get_camera("comcam") + self.assertIsInstance(com_cam, cameraGeom.Camera) + self.assertEqual(com_cam.getName(), LsstComCam.getCamera().getName()) + with self.assertRaises(ValueError): get_camera("invalid")