Note

This page is automatically generated by exporting this example notebook.

Example Notebook

The code below represents an example user-define operation that can be converted into a SAL Script, used to illustrate the process. It is assumed that the user understands and has tested the code they wish to convert to a SAL Script.

In this example we will perform a dithering pattern on the sky with the Auxiliary Telescope and take a sequence of images at each position.

We will use functionality from the two main observatory control classes for the Auxiliary Telescope; ATCS and LATISS. More information about these can be found in the ts_observatory_control user guide.

import asyncio
import logging

import numpy as np
from matplotlib import pyplot as plt

from lsst.ts import salobj

from lsst.ts.observatory.control.auxtel.atcs import ATCS
from lsst.ts.observatory.control.auxtel.latiss import LATISS
from lsst.ts.observatory.control.utils.enums import RotType

Setting up logging

When running on a notebook you may be interested in getting logging feedback. To enable this you may want to setup the Python logging facility.

This next cell will setup the basic log configuration in debug mode. If you find this too verbose and want to change the level, you can replace logging.DEBUG with logging.INFO, logging.WARNING, logging.ERROR or skip the next cell altogether.

logging.basicConfig(format="%(name)s:%(message)s", level=logging.DEBUG)

Matplotlib can be chatty so, better decrease its log level.

logging.getLogger("matplotlib").setLevel(logging.WARNING)

Setup control classes

The first step in interacting with the system is to setup the SalObj library and the control classes.

This is done by creating a salobj.Domain, an object to handle the DDS communication and later passing it to the control classes.

domain = salobj.Domain()
atcs = ATCS(domain)
latiss = LATISS(domain)

Reducing salobj.Remote internal logging.

The internal salobj.Remote classes can get very chatty due to the incomming traffic from the CSCs. You can reduce this by using a method provided by the control classes.

atcs.set_rem_loglevel(logging.ERROR)
latiss.set_rem_loglevel(logging.ERROR)

Wait for salobj to finish setup DDS communication.

This is a background task that we need to await before we can communicate with the components.

The control software performs numerous tasks asynchronously, with Python’s asyncio library. Using an await statement ensures that the command will not return until it’s completed. For more information see documentation in the asyncio library.

await asyncio.gather(atcs.start_task, latiss.start_task)

Executing Operations

From now on we are ready to interact with the system.

We are now going to write down the loop that performs the dithering and data taking.

I will assume you had some time to think about the problem and exercice it enough to get confortable with parameterizing it and so on.

The idea is to develop a procedure that will do the following:

  1. Slew to a target that is defined by a name that can be resolved by simbad, and a rotator setup.
  2. Given a pre-defined grid of x/y offsets from the original position;
    1. Offset the telescope to each,
    2. Take a set of pre-defined observations.

We start by defining the parameters in the cells bellow.

Target definition

The next cell defines the target to slew to and the rotator value/type.

target_name = "HD 164461"
rot_value = 0.
rot_type = RotType.PhysicalSky

Define offset grid

n_grid = 11  # how many visits in the grid
grid_x = (np.random.rand(n_grid)-0.5)*120.  # offset in image coordinate x-axis (in arcsec)
grid_y = (np.random.rand(n_grid)-0.5)*120.  # offset in image coordinate y-axis (in arcsec)

We are in a Jupyter notebook so we might as well plot the grid generated above.

plt.plot(grid_x, grid_y, '.:')

plt.xlabel("x-offset in arcsec")
plt.ylabel("y-offset in arcsec")

Define observations setup

exptime = [5., 10., 20.]  # list of exposure times in seconds
obs_filter = ["RG610", "RG610", "RG610"]  # list of filters
obs_grating = ["empty_1", "ronchi90lpmm", "ronchi170lpmm"]  # list of gratings

Run observation sequence

Now we have the parameters defined we can run a loop that will execute the dithering and observing sequence.

await atcs.slew_object(name=target_name, rot=rot_value, rot_type=rot_type)

Note on the operation bellow

As of ts_observatory_control v0.7, there is a background race condition between changing the instrument configuration and taking an image. Once the instrument configuration changes, the ATAOS component will apply offsets to focus and telescope position to compensate for focus/image motion due to filter/grating settings. This will end up resulting in image motion, if the appropriate events are not waited on.

A fix for this issue is being worked out in DM-28530 and will soon be available. In order to keep this example simple and clear, we decided not to add the current workaround to this issue. In any case, if you plan on executing operations that involves setting the instrument configuration and taking an image with any of the LATISS.take_* commands, check with observatory personnel whether this condition was already resolved.

for xx, yy in zip(grid_x, grid_y):
    # Offset telescope
    # Use non-relative offset as they are easier to reset
    await atcs.offset_xy(x=xx, y=yy, relative=False)

    # Take data
    for etime, flt, grt in zip(exptime, obs_filter, obs_grating):
        await latiss.take_object(exptime=etime, filter=flt, grating=grt)
# Reset offset
await atcs.offset_xy(x=0., y=0., relative=False)