Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
gmegh committed Sep 13, 2023
1 parent 1b14d85 commit 63291fb
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 73 deletions.
137 changes: 76 additions & 61 deletions python/lsst/ts/standardscripts/maintel/closed_loop_cwfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import yaml
from lsst.ts import salobj
from lsst.ts.observatory.control import RemoteGroup

# TO-DO: Remove this import once the new LSSTCam is available
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.remote_group import Usages
Expand Down Expand Up @@ -92,12 +94,8 @@ class DOFComponent(enum.IntEnum):


class ClosedLoopCwfs(salobj.BaseScript, metaclass=abc.ABCMeta):
"""Implements generic behavior for scripts that execute curvature wavefront
sensing, abstracting the part that performs the measurements.
This metaclass implements the basic functionality of finding targets and
taking the (intra/extra focal) data. Child classes are left to implement
the processing of the data and returning the results.
"""Closed loop CWFS script. This script is used to perform measurements of
the wavefront error, then propose dof offsets based on ts_ofc.
Parameters
----------
Expand All @@ -113,22 +111,20 @@ class ClosedLoopCwfs(salobj.BaseScript, metaclass=abc.ABCMeta):
-----
**Checkpoints**
- "Detection image": If taking in-focus detection image.
- "Taking image...": If taking in-focus detection image.
- "[N/MAX_ITER]: CWFS loop starting...": Before each cwfs iteration, where
"N" is the iteration number and "MAX_ITER" is the maximum number of
iterations.
- "[N/MAX_ITER]: CWFS converged.": Once CWFS reaches convergence.
- "[N/MAX_ITER]: CWFS focus error too large.": If computed focus correction
is larger than specified threshhold.
- "[N/MAX_ITER]: CWFS applying coma and focus correction.": Just before
- "[N/MAX_ITER]: CWFS applying correction.": Just before
corrections are applied.
**Details**
This script is used to perform measurements of the wavefront error, then
propose hexapod offsets based on an input sensitivity matrix to minimize
the errors. The hexapod offsets are not applied automatically and must be
performed by the user.
propose dof offsets based on ts_ofc. The offsets are not applied
automatically and must be turned on by the user through
apply_corrections attribute.
"""

def __init__(self, index=1, add_remotes=True, descr=""):
Expand Down Expand Up @@ -196,48 +192,62 @@ def get_schema(cls) -> typing.Dict[str, typing.Any]:
type: number
default: 30.
threshold:
description: >-
Focus correction threshold. If correction is lower than this
value, stop correction loop.
type: number
default: 0.004
description: >-
DOF threshold for convergence (um). If DOF offsets are
smaller than this value, the script will stop.
type: array
items:
type: number
minimum: 0
minItems: 50
maxItems: 50
default: [0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004,
0.004, 0.004, 0.004, 0.004, 0.004]
max_iter:
description: Maximum number of iterations.
type: integer
default: 5
description: Maximum number of iterations.
type: integer
default: 5
program:
description: >-
Optional name of the program this dataset belongs to.
type: string
default: CWFS
description: >-
Optional name of the program this dataset belongs to.
type: string
default: CWFS
reason:
description: Optional reason for taking the data.
anyOf:
- type: string
- type: "null"
default: null
used_dof:
used_dofs:
oneOf:
- type: array
- type: array
items:
type: integer
minimum: 0
maximum: 49
- type: array
type: integer
minimum: 0
maximum: 49
- type: array
items:
type: string
enum: [
"M2 dz", "M2 dx", "M2 dy", "M2 rx", "M2 ry",
"Cam dz", "Cam dx", "Cam dy", "Cam rx", "Cam ry",
"M1M3 B1", "M1M3 B2", "M1M3 B3", "M1M3 B4", "M1M3 B5",
"M1M3 B6", "M1M3 B7", "M1M3 B8", "M1M3 B9", "M1M3 B10",
"M1M3 B11", "M1M3 B12", "M1M3 B13", "M1M3 B14", "M1M3 B15",
"M1M3 B16", "M1M3 B17", "M1M3 B18", "M1M3 B19", "M1M3 B20",
"M2 B1", "M2 B2", "M2 B3", "M2 B4", "M2 B5",
"M2 B6", "M2 B7", "M2 B8", "M2 B9", "M2 B10",
"M2 B11", "M2 B12", "M2 B13", "M2 B14", "M2 B15",
"M2 B16", "M2 B17", "M2 B18", "M2 B19", "M2 B20"
]
type: string
enum: [
"M2_dz", "M2_dx", "M2_dy", "M2_rx", "M2_ry",
"Cam_dz", "Cam_dx", "Cam_dy", "Cam_rx", "Cam_ry",
"M1M3_B1", "M1M3_B2", "M1M3_B3", "M1M3_B4", "M1M3_B5",
"M1M3_B6", "M1M3_B7", "M1M3_B8", "M1M3_B9", "M1M3_B10",
"M1M3_B11", "M1M3_B12", "M1M3_B13", "M1M3_B14", "M1M3_B15",
"M1M3_B16", "M1M3_B17", "M1M3_B18", "M1M3_B19", "M1M3_B20",
"M2_B1", "M2_B2", "M2_B3", "M2_B4", "M2_B5",
"M2_B6", "M2_B7", "M2_B8", "M2_B9", "M2_B10",
"M2_B11", "M2_B12", "M2_B13", "M2_B14", "M2_B15",
"M2_B16", "M2_B17", "M2_B18", "M2_B19", "M2_B20"
]
default: [1, 2, 3, 4, 5]
apply_corrections:
description: >-
Expand Down Expand Up @@ -270,11 +280,11 @@ async def configure(self, config: types.SimpleNamespace) -> None:

self.program = config.program

selected_dof = config.used_dof
if isinstance(selected_dof[0], str):
selected_dof = [getattr(DOFComponent, dof) for dof in selected_dof]
self.used_dof = np.zeros(50)
self.used_dof[selected_dof] = 1
selected_dofs = config.used_dofs
if isinstance(selected_dofs[0], str):
selected_dofs = [getattr(DOFComponent, dof) for dof in selected_dofs]
self.used_dofs = np.zeros(50)
self.used_dofs[selected_dofs] = 1

self.apply_corrections = config.apply_corrections

Expand Down Expand Up @@ -327,26 +337,27 @@ async def arun(self, checkpoint: bool = False) -> None:
f"[{i + 1}/{self.max_iter}]: CWFS loop starting..."
)

await self.checkpoint(f"[{i + 1}/{self.max_iter}]: Taking image...")

# Setting visit_id's to none so run_cwfs will take a new dataset.
image = await self.lsstcam.take_acq(
self.exposure_time,
group_id=self.group_id,
reason="INFOCUS_image"
+ ("" if self.reason is None else f"_{self.reason}"),
program=self.program,
sensors=self.sensors,
filter=self.filter,
grating=self.grating,
)
visit_id = int(image[0])

self.mtaos.rem.mtaos.cmd_runWEP.set_start(visitId=visit_id)
wavefront_error = await self.mtaos.rem.mtaos.evt_wavefrontError.next(
flush=False, timeout=self.timeout_std
flush=False, timeout=STD_TIMEOUT
)

self.log.info(
f"Wavefront error zernike coefficients: {wavefront_error} in nm."
f"Wavefront error zernike coefficients: {wavefront_error} in um."
)

config = {
Expand All @@ -361,23 +372,27 @@ async def arun(self, checkpoint: bool = False) -> None:
}
config_yaml = yaml.dump(config, default_flow_style=False)

self.mtaos.rem.mtaos.cmd_runOFC.set_start(config=config_yaml)
await self.mtaos.rem.mtaos.cmd_runOFC.set_start(config=config_yaml)
dof_offset = await self.mtaos.rem.mtaos.evt_degreeOfFreedom.next(
flush=False, timeout=self.timeout_std
flush=False, timeout=STD_TIMEOUT
)

if abs(dof_offset) < self.threshold:
self.log.info(
f"OFC offsets are inside tolerance level \
({self.threshold:0.3f}). "
)
if all(abs(dof_offset) < self.threshold):
self.log.info(f"OFC offsets are inside tolerance ({self.threshold}). ")
if checkpoint:
await self.checkpoint(f"[{i + 1}/{self.max_iter}]: CWFS converged.")

self.log.info("closed_loop_cwfs script completed successfully!\n")
self.log.info("Closed Loop CWFS loop completed successfully!\n")
return
elif self.apply_corrections:
self.mtaos.rem.mtaos.cmd_issueCorrection.start()
self.log.info("Applying corrections...")

if checkpoint:
await self.checkpoint(
f"[{i + 1}/{self.max_iter}]: Applying correction."
)

await self.mtaos.rem.mtaos.cmd_issueCorrection.start()
else:
pass

Expand Down
20 changes: 8 additions & 12 deletions tests/test_maintel_closed_loop_cwfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@

import asyncio
import random
import types
import unittest

import numpy as np
from lsst.ts import standardscripts
from lsst.ts.salobj import State
from lsst.ts.standardscripts.maintel import ClosedLoopCwfs

random.seed(47) # for set_random_lsst_dds_partition_prefix
Expand Down Expand Up @@ -63,9 +61,6 @@ async def basic_make_script(self, index):
self.corrections = np.zeros(50)
return (self.script,)

async def get_summary_state(self, *args, **kwargs):
return types.SimpleNamespace(summaryState=State.ENABLED)

async def return_zernikes(self, *args, **kwargs):
return np.random.rand(19)

Expand All @@ -79,12 +74,10 @@ async def apply_offsets(self, *args, **kwags):
async def get_offsets(self, *args, **kwags):
# return corrections to be non zero the first time this is called
await asyncio.sleep(0.5)
offsets = np.zeros(50)
self.corrections = np.zeros(50)

if any(self.state_0):
offsets[:5] -= 0.5

return offsets
self.corrections[:5] -= 0.5

async def test_configure(self):
# Try configure with minimum set of parameters declared
Expand All @@ -94,7 +87,7 @@ async def test_configure(self):
exposure_time = 30
filter = "r"
used_dofs = ["M2_dz", "M2_dx", "M2_dy", "M2_rx", "M2_ry"]
threshold = 1e-3
threshold = [0.005] * 50
apply_corrections = True

await self.configure_script(
Expand All @@ -111,7 +104,10 @@ async def test_configure(self):
assert self.script.rotation_angle == rotation_angle
assert self.script.exposure_time == exposure_time
assert self.script.filter == filter
assert self.script.used_dofs == [0, 1, 2, 3, 4]

configured_dofs = np.zeros(50)
configured_dofs[:5] += 1
assert all(self.script.used_dofs == configured_dofs)
assert self.script.threshold == threshold
assert self.script.apply_corrections == apply_corrections

Expand All @@ -127,7 +123,7 @@ async def test_run(self):
# Run the script
await self.run_script()

assert self.state_0 == [0, 0, 0, 0, 0]
assert all(self.state_0 == np.zeros(50))

async def test_executable(self):
scripts_dir = standardscripts.get_scripts_dir()
Expand Down

0 comments on commit 63291fb

Please sign in to comment.