Minimal working setup for pyobs inside a Jupyter notebook
Introduction
This is a short introduction to using pyobs inside a Jupyter notebook. If you are already familiar with pyobs, you will find that the only big difference is the setup process and how you interact with and manage your own module. If you are new to pyobs, this notebook should give you a good first introduction for writing code which interacts with pyobs.
Setup
Preparations
The following code (and pyobs in general) depends on the asyncio
event loop running. If the following code returns False you will
need to upgrade your Jupyter version, as older versions don’t support
asyncio.
import asyncio
asyncio.get_event_loop().is_running()
Before we start with the actual setup, we have to redirect the loggin
output to stdout, so that is displayed inside the cell output. If
you want to disable error/warning messages, you can skip this step.
import logging
import sys
logging.basicConfig(stream=sys.stdout)
Credentials
This is not specific to pyobs itself, but is general advice: NEVER
commit credentials to a git repository. In the case of the normal pyobs
yaml configuration files pyobs allows other config files config
files to be imported, in which the credentials can be stored, while the
config file containing the credentials can be added to the
.gitignore file. In the case of Jupyter notebooks I recommend using
.env files, which can be simmilarly excluded from the repo by adding
them to the .gitignore file. This also allow you to share the
credentials between multiple notebooks in the same probject.
For this to work, simply add a .env file to your project root and
add the following lines:
COMM_JID = "[USERNAME]@iag50srv.astro.physik.uni-goettingen.de"
COMM_PWD = "[PASSWORD]"
You want to replace the username and password with your credentials and
if you are not at the IAG, replace the address after the @ with the
one of your institute. After adding this, the load_dotenv() below,
shoud return True
import os
from dotenv import load_dotenv
load_dotenv()
COMM_JID = os.getenv('COMM_JID')
COMM_PWD = os.getenv('COMM_PWD')
Comm
For our pyobs module to work, it needs a valid comm
module
to communicate with other modules (telescopes, cameras, etc.). In a
production environment, XMPP is used for this communication, so a
XmppComm module is created with the credentials, that where
previously loaded.
from pyobs.comm.xmpp import XmppComm
comm = XmppComm(jid=COMM_JID, password=COMM_PWD, use_tls=True)
Open Comms
Open a channel, lieutenant.
By opening the comm module, we connect it to the pyobs network, so that we can communicate with the other modules on the network.
await comm.open()
If everything works and if other modules are connected to the network, the following command should return the names of these modules:
comm.clients
Closing Comms
At the end of a session, the comm module should be closed again. This signals to the rest of the network, that the module is not longer available.
await comm.close()
Virtual File System (VFS)
If you want to work with a camera, the module also needs access to the pyobs virtual filesystem. Again if you are not at the IAG, you will need to replace the download address below, with your own address.
from pyobs.vfs import VirtualFileSystem
vfs = VirtualFileSystem(
roots={
"cache":
{
"class": "pyobs.vfs.HttpFile",
"download": "https://iag50srv.astro.physik.uni-goettingen.de/pyobs/filecache/"
}})
Usage
The module can now be used to control other modules on the network.
comm.proxy(...) needs the “username” of the module it is proxying
(in this case, the name of the telescope or camera), and returns an
async context manager rather than a usable object directly — a proxy
is always obtained as async with comm.proxy(...) as x:, resolved
fresh in whichever cell needs it, rather than held in a variable across
cells: the remote module can reconnect between cells, and a proxy
resolved just before use is guaranteed to be the current one.
Telescope
from pyobs.interfaces import ITelescope, IPointingRaDec, IPointingAltAz
TELESCOPE_NAME = "telescope"
The telescope’s orientation is published as live state rather than
fetched via a method call. wait_for_state returns the last known
value immediately if one has already arrived, otherwise it waits (up
to a timeout) for the first update.
async with comm.proxy(TELESCOPE_NAME, ITelescope) as telescope:
radec = await telescope.wait_for_state(IPointingRaDec)
altaz = await telescope.wait_for_state(IPointingAltAz)
radec, altaz
and to move it in altaz coordinates…
async with comm.proxy(TELESCOPE_NAME, ITelescope) as telescope:
await telescope.move_altaz(alt=60, az=180)
or radec coordiantes (both in degrees).
async with comm.proxy(TELESCOPE_NAME, ITelescope) as telescope:
await telescope.move_radec(ra=60, dec=25)
Camera
A camera can be used in the same way as a telescope. Here, we use a
module with the “username” "sbig6303e" as the camera. Setting the
exposure time and image type, then taking the exposure, all happen
against the same resolved proxy, so this all lives in one cell.
grab_data returns the path to the image in the virtual filesystem,
which is then supplied to the vfs module to retrieve the image.
from pyobs.interfaces import ICamera, IExposureTime, IImageType
from pyobs.utils.enums import ImageType
CAMERA_NAME = "sbig6303e"
async with comm.proxy(CAMERA_NAME, ICamera) as camera:
if isinstance(camera, IExposureTime):
await camera.set_exposure_time(2)
if isinstance(camera, IImageType):
await camera.set_image_type(ImageType.OBJECT)
image_name = await camera.grab_data(broadcast=False)
img = await vfs.read_image(image_name)
Now we can look at the header…
img.header
and at the image itself.
import matplotlib.pyplot as plt
plt.imshow(img.data, cmap="gray")
plt.show()
We can also save the image as a file.
img.writeto("image_test.fits")