Skip to content

Commit

Permalink
colibri (skyportal#5214)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcoughlin authored Aug 28, 2024
1 parent a550a45 commit d5d11f6
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 4 deletions.
30 changes: 30 additions & 0 deletions alembic/versions/fcc48485cb52_colibri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Colibri migration
Revision ID: fcc48485cb52
Revises: b0f0b2a30357
Create Date: 2024-08-28 01:12:28.594050
"""

from alembic import op

# revision identifiers, used by Alembic.
revision = 'fcc48485cb52'
down_revision = 'b0f0b2a30357'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.get_context().autocommit_block():
op.execute(
"ALTER TYPE followup_apis ADD VALUE IF NOT EXISTS 'COLIBRIAPI' AFTER 'ATLASAPI'"
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
5 changes: 5 additions & 0 deletions config.yaml.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ app:
host: herculesii.astro.berkeley.edu
port:

colibri:
protocol: http
host:
port:

treasuremap_endpoint: https://treasuremap.space

tns:
Expand Down
22 changes: 22 additions & 0 deletions data/db_demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ telescope:
nickname: KAIT
skycam_link: null
=id: KAIT
- diameter: 1.3
elevation: 2800
lat: 31.0441
lon: 115.4637
name: COLIBRI Telescope
nickname: COLIBRI
skycam_link: null
=id: COLIBRI
- diameter: 2.1
elevation: 2096.0
lat: 31.9599
Expand Down Expand Up @@ -524,6 +532,13 @@ instrument:
filters: ['bessellux', 'sdssg', 'sdssr', 'sdssi', 'sdssz', 'bessellb', 'bessellv', 'bessellr', 'besselli']
api_classname: KAITAPI
=id: KAIT
- band: optical
name: COLIBRI
telescope_id: =COLIBRI
type: imager
filters: ['sdssg', 'sdssr', 'sdssi', 'sdssz']
api_classname: COLIBRIAPI
=id: COLIBRI
- name: SEDMv2
type: imaging spectrograph
band: V
Expand Down Expand Up @@ -766,6 +781,13 @@ allocation:
hours_allocated: 100
group_id: =program_A
instrument_id: =KAIT
- pi: Michael Coughlin
proposal_id: COLIBRI-001
start_date: "3020-02-12T00:00:00"
end_date: "3020-07-12T00:00:00"
hours_allocated: 100
group_id: =program_A
instrument_id: =COLIBRI
- pi: Michael Coughlin
proposal_id: T80CAM-001
start_date: "3020-02-12T00:00:00"
Expand Down
1 change: 1 addition & 0 deletions skyportal/facility_apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

# Instrument Specific APIs
from .atlas import ATLASAPI
from .colibri import COLIBRIAPI
from .growth_india import GROWTHINDIAMMAAPI
from .kait import KAITAPI
from .sedm import SEDMAPI, SEDMListener
Expand Down
215 changes: 215 additions & 0 deletions skyportal/facility_apis/colibri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
from datetime import datetime
import requests
from requests.auth import HTTPBasicAuth

from astropy.coordinates import SkyCoord
from astropy import units as u

from . import FollowUpAPI
from baselayer.app.env import load_env
from baselayer.app.flow import Flow

from ..utils import http

env, cfg = load_env()


if cfg.get('app.colibri.port') is None:
COLIBRI_URL = f"{cfg['app.colibri.protocol']}://{cfg['app.colibri.host']}"
else:
COLIBRI_URL = f"{cfg['app.colibri.protocol']}://{cfg['app.colibri.host']}:{cfg['app.colibri.port']}"


class COLIBRIRequest:

"""A dictionary structure for COLIBRI ToO requests."""

def _build_payload(self, request):
"""Payload json for COLIBRI queue requests.
Parameters
----------
request: skyportal.models.FollowupRequest
The request to add to the queue and the SkyPortal database.
Returns
----------
payload: json
payload for requests.
"""

for filt in request.payload["observation_choices"]:
if filt not in ["U", "g", "r", "i", "z", "B", "V", "R", "I"]:
raise ValueError(f"Improper observation_choice {filt}")

c = SkyCoord(ra=request.obj.ra * u.degree, dec=request.obj.dec * u.degree)
ra_str = c.ra.to_string(unit='hour', sep=':', precision=2, pad=True)
dec_str = c.dec.to_string(unit='degree', sep=':', precision=2, pad=True)

# The target of the observation
target = {
'name': request.obj.id,
'alpha': ra_str,
'delta': dec_str,
'equinox': '2000',
'uncertainty': '1.0as',
'priority': int(request.payload["priority"]),
'filters': "".join(request.payload["observation_choices"]),
'type': 'transient',
'projectidentifier': str(request.allocation.id),
'identifier': str(request.id),
'enabled': "true",
'eventtimestamp': request.payload["start_date"],
'alerttimestamp': request.payload["start_date"],
}

return target


class COLIBRIAPI(FollowUpAPI):

"""An interface to COLIBRI operations."""

# subclasses *must* implement the method below
@staticmethod
def submit(request, session, **kwargs):
"""Submit a follow-up request to COLIBRI.
Parameters
----------
request: skyportal.models.FollowupRequest
The request to add to the queue and the SkyPortal database.
session: sqlalchemy.Session
Database session for this transaction
"""

from ..models import FacilityTransaction

req = COLIBRIRequest()
requestgroup = req._build_payload(request)

altdata = request.allocation.altdata
if not altdata:
raise ValueError('Missing allocation information.')

requestpath = f"{COLIBRI_URL}/cgi-bin/internal/process_colibri_ztf_request.py"

r = requests.post(
requestpath,
auth=HTTPBasicAuth(altdata['username'], altdata['password']),
json=requestgroup,
)
r.raise_for_status()

if r.status_code == 200:
request.status = 'submitted'
else:
request.status = f'rejected: {r.content}'

transaction = FacilityTransaction(
request=http.serialize_requests_request(r.request),
response=http.serialize_requests_response(r),
followup_request=request,
initiator_id=request.last_modified_by_id,
)

session.add(transaction)

if kwargs.get('refresh_source', False):
flow = Flow()
flow.push(
'*',
'skyportal/REFRESH_SOURCE',
payload={'obj_key': request.obj.internal_key},
)
if kwargs.get('refresh_requests', False):
flow = Flow()
flow.push(
request.last_modified_by_id,
'skyportal/REFRESH_FOLLOWUP_REQUESTS',
)

@staticmethod
def delete(request, session, **kwargs):
from ..models import FollowupRequest

last_modified_by_id = request.last_modified_by_id
obj_internal_key = request.obj.internal_key

if len(request.transactions) == 0:
session.query(FollowupRequest).filter(
FollowupRequest.id == request.id
).delete()
session.commit()
else:
raise NotImplementedError(
"Can't delete requests already submitted successfully to COLIBRI."
)

if kwargs.get('refresh_source', False):
flow = Flow()
flow.push(
'*',
'skyportal/REFRESH_SOURCE',
payload={'obj_key': obj_internal_key},
)
if kwargs.get('refresh_requests', False):
flow = Flow()
flow.push(
last_modified_by_id,
'skyportal/REFRESH_FOLLOWUP_REQUESTS',
)

form_json_schema = {
"type": "object",
"properties": {
"observation_choices": {
"type": "array",
"title": "Desired Observations",
"items": {
"type": "string",
"enum": ["U", "g", "r", "i", "z", "B", "V", "R", "I"],
},
"uniqueItems": True,
"minItems": 1,
},
"priority": {
"type": "number",
"default": 1.0,
"minimum": 1,
"maximum": 5,
"title": "Priority",
},
"start_date": {
"type": "string",
"default": datetime.utcnow().isoformat(),
"title": "Start Date (UT)",
},
},
"required": [
"observation_choices",
"priority",
"start_date",
],
}

form_json_schema_altdata = {
"type": "object",
"properties": {
"username": {
"type": "string",
"title": "Username",
},
"password": {
"type": "string",
"title": "Password",
},
},
}

ui_json_schema = {"observation_choices": {"ui:widget": "checkboxes"}}

alias_lookup = {
'observation_choices': "Request",
}
4 changes: 0 additions & 4 deletions skyportal/facility_apis/lt.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,11 @@ def submit(request, session, **kwargs):
},
"start_date": {
"type": "string",
"format": "date",
"default": datetime.utcnow().isoformat(),
"title": "Start Date (UT)",
},
"end_date": {
"type": "string",
"format": "date",
"title": "End Date (UT)",
"default": (datetime.utcnow() + timedelta(days=7)).isoformat(),
},
Expand Down Expand Up @@ -820,13 +818,11 @@ def submit(request, session, **kwargs):
},
"start_date": {
"type": "string",
"format": "date",
"default": datetime.utcnow().isoformat(),
"title": "Start Date (UT)",
},
"end_date": {
"type": "string",
"format": "date",
"title": "End Date (UT)",
"default": (datetime.utcnow() + timedelta(days=7)).isoformat(),
},
Expand Down

0 comments on commit d5d11f6

Please sign in to comment.