Skip to content

Commit

Permalink
Merge pull request #152 from lsst-ts/tickets/DM-46969
Browse files Browse the repository at this point in the history
Tickets/dm 46969 Create script for take_rotated_comcam.py
  • Loading branch information
iglesu authored Oct 25, 2024
2 parents a2bf33e + 244e2df commit 02954f3
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 0 deletions.
2 changes: 2 additions & 0 deletions doc/news/DM-46969.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Create take_rotated_comcam.py script.
The script takes a ComCam aos sequence at different rotation angles.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python
# This file is part of ts_externalscripts
#
# Developed for the LSST Telescope and Site Systems.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License

import asyncio

from lsst.ts.externalscripts.maintel import TakeRotatedComCam

asyncio.run(TakeRotatedComCam.amain())
1 change: 1 addition & 0 deletions python/lsst/ts/externalscripts/maintel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .make_comcam_calibrations import *
from .take_comcam_guider_image import *
from .take_ptc_flats_comcam import *
from .take_rotated_comcam import *
from .take_twilight_flats_comcam import *
from .take_twilight_flats_lsstcam import *
from .track_target_sched import *
Expand Down
179 changes: 179 additions & 0 deletions python/lsst/ts/externalscripts/maintel/take_rotated_comcam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# This file is part of ts_externalscripts
#
# Developed for the LSST Telescope and Site Systems.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

__all__ = ["TakeRotatedComCam"]

import astropy.units
import yaml
from astropy.coordinates import ICRS, Angle
from lsst.ts.observatory.control.utils import RotType
from lsst.ts.standardscripts.maintel.take_aos_sequence_comcam import (
TakeAOSSequenceComCam,
)
from lsst.ts.xml.enums.MTPtg import WrapStrategy
from lsst.ts.xml.enums.Script import MetadataCoordSys, MetadataRotSys


class TakeRotatedComCam(TakeAOSSequenceComCam):
"""Take images with ComCam at different rotator angles.
Parameters
----------
index : `int`
Index of Script SAL component.
Notes
-----
**Checkpoints**
- "Slewing to rotator angle {angle} degrees.": Before slewing to the angle.
- "Taking images at rotator angle {angle} degrees.": Before taking images.
"""

def __init__(self, index):
super().__init__(
index=index, descr="Take AOS sequence at rotated positions with ComCam."
)
# Timeout for slewing (in seconds).
self.slew_timeout = 240.0

@classmethod
def get_schema(cls):
"""Get the configuration schema, combining base class schema with
additional properties."""
schema_dict = super().get_schema()
schema_dict["$id"] = (
"https://github.com/lsst-ts/ts_externalscripts/maintel/TakeRotatedComCam.yaml"
)
schema_dict["title"] = "TakeRotatedComCam v1"
schema_dict["description"] = "Configuration for TakeRotatedComCam."

additional_schema_yaml = """
properties:
ra:
description: ICRS right ascension (hour).
anyOf:
- type: number
minimum: 0
maximum: 24
- type: string
dec:
description: ICRS declination (deg).
anyOf:
- type: number
minimum: -90
maximum: 90
- type: string
target_name:
description: Name of the target object.
type: string
angles:
description: Sequence of rotation angles to move the rotator to.
type: array
items:
type: number
slew_timeout:
description: Timeout for slew operations (seconds).
type: number
default: 240.0
required:
- ra
- dec
- target_name
- angles
"""
additional_schema = yaml.safe_load(additional_schema_yaml)

schema_dict["properties"].update(additional_schema["properties"])

schema_dict.setdefault("required", [])
schema_dict["required"].extend(additional_schema.get("required", []))

return schema_dict

def set_metadata(self, metadata):
super().set_metadata(metadata)
metadata.coordinateSystem = MetadataCoordSys.ICRS
radec_icrs = ICRS(
Angle(self.config.ra, unit=astropy.units.hourangle),
Angle(self.config.dec, unit=astropy.units.deg),
)
metadata.position = [radec_icrs.ra.deg, radec_icrs.dec.deg]

# Since we are defaulting to RotType.Physical, metadata rotation system
# should be MOUNT
metadata.rotationSystem = MetadataRotSys.MOUNT
metadata.cameraAngle = float(self.config.angles[0])
metadata.summary = f"Rotator angles: {self.config.angles}"

async def configure(self, config):
"""Configure script.
Parameters
----------
config : `types.SimpleNamespace`
Script configuration, as defined by `schema`.
"""
await super().configure(config)
self.config = config
self.slew_timeout = getattr(config, "slew_timeout", self.slew_timeout)
self.log.info(f"Configured with target name={self.config.target_name}")

async def run_block(self):
"""Execute script operations."""
await self.assert_feasibility()

for angle in self.config.angles:
self.log.info(
f"Slewing to target {self.config.target_name} with rotator angle {angle} degrees."
)

await self.checkpoint(
f"[{self.config.target_name}; "
f"ra={self.config.ra}, dec={self.config.dec};"
f"rot={angle:0.2f}]::"
f"Slewing to rotator angle {angle} degrees."
)

await self.mtcs.slew_icrs(
ra=self.config.ra,
dec=self.config.dec,
rot=angle,
rot_type=RotType.Physical,
target_name=self.config.target_name,
az_wrap_strategy=WrapStrategy.NOUNWRAP,
)

await self.checkpoint(
f"[{self.config.target_name}; "
f"ra={self.config.ra}, dec={self.config.dec};"
f"rot={angle:0.2f}]::"
f"Take aos sequence at angle {angle} degrees."
)

await self.take_aos_sequence()

await self.checkpoint(
f"[{self.config.target_name}; "
f"ra={self.config.ra}, dec={self.config.dec};"
f"rot={angle:0.2f}]::"
f"Done aos sequence at angle {angle} degrees."
)
161 changes: 161 additions & 0 deletions tests/maintel/test_take_rotated_comcam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# This file is part of ts_externalscripts
#
# Developed for the LSST Telescope and Site Systems.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import unittest

from lsst.ts import externalscripts, standardscripts
from lsst.ts.externalscripts.maintel import TakeRotatedComCam
from lsst.ts.idl.enums.Script import ScriptState
from lsst.ts.observatory.control.maintel.comcam import ComCam, ComCamUsages
from lsst.ts.observatory.control.maintel.mtcs import MTCS, MTCSUsages
from lsst.ts.observatory.control.utils import RotType
from lsst.ts.standardscripts.maintel import Mode
from lsst.ts.xml.enums.ATPtg import WrapStrategy


class TestTakeRotatedComCam(
standardscripts.BaseScriptTestCase, unittest.IsolatedAsyncioTestCase
):
async def basic_make_script(self, index):
self.script = TakeRotatedComCam(index=index)

self.script.mtcs = MTCS(
domain=self.script.domain,
intended_usage=MTCSUsages.DryTest,
log=self.script.log,
)

self.script.camera = ComCam(
domain=self.script.domain,
intended_usage=ComCamUsages.DryTest,
log=self.script.log,
)

self.script.mtcs.slew_icrs = unittest.mock.AsyncMock()
self.script.mtcs.wait_for_inposition = unittest.mock.AsyncMock()
self.script.mtcs.assert_feasibility = unittest.mock.AsyncMock()
self.script.mtcs.ready_to_take_data = unittest.mock.AsyncMock()
self.script.mtcs.offset_camera_hexapod = unittest.mock.AsyncMock()
self.script.camera.expose = unittest.mock.AsyncMock()
self.script.camera.setup_instrument = unittest.mock.AsyncMock()
self.script.camera.ready_to_take_data = unittest.mock.AsyncMock()
self.script.take_aos_sequence = unittest.mock.AsyncMock()

return (self.script,)

async def test_configure(self):
async with self.make_script():
exposure_time = 15.0
filter = "g"
dz = 2000.0
mode = "INTRA"
angles = [0.0, 45.0, 90.0]
target_name = "HD 185975"
ra = "20:28:18.74"
dec = "-87:28:19.9"
slew_timeout = 300.0

await self.configure_script(
angles=angles,
target_name=target_name,
ra=ra,
dec=dec,
slew_timeout=slew_timeout,
filter=filter,
exposure_time=exposure_time,
dz=dz,
mode=mode,
)
assert self.script.exposure_time == exposure_time
assert self.script.filter == filter
assert self.script.dz == 2000.0
assert self.script.mode == Mode.INTRA
assert self.script.config.angles == angles
assert self.script.config.target_name == target_name
assert self.script.config.ra == ra
assert self.script.config.dec == dec
assert self.script.slew_timeout == slew_timeout

async def test_run(self):
async with self.make_script():
exposure_time = 15.0
filter = "g"
dz = 2000.0
mode = "TRIPLET"
angles = [0.0, 45.0, 90.0]
target_name = "HD 185975"
ra = "20:28:18.74"
dec = "-87:28:19.9"
slew_timeout = 300.0

await self.configure_script(
angles=angles,
target_name=target_name,
ra=ra,
dec=dec,
slew_timeout=slew_timeout,
filter=filter,
exposure_time=exposure_time,
dz=dz,
mode=mode,
)

await self.run_script()

self.assertEqual(self.script.state.state, ScriptState.DONE)

expected_slew_calls = len(angles)
expected_take_sequence_calls = len(angles)

self.assertEqual(
self.script.mtcs.slew_icrs.await_count,
expected_slew_calls,
f"slew_icrs was called {self.script.mtcs.slew_icrs.await_count} times, "
f"expected {expected_slew_calls}",
)

self.assertEqual(
self.script.take_aos_sequence.await_count,
expected_take_sequence_calls,
f"take_aos_sequence was called {self.script.take_aos_sequence.await_count} times, "
f"expected {expected_take_sequence_calls}",
)

for call_args in self.script.mtcs.slew_icrs.await_args_list:
called_args, called_kwargs = call_args
self.assertEqual(called_kwargs["ra"], ra)
self.assertEqual(called_kwargs["dec"], dec)
self.assertEqual(called_kwargs["rot_type"], RotType.Physical)
self.assertEqual(called_kwargs["target_name"], target_name)
self.assertIn(called_kwargs["rot"], angles)
self.assertEqual(
called_kwargs["az_wrap_strategy"], WrapStrategy.NOUNWRAP
)

async def test_executable_script(self) -> None:
"""Test that the script is executable."""
scripts_dir = externalscripts.get_scripts_dir()
script_path = scripts_dir / "maintel" / "take_rotated_comcam.py"
await self.check_executable(script_path)


if __name__ == "__main__":
unittest.main()

0 comments on commit 02954f3

Please sign in to comment.