Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added view to enable target selection for manual observing runs #840

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs/observing/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ allow your TOM to submit observation requests to observatories.
:doc:`Facility Modules <../api/tom_observations/facilities>` - Take a look at the supported facilities.

:doc:`Observation Views <../api/tom_observations/views>` - Familiarize yourself with the available Observation Views.

:doc:`Selecting Targets <selecting_targets_for_facility>` - Display a selection of targets for a specific observing facility.
252 changes: 252 additions & 0 deletions docs/observing/selecting_targets_for_facility.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
Selecting Targets for an Observing Facility
===========================================

During observing runs, particularly at manually- or remotely-operated telescope
facilities, it can often be very useful to display a selection of targets to be
observed on a particular night. This needs to take into account target visibiliy from
the telescope site, as well as any prioritization of targets that the team have made.

TOMs provide support for this through the Target Selection option under the Target menu
in the main navigation bar.

.. image:: target_selection_menu_option.png
:alt: Menu option for Target Selection view

Observers can select the telescope facility that they are observing from using the form
provided, indicating the date of the observing run. The selected targets can be draw
from a predefined Target Grouping, but if none is specified then targets will be drawn
from all of the targets that the user has permission to see.

The TOM will evaluate the visibility of the selected targets for the telescope on the
night in question, and the resulting table will include all objects with a minimum
airmass less than 2.0.

.. image:: target_selection_table_default.png
:alt: Default table output for target selection

Customizing the Selected Targets Table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, this table will include the essential parameters necessary to point a
telescope at the target, but it can be easily extended to add further information.

The columns of the table can be configured by editing the TOM's ``settings.py`` file.
Additional parameters can be defined for each target by adding dictionary definitions
to the ``EXTRA_FIELDS`` list, as shown in the example below:

.. code-block:: python

# settings.py
EXTRA_FIELDS = [
{'name': 'Mag_now', 'type': 'number'},
{'name': 'Priority1', 'type': 'number'},
{'name': 'Priority2', 'type': 'string'}
]
SELECTION_EXTRA_FIELDS = [
'Mag_now',
'Priority1',
'Priority2',
]

In this example, we have added ``EXTRA_FIELDS`` named ``Mag_now``, ``Priority1``
and ``Priority2``, which the user can set either by editing each Target's parameters
directly, or programmatically. Having done so, we can add those ``EXTRA_FIELDS`` to
the target selection table by adding the parameter names to the list of ``SELECTION_EXTRA_FIELDS``.
This produces the table displayed below.

.. image:: target_selection_table_extra_fields.png
:alt: Target Selection table with additional columns added


Adding An Observing Facility to the Target Selection Form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The Target selection form inherits all of the TOM's built-in observing facility classes.
This can be extended to include additional telescopes, including those that are
operated manually, just by declaring a new telescope class.

In the top level of your TOM's code directory, add a new directory called ``facilities``:

.. code:: python

cd mytom/
mkdir facilities

::

├── facilities/
├── data
├── db.sqlite3
├── manage.py
├── mytom
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── static
├── templates
└── tmp

We need to add an ``__init__.py`` file to this sub-directory, to let Python know that
this is an application. This file should be empty, so we just create it:

.. code:: python

touch facilities/__init__.py

Now we can create the new telescope facility class within this ``facilities`` directory.
The easiest way to do this is to download a copy of the `example facility <https://github.com/TOMToolkit/tom_base/blob/dev/tom_observations/facilities/manual.py>`__
provided in the TOM Toolkit's repository. You can rename this file to distinguish it
from other facilities. In this example, we will add the El Leoncito Astronomical Complex,
also known as CASLEO:

::

├── facilities/
│ ├── __init__.py
│ ├── casleo.py

The new telescope class file can now be updated to provide the essential information
about the site. The code block below highlights the sections of the file that need to be
updated by comparing the default with the customized example.

First we need to declare the exact location of the observatory site. Note that the sites
dictionary can accept multiple dictionaries, each describing a different site. This is how
the TOM handles observatories that have multiple sites, such as the `LCO network <https://github.com/TOMToolkit/tom_base/blob/dev/tom_observations/facilities/lco.py>`__.

.. code:: python

# casleo.py

# DEFAULT:
try:
EXAMPLE_MANUAL_SETTINGS = settings.FACILITIES['EXAMPLE_MANUAL']
except KeyError:
EXAMPLE_MANUAL_SETTINGS = {
}

EXAMPLE_SITES = {
'Example Manual Facility': {
'sitecode': 'Example',
'latitude': 0.0,
'longitude': 0.0,
'elevation': 0.0
},
}
EXAMPLE_TERMINAL_OBSERVING_STATES = ['Completed']

# UPDATED TO:
try:
CASLEO_SETTINGS = settings.FACILITIES['CASLEO']
except KeyError:
CASLEO_SETTINGS = {
}

CASLEO_SITES = {
'El Leoncito': {
'sitecode': 'CASLEO',
'latitude': -31.7986,
'longitude': -69.2956,
'elevation': 2483.0
},
}
TERMINAL_OBSERVING_STATES = ['Completed']

Then we give the facility class a distinctive name:

.. code:: python

# casleo.py

# DEFAULT:
class ExampleManualFacility(BaseManualObservationFacility):
"""
"""

name = 'Example'
observation_types = [('OBSERVATION', 'Manual Observation')]

# UPDATED TO:
class CASLEOFacility(BaseManualObservationFacility):
"""
"""

name = 'El Leoncito'
observation_types = [('OBSERVATION', 'Manual Observation')]

We also need to update the reference to the list of possible end states of observing requests.
This list can be expanded for telescopes that are programmatically accessible, but it can be left
with the default list for manual facilities.

.. code:: python

# casleo.py

# DEFAULT:
def get_terminal_observing_states(self):
"""
Returns the states for which an observation is not expected
to change.
"""
return EXAMPLE_TERMINAL_OBSERVING_STATES


# UPDATED TO:
def get_terminal_observing_states(self):
"""
Returns the states for which an observation is not expected
to change.
"""
return TERMINAL_OBSERVING_STATES


Lastly, we need to make sure that the method to fetch the information on observing sites refers to the
list of dictionaries that we specified above.

.. code:: python

# casleo.py

# DEFAULT:
def get_observing_sites(self):
"""
Return a list of dictionaries that contain the information
necessary to be used in the planning (visibility) tool. The
list should contain dictionaries each that contain sitecode,
latitude, longitude and elevation.
"""
return EXAMPLE_SITES


# UPDATED TO:
def get_observing_sites(self):
"""
Return a list of dictionaries that contain the information
necessary to be used in the planning (visibility) tool. The
list should contain dictionaries each that contain sitecode,
latitude, longitude and elevation.
"""
return CASLEO_SITES


The new facility is now ready. To make sure that the TOM includes it,
we simply need to add it to our TOM's list of facilities in the ``settings.py`` file:


.. code-block:: python

# settings.py
TOM_FACILITY_CLASSES = [
'tom_observations.facilities.lco.LCOFacility',
'tom_observations.facilities.gemini.GEMFacility',
'tom_observations.facilities.soar.SOARFacility',
'facilities.casleo.CASLEOFacility',
]


Returning to the target selection form, the new observatory now appears as
an option in the Observatory pulldown menu.


.. image:: target_selection_table_new_facility.png
:alt: Target selection table with new telescope facility added
Binary file added docs/observing/target_selection_menu_option.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/observing/target_selection_table_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tom_common/templates/tom_common/navbar_content.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'targets:list' %}">Targets</a>
<a class="dropdown-item" href="{% url 'targets:targetgrouping' %}">Target Grouping</a>
<a class="dropdown-item" href="{% url 'targets:target-selection' %}">Target Selection</a>
</div>
</li>
<li class="nav-item {% if request.resolver_match.namespace == 'alerts' %}active{% endif %}">
Expand Down
1 change: 1 addition & 0 deletions tom_setup/templates/tom_setup/settings.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ HARVESTERS = {
# {'name': 'dicovery_date', 'type': 'datetime'}
# ]
EXTRA_FIELDS = []
SELECTION_EXTRA_FIELDS = []

# Authentication strategy can either be LOCKED (required login for all views)
# or READ_ONLY (read only access to views)
Expand Down
18 changes: 17 additions & 1 deletion tom_targets/forms.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from django import forms
from django.conf import settings
from astropy.coordinates import Angle
from astropy import units as u
from django.forms import ValidationError, inlineformset_factory
from django.conf import settings
from django.contrib.auth.models import Group
from guardian.shortcuts import assign_perm, get_groups_with_perms, remove_perm
from tom_observations import facility

from tom_dataproducts.sharing import get_sharing_destination_options
from .models import (
Expand Down Expand Up @@ -203,3 +204,18 @@ class TargetListShareForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['share_destination'].choices = get_sharing_destination_options()


class TargetSelectionForm(forms.Form):
"""
Form for selecting the targets from a pre-existing TargetList
"""
target_list = forms.ModelChoiceField(
TargetList.objects.all(),
required=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not requiring a TargetList means that if the user doesn't select anything, then the code will try to do this for EVERY target. This will likely completely crash any TOM with a large DB as it tries to get visibilities for a few million objects.

facilities = [(x, x) for x in facility.get_service_classes()]
observatory = forms.ChoiceField(required=True, choices=facilities)
date = forms.DateTimeField(required=True, help_text='YYYY-MM-DD')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forms.DateField(required=True, initial=datetime.date.today, widget=forms.TextInput(attrs={'type': 'date'})) Is a much cleaner way to do what you want here.

(Be sure to import datetime above)


def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
46 changes: 46 additions & 0 deletions tom_targets/templates/tom_targets/target_facility_selection.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% extends 'tom_common/base.html' %}
{% load bootstrap4 targets_extras dataproduct_extras static %}
{% block title %}Target Selection{% endblock %}
{% block additional_css %}
<link rel="stylesheet" href="{% static 'tom_targets/css/main.css' %}">
{% endblock %}
{% block content %}
<h3>Select Targets for an Observing Facility</h3>
<div class="row">
<form method="post" class="form" id='target-selection-form'>
{% csrf_token %}
<div class="form-row" style="padding-inline:1rem">
<div class="col-sm-3">
{% bootstrap_field form.observatory %}
</div>
<div class="col-sm-5">
{% bootstrap_field form.date %}
</div>
<div class="col-sm-3">
{% bootstrap_field form.target_list %}
</div>
<div class="col-sm-1">
<input type="submit" class="btn btn-primary" formaction="{% url 'targets:target-selection' %}" id="submit_target_selection" value="Submit" name="target_selection_form" style="position:absolute; bottom:1rem"">
</div>
</div>
</form>
</div>
<div class="row">
{% if observable_targets %}
<table class="table table-striped">
<tr>
{% for col in table_columns %}
<th>{{col}}</th>
{% endfor %}
</tr>
{% for entry in observable_targets %}
<tr>
{% for item in entry %}
<td>{{item}}</td>
{% endfor %}
</tr>
{% endfor %}
{% endif %}
</table>
</div>
{% endblock %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add empty line here.

2 changes: 2 additions & 0 deletions tom_targets/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .views import TargetDeleteView, TargetListView, TargetImportView, TargetExportView, TargetShareView
from .views import TargetGroupingView, TargetGroupingDeleteView, TargetGroupingCreateView, TargetAddRemoveGroupingView
from .views import TargetGroupingShareView, TargetHermesPreloadView, TargetGroupingHermesPreloadView
from .views import TargetFacilitySelectionView

from .api_views import TargetViewSet, TargetExtraViewSet, TargetNameViewSet, TargetListViewSet
from tom_common.api_router import SharedAPIRootRouter
Expand Down Expand Up @@ -34,5 +35,6 @@
path('targetgrouping/<int:pk>/share/', TargetGroupingShareView.as_view(), name='share-group'),
path('targetgrouping/<int:pk>/hermes-preload/', TargetGroupingHermesPreloadView.as_view(),
name='group-hermes-preload'),
path('targetselection/', TargetFacilitySelectionView.as_view(), name='target-selection'),

]
Loading
Loading