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

DM-47655: Add 'Time since last image' clock #227

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,11 @@
"react-dom": "^18.2.0",
"reconnecting-websocket": "^4.4.0",
"uuid": "^9.0.1"
},
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": false
}
}
6 changes: 6 additions & 0 deletions python/lsst/ts/rubintv/background/currentpoller.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ async def process_metadata_file(
await notify_ws_clients(
Service.CAMERA, MessageType.CAMERA_METADATA, loc_cam, data
)
await notify_ws_clients(
Service.CHANNEL, MessageType.CAMERA_METADATA, loc_cam, data
)

async def sieve_out_night_reports(
self, objects: list[dict[str, str]], location: Location, camera: Camera
Expand Down Expand Up @@ -491,6 +494,9 @@ async def get_latest_data(
)
yield MessageType.CHANNEL_EVENT, event.__dict__ if event else None

metadata = await self.get_current_metadata(location.name, camera)
yield MessageType.CAMERA_METADATA, metadata

case Service.NIGHTREPORT:
night_report = await self.get_current_night_report(
location.name, camera.name
Expand Down
20 changes: 15 additions & 5 deletions python/lsst/ts/rubintv/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
from enum import Enum
from typing import Any

from lsst.ts.rubintv.config import rubintv_logger
from lsst.ts.rubintv import __version__
from lsst.ts.rubintv.config import config, rubintv_logger
from pydantic import BaseModel, ConfigDict
from pydantic.dataclasses import dataclass

from .. import __version__
from ..config import config

logger = rubintv_logger()


Expand Down Expand Up @@ -98,9 +96,15 @@ class MosaicViewMeta(BaseModel):


class ExtraButton(HasButton):
# relative link to related content
linkURL: str


class TimeSinceClock(BaseModel):
# label string to display next to clock
label: str


class Camera(HasButton):
"""Represents a camera entity, capable of handling different channels like
images or movies.
Expand Down Expand Up @@ -129,9 +133,14 @@ class Camera(HasButton):
A link to the image viewer. Defaults to an empty string.
copy_row_template : str, optional
Template string for copying a row. Defaults to an empty string.
mosaic_view_meta: list[MosaicViewMeta], optional
mosaic_view_meta : list[MosaicViewMeta], optional
List of channels and associated metadata columns for a mosaic view of
current images and plots.
extra_buttons : list[ExtraButton], optional
Any extra buttons to link to other parts of the website.
time_since_clock : TimeSinceClock
Label to display next to the time since (for e.g. last image) clock.


Methods
-------
Expand All @@ -155,6 +164,7 @@ class Camera(HasButton):
copy_row_template: str = ""
mosaic_view_meta: list[MosaicViewMeta] = []
extra_buttons: list[ExtraButton] = []
time_since_clock: TimeSinceClock | None = None

def seq_channels(self) -> list[Channel]:
return [c for c in self.channels if not c.per_day]
Expand Down
7 changes: 6 additions & 1 deletion python/lsst/ts/rubintv/models/models_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ locations:
profile_name: rubin-rubintv-data-summit
bucket_name: rubin-rubintv-data-summit
camera_groups:
Test: [ comcam ]
Test: [ comcam, comcam_sim ]

- name: slac
title: SLAC
Expand Down Expand Up @@ -258,6 +258,9 @@ cameras:
color: "#83eebe"
icon: movies

time_since_clock:
label: Time since last image

extra_buttons:
- title: Movies View
name: moviesView
Expand Down Expand Up @@ -322,6 +325,8 @@ cameras:
online: True
logo: Donut_watching_grey.jpg
text_colour: "#fff"
time_since_clock:
label: Time since last refocus
channels:
- name: fp_donut_gallery
title: Focal Plane Donut Gallery
Expand Down
3 changes: 1 addition & 2 deletions python/lsst/ts/rubintv/templates/_camera.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
window.APP_DATA.baseUrl = "{{ home_url }}"
window.APP_DATA.camera = {{ camera|tojson }}
window.APP_DATA.date = "{{ date }}"
window.APP_DATA.historicalBusy = {{ historical_busy|tojson if historical_busy else "null" }}
</script>
<div id="historicalbusy" data-historicalbusy="{{ historical_busy }}">
</div>
{% endblock further %}
2 changes: 1 addition & 1 deletion python/lsst/ts/rubintv/templates/_layout.jinja
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" data-version="{{ version }}" data-locationname="{{ location.name if location }}">
<html lang="en" data-version="{{ version }}">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
Expand Down
3 changes: 0 additions & 3 deletions python/lsst/ts/rubintv/templates/admin.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
{% endblock breadcrumb %}

{% block content %}
<div id="historicalbusy" data-historicalbusy="False"></div>

<form action="reload_historical" method="POST" id="historicalReset">
<button>Reload All Historical Data</button>
</form>

{% endblock content %}
{% block footer_scripts %}
<script src="{{ url_for('static', path='assets/admin.js') }}"></script>
Expand Down
2 changes: 1 addition & 1 deletion python/lsst/ts/rubintv/templates/current_event.jinja
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "single_event.jinja" %}

{% block undertitle %}
Current
Latest
{% endblock undertitle %}

{% block further %}
Expand Down
1 change: 1 addition & 0 deletions python/lsst/ts/rubintv/templates/single_event.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
</h2>
<nav id="prev-next-nav">
</nav>
<div id="time-since-clock"></div>
</div>
{% set img_url = url_for('event_image',
location_name=location.name,
Expand Down
29 changes: 15 additions & 14 deletions src/js/components/Banner.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import React from 'react'
import React from "react"

export default function Banner ({ siteLocation, locationName, camera }) {
export default function Banner({ siteLocation, locationName, camera }) {
const camName = camera.name
let banner = ''
let cls = 'banner-text'
if (camName === 'comcam_sim' || camName === 'comcam') {
if (locationName === 'slac') {
banner = 'USDF Nightly Validation Processing'
cls += ' slac'
let banner = ""
let cls = "banner-text"
if (camName === "comcam_sim" || camName === "comcam") {
if (locationName === "slac") {
banner = "USDF Nightly Validation Processing"
cls += " slac"
} else {
banner = 'Summit Quicklook Processing'
cls += ' summit'
banner = "Summit Quicklook Processing"
cls += " summit"
}
}
return (
banner &&
<div className='banner-wrap'>
<h2 className={ cls }>{ banner }</h2>
</div>
banner && (
<div className="banner-wrap">
<h2 className={cls}>{banner}</h2>
</div>
)
)
}
111 changes: 102 additions & 9 deletions src/js/components/Clock.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect } from "react"

export default function Clock () {
export default function Clock() {
const [time, setTime] = useState(new Date())

useEffect(() => {
Expand All @@ -16,21 +16,114 @@ export default function Clock () {
const hoursMins = getHoursAndMins(time)
const secs = padZero(time.getUTCSeconds())
return (
<div id='clock'>
<div className='clockWrap'>
<span className='hours-mins'>{hoursMins}</span>
<span className='secs'>{secs}</span>
<div className="clock">
<div>
<span className="hours-mins">{hoursMins}</span>
<span className="secs">{secs}</span>
</div>
</div>
)
}

function getHoursAndMins (time) {
function getHoursAndMins(time) {
const h = padZero(time.getUTCHours())
const m = padZero(time.getUTCMinutes())
return h + ':' + m
return h + ":" + m
}

function padZero (num) {
function padZero(num) {
return (num + 100).toString().slice(-2)
}

export function TimeSinceLastImageClock(props) {
const { metadata: propsMeta, camera } = props

const [isOnline, setIsOnline] = useState(true)
const [time, setTime] = useState(Date.now())
const [metadata, setMetadata] = useState(propsMeta)

useEffect(() => {
const timerId = setInterval(() => {
setTime(Date.now())
}, 1000)

function handleWSStateChangeEvent(event) {
const { online } = event.detail
setIsOnline(online)
}
window.addEventListener("ws_status_change", handleWSStateChangeEvent)

function handleChannelMetadata(event) {
const { data, dataType } = event.detail
if (dataType === "metadata") {
setMetadata(data)
}
}
window.addEventListener("channel", handleChannelMetadata)

return () => {
clearInterval(timerId)
window.removeEventListener("ws_status_change")
window.removeEventListener("channel")
}
}, [])

let row = {}
if (metadata) {
const lastSeq = Object.entries(metadata)
.map(([seq]) => parseInt(seq))
.pop()
row = metadata[lastSeq]
}
const toSum = ["Date begin", "Exposure time"]
let error, timeElapsed
if (
!toSum.reduce((acc, col) => Object.keys(row).includes(col) && acc, true)
) {
console.log(`Can't make Time Since Last Image with:`, row)
error = "Can't ascertain..."
} else {
let UTCDateString = row["Date begin"]
if (!UTCDateString.endsWith("Z")) {
UTCDateString += "Z"
}
const startTime = Date.parse(row["Date begin"])
const exposureTime = row["Exposure time"] * 1000
const endTime = startTime + exposureTime
timeElapsed = time - endTime
}
const className = ["clock time-since-clock", isOnline ? "" : "offline"].join(
" "
)
return (
<div className={className}>
<div>
{isOnline && (
<p>
<span className="label">{camera.time_since_clock.label}</span>
{error && <span>{error}</span>}
{timeElapsed && (
<span className="timeElapsed">{toTimeString(timeElapsed)}</span>
)}
</p>
)}
{!isOnline && <p>Lost comms with app</p>}
</div>
</div>
)
}

const toTimeString = (timestamp) => {
const _24HOURS = 8.64e7 // 24*60*60*1000
const endPos = ~(4 * !!timestamp) // to trim "Z" or ".sssZ"
let timeString = new Date(timestamp).toISOString().slice(11, endPos)

if (timestamp >= _24HOURS) {
// to extract ["hh", "mm:ss[.mss]"]
var parts = timeString.split(/:(?=\d{2}:)/)
parts[0] -= -24 * Math.floor(timestamp / _24HOURS)
timeString = parts.join(":")
}

return timeString
}
Loading