diff --git a/app.py b/app.py
index bf250c20..7dcf249c 100644
--- a/app.py
+++ b/app.py
@@ -15,6 +15,7 @@
import dash
import dash_bootstrap_components as dbc
from dash.long_callback import DiskcacheLongCallbackManager
+from dash import DiskcacheManager
# import jpype
@@ -24,6 +25,7 @@
cache = diskcache.Cache("./cache")
long_callback_manager = DiskcacheLongCallbackManager(cache)
+background_callback_manager = DiskcacheManager(cache)
args = yaml.load(open('config.yml'), yaml.Loader)
@@ -56,7 +58,8 @@
"name": "viewport",
"content": "width=device-width, initial-scale=1"
}],
- long_callback_manager=long_callback_manager
+ long_callback_manager=long_callback_manager,
+ background_callback_manager=background_callback_manager,
)
diff --git a/apps/cards.py b/apps/cards.py
index 06784baa..3b04a325 100644
--- a/apps/cards.py
+++ b/apps/cards.py
@@ -33,6 +33,43 @@
import numpy as np
import urllib
+lc_help = r"""
+##### Difference magnitude
+
+Circles (●) with error bars show valid alerts that pass the Fink quality cuts.
+In addition, the _Difference magnitude_ view shows:
+- upper triangles with errors (▲), representing alert measurements that do not satisfy Fink quality cuts, but are nevetheless contained in the history of valid alerts and used by classifiers.
+- lower triangles (▽), representing 5-sigma magnitude limit in difference image based on PSF-fit photometry contained in the history of valid alerts.
+
+If the `Color` switch is turned on, the view also shows the panel with `g - r` color, estimated by combining nearby (closer than 0.3 days) measurements in two filters.
+
+##### DC magnitude
+DC magnitude is computed by combining the nearest reference image catalog magnitude (`magnr`),
+differential magnitude (`magpsf`), and `isdiffpos` (positive or negative difference image detection) as follows:
+$$
+m_{DC} = -2.5\log_{10}(10^{-0.4m_{magnr}} + \texttt{sign} 10^{-0.4m_{magpsf}})
+$$
+
+where `sign` = 1 if `isdiffpos` = 't' or `sign` = -1 if `isdiffpos` = 'f'.
+Before using the nearest reference image source magnitude (`magnr`), you will need
+to ensure the source is close enough to be considered an association
+(e.g., `distnr` $\leq$ 1.5 arcsec). It is also advised you check the other associated metrics
+(`chinr` and/or `sharpnr`) to ensure it is a point source. ZTF recommends
+0.5 $\leq$ `chinr` $\leq$ 1.5 and/or -0.5 $\leq$ `sharpnr` $\leq$ 0.5.
+
+The view also shows, with dashed horizontal lines, the levels corresponding to the magnitudes of the nearest reference image catalog entry (`magnr`) used in computing DC magnitudes.
+
+This view may be augmented with the photometric points from [ZTF Data Releases](https://www.ztf.caltech.edu/ztf-public-releases.html) by clicking `Get DR photometry` button. The points will be shown with semi-transparent dots (•).
+
+##### DC flux
+DC flux (in Jansky) is constructed from DC magnitude by using the following:
+$$
+f_{DC} = 3631 \times 10^{-0.4m_{DC}}
+$$
+
+Note that we display the flux in milli-Jansky.
+"""
+
def card_lightcurve_summary():
""" Add a card containing the lightcurve
@@ -41,50 +78,18 @@ def card_lightcurve_summary():
card: dbc.Card
Card with the cutouts drawn inside
"""
-
- lc_help = dcc.Markdown(
- """
- ##### Difference magnitude
-
- Circles (●) with error bars show valid alerts that pass the Fink quality cuts.
- In addition, the _Difference magnitude_ view shows:
- - upper triangles with errors (▲), representing alert measurements that do not satisfy Fink quality cuts, but are nevetheless contained in the history of valid alerts and used by classifiers.
- - lower triangles (▽), representing 5-sigma magnitude limit in difference image based on PSF-fit photometry contained in the history of valid alerts.
-
- ##### DC magnitude
- DC magnitude is computed by combining the nearest reference image catalog magnitude (`magnr`),
- differential magnitude (`magpsf`), and `isdiffpos` (positive or negative difference image detection) as follows:
- $$
- m_{DC} = -2.5\\log_{10}(10^{-0.4m_{magnr}} + \\texttt{sign} 10^{-0.4m_{magpsf}})
- $$
-
- where `sign` = 1 if `isdiffpos` = 't' or `sign` = -1 if `isdiffpos` = 'f'.
- Before using the nearest reference image source magnitude (`magnr`), you will need
- to ensure the source is close enough to be considered an association
- (e.g., `distnr` $\\leq$ 1.5 arcsec). It is also advised you check the other associated metrics
- (`chinr` and/or `sharpnr`) to ensure it is a point source. ZTF recommends
- 0.5 $\\leq$ `chinr` $\\leq$ 1.5 and/or -0.5 $\\leq$ `sharpnr` $\\leq$ 0.5.
-
- ##### DC flux
- DC flux (in Jansky) is constructed from DC magnitude by using the following:
- $$
- f_{DC} = 3631 \\times 10^{-0.4m_{DC}}
- $$
-
- Note that we display the flux in milli-Jansky.
- """, mathjax=True
- )
-
card = dmc.Paper(
[
- dcc.Graph(
- id='lightcurve_cutouts',
- style={
- 'width': '100%',
- 'height': '30pc'
- },
- config={'displayModeBar': False},
- className="mb-2"
+ loading(
+ dcc.Graph(
+ id='lightcurve_cutouts',
+ style={
+ 'width': '100%',
+ 'height': '30pc'
+ },
+ config={'displayModeBar': False},
+ className="mb-2"
+ )
),
dbc.Row(
dbc.Col(
@@ -101,7 +106,41 @@ def card_lightcurve_summary():
)
)
),
- help_popover(lc_help, 'help_lc'),
+ dmc.Group(
+ [
+ dmc.Switch(
+ "Color",
+ id='lightcurve_show_color',
+ color='gray',
+ radius='xl',
+ size='sm',
+ persistence=True
+ ),
+ dmc.Button(
+ "Get DR photometry",
+ id='lightcurve_request_release',
+ variant="outline",
+ color='gray',
+ radius='xl', size='xs',
+ compact=False,
+ ),
+ help_popover(
+ dcc.Markdown(
+ lc_help, mathjax=True
+ ),
+ 'help_lc',
+ trigger=dmc.ActionIcon(
+ DashIconify(icon="mdi:help"),
+ id='help_lc',
+ color='gray',
+ variant="outline",
+ radius='xl',
+ size='md',
+ )
+ ),
+ ],
+ position='center', align='center'
+ )
], radius='xl', p='md', shadow='xl', withBorder=True
)
return card
diff --git a/apps/plotting.py b/apps/plotting.py
index 81a7d413..31345ce8 100644
--- a/apps/plotting.py
+++ b/apps/plotting.py
@@ -17,6 +17,7 @@
import numpy as np
from gatspy import periodic
from scipy.optimize import curve_fit
+from copy import deepcopy
import datetime
import copy
@@ -33,8 +34,9 @@
from dash_iconify import DashIconify
from dash.exceptions import PreventUpdate
-from apps.utils import convert_jd, readstamp, _data_stretch, convolve
+from apps.utils import convert_jd, readstamp, _data_stretch, convolve, extract_color
from fink_utils.photometry.conversion import apparent_flux, dc_mag
+from fink_utils.photometry.utils import is_source_behind
from apps.utils import sine_fit
from apps.utils import class_colors
from apps.statistics import dic_names
@@ -78,6 +80,7 @@
}
layout_lightcurve = dict(
+ autosize=True,
automargin=True,
margin=dict(l=50, r=30, b=0, t=0),
hovermode="closest",
@@ -94,12 +97,14 @@
),
xaxis={
'title': 'Observation date',
- 'automargin': True
+ 'automargin': True,
+ 'zeroline': False
},
yaxis={
'autorange': 'reversed',
'title': 'Magnitude',
- 'automargin': True
+ 'automargin': True,
+ 'zeroline': False
}
)
@@ -690,9 +695,11 @@ def plot_classbar(object_data):
Input('url', 'pathname'),
Input('object-data', 'children'),
Input('object-upper', 'children'),
- Input('object-uppervalid', 'children')
+ Input('object-uppervalid', 'children'),
+ Input('object-release', 'children'),
+ Input('lightcurve_show_color', 'checked')
])
-def draw_lightcurve(switch: int, pathname: str, object_data, object_upper, object_uppervalid) -> dict:
+def draw_lightcurve(switch: int, pathname: str, object_data, object_upper, object_uppervalid, object_release, show_color) -> dict:
""" Draw object lightcurve with errorbars
Parameters
@@ -709,39 +716,70 @@ def draw_lightcurve(switch: int, pathname: str, object_data, object_upper, objec
----------
figure: dict
"""
+ # Primary high-quality data points
pdf_ = pd.read_json(object_data)
cols = [
- 'i:jd', 'i:magpsf', 'i:sigmapsf', 'i:fid',
+ 'i:jd', 'i:magpsf', 'i:sigmapsf', 'i:fid', 'i:distnr',
'i:magnr', 'i:sigmagnr', 'i:magzpsci', 'i:isdiffpos', 'i:candid'
]
pdf = pdf_.loc[:, cols]
+ # Upper limits
+ pdf_upper = pd.read_json(object_upper)
+
+ # Lower-quality data points
+ pdf_upperv = pd.read_json(object_uppervalid)
+
# type conversion
dates = pdf['i:jd'].apply(lambda x: convert_jd(float(x), to='iso'))
+ dates_upper = pdf_upper['i:jd'].apply(lambda x: convert_jd(float(x), to='iso'))
+ dates_upperv = pdf_upperv['i:jd'].apply(lambda x: convert_jd(float(x), to='iso'))
+
+ if object_release:
+ # Data release photometry
+ pdf_release = pd.read_json(object_release)
+ dates_release = pdf_release['mjd'].apply(lambda x: convert_jd(float(x)+2400000.5, to='iso'))
+ else:
+ pdf_release = pd.DataFrame()
+
+ # Exclude lower-quality points overlapping higher-quality ones
+ mask = np.in1d(pdf_upperv['i:jd'].values, pdf['i:jd'].values)
+ pdf_upperv,dates_upperv = [_[~mask] for _ in (pdf_upperv,dates_upperv)]
# shortcuts
mag = pdf['i:magpsf']
err = pdf['i:sigmapsf']
+ # Should we correct DC magnitudes for the nearby source?..
+ is_dc_corrected = is_source_behind(pdf['i:distnr'].values[0])
+
+ # We should never modify global variables!!!
+ layout = deepcopy(layout_lightcurve)
+
if switch == "Difference magnitude":
- layout_lightcurve['yaxis']['title'] = 'Difference magnitude'
- layout_lightcurve['yaxis']['autorange'] = 'reversed'
+ layout['yaxis']['title'] = 'Difference magnitude'
+ layout['yaxis']['autorange'] = 'reversed'
scale = 1.0
elif switch == "DC magnitude":
- # inplace replacement
- mag, err = np.transpose(
- [
- dc_mag(*args) for args in zip(
- mag.astype(float).values,
- err.astype(float).values,
- pdf['i:magnr'].astype(float).values,
- pdf['i:sigmagnr'].astype(float).values,
- pdf['i:isdiffpos'].values
- )
- ]
- )
- layout_lightcurve['yaxis']['title'] = 'Apparent DC magnitude'
- layout_lightcurve['yaxis']['autorange'] = 'reversed'
+ if is_dc_corrected:
+ # inplace replacement for DC corrected flux
+ mag, err = np.transpose(
+ [
+ dc_mag(*args) for args in zip(
+ mag.astype(float).values,
+ err.astype(float).values,
+ pdf['i:magnr'].astype(float).values,
+ pdf['i:sigmagnr'].astype(float).values,
+ pdf['i:isdiffpos'].values
+ )
+ ]
+ )
+ # Keep only "good" measurements
+ idx = err < 1
+ pdf, dates, mag, err = [_[idx] for _ in [pdf, dates, mag, err]]
+
+ layout['yaxis']['title'] = 'Apparent DC magnitude'
+ layout['yaxis']['autorange'] = 'reversed'
scale = 1.0
elif switch == "DC flux":
# inplace replacement
@@ -750,178 +788,267 @@ def draw_lightcurve(switch: int, pathname: str, object_data, object_upper, objec
apparent_flux(*args) for args in zip(
mag.astype(float).values,
err.astype(float).values,
- pdf['i:magnr'].astype(float).values,
+ pdf['i:magnr'].astype(float).values if is_dc_corrected else [99.0]*len(pdf.index),
pdf['i:sigmagnr'].astype(float).values,
pdf['i:isdiffpos'].values
)
]
)
- layout_lightcurve['yaxis']['title'] = 'Apparent DC flux (milliJansky)'
- layout_lightcurve['yaxis']['autorange'] = True
+
+ layout['yaxis']['title'] = 'Apparent DC flux (milliJansky)'
+ layout['yaxis']['autorange'] = True
scale = 1e3
- hovertemplate = r"""
- %{yaxis.title.text}: %{customdata[1]}%{y:.2f} ± %{error_y.array:.2f}
- %{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
- mjd: %{customdata[0]}
-
- """
+ layout['shapes'] = []
+
figure = {
- 'data': [
+ 'data': [],
+ "layout": layout
+ }
+
+ for fid,fname,color in (
+ (1, "g", COLORS_ZTF[0]),
+ (2, "r", COLORS_ZTF[1])
+ ):
+ # High-quality measurements
+ hovertemplate = r"""
+ %{yaxis.title.text}: %{customdata[1]}%{y:.2f} ± %{error_y.array:.2f}
+ %{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
+ mjd: %{customdata[0]}
+
+ """
+ idx = pdf['i:fid'] == fid
+ figure['data'].append(
{
- 'x': dates[pdf['i:fid'] == 1],
- 'y': mag[pdf['i:fid'] == 1] * scale,
+ 'x': dates[idx],
+ 'y': mag[idx] * scale,
'error_y': {
'type': 'data',
- 'array': err[pdf['i:fid'] == 1] * scale,
+ 'array': err[idx] * scale,
'visible': True,
- 'color': COLORS_ZTF[0]
+ 'width': 0,
+ 'opacity': 0.5,
+ 'color': color
},
'mode': 'markers',
- 'name': 'g band',
+ 'name': '{} band'.format(fname),
'customdata': np.stack(
(
- pdf['i:jd'].apply(lambda x: x - 2400000.5)[pdf['i:fid'] == 1],
- pdf['i:isdiffpos'].apply(lambda x: '(-) ' if x == 'f' else '')[pdf['i:fid'] == 1],
- ), axis=-1
- ),
- 'hovertemplate': hovertemplate,
- "legendgroup": "g band",
- 'legendrank': 110,
- 'marker': {
- 'size': 12,
- 'color': COLORS_ZTF[0],
- 'symbol': 'o'}
- },
- {
- 'x': dates[pdf['i:fid'] == 2],
- 'y': mag[pdf['i:fid'] == 2] * scale,
- 'error_y': {
- 'type': 'data',
- 'array': err[pdf['i:fid'] == 2] * scale,
- 'visible': True,
- 'color': COLORS_ZTF[1]
- },
- 'mode': 'markers',
- 'name': 'r band',
- 'legendrank': 120,
- 'customdata': np.stack(
- (
- pdf['i:jd'].apply(lambda x: x - 2400000.5)[pdf['i:fid'] == 2],
- pdf['i:isdiffpos'].apply(lambda x: '(-) ' if x == 'f' else '')[pdf['i:fid'] == 2],
+ pdf['i:jd'].apply(lambda x: x - 2400000.5)[idx],
+ pdf['i:isdiffpos'].apply(lambda x: '(-) ' if x == 'f' else '')[idx],
), axis=-1
),
'hovertemplate': hovertemplate,
- "legendgroup": "r band",
+ "legendgroup": "{} band".format(fname),
+ 'legendrank': 100 + 10*fid,
'marker': {
'size': 12,
- 'color': COLORS_ZTF[1],
+ 'color': color,
'symbol': 'o'}
}
- ],
- "layout": layout_lightcurve
- }
+ )
- if switch == "Difference magnitude":
- pdf_upper = pd.read_json(object_upper)
- # candid: %{customdata[0]}
not available in index tables...
- hovertemplate_upper = r"""
- Upper limit: %{y:.2f}
+ if switch == "Difference magnitude":
+ # Upper limits
+ if not pdf_upper.empty:
+ # candid: %{customdata[0]}
not available in index tables...
+ hovertemplate_upper = r"""
+ Upper limit: %{y:.2f}
+ %{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
+ mjd: %{customdata}
+
+ """
+ idx = pdf_upper['i:fid'] == fid
+ figure['data'].append(
+ {
+ 'x': dates_upper[idx],
+ 'y': pdf_upper['i:diffmaglim'][idx],
+ 'mode': 'markers',
+ 'name': '',
+ 'customdata': pdf_upper['i:jd'].apply(lambda x: x - 2400000.5)[idx],
+ 'hovertemplate': hovertemplate_upper,
+ "legendgroup": "{} band upper".format(fname),
+ 'legendrank': 101 + 10*fid,
+ 'marker': {
+ 'color': color,
+ 'symbol': 'triangle-down-open'
+ },
+ # 'showlegend': False
+ }
+ )
+
+ # Lower-quality data points
+ if not pdf_upperv.empty:
+ # candid: %{customdata[0]}
not available in index tables...
+ hovertemplate_upperv = r"""
+ %{yaxis.title.text} (low quality): %{y:.2f} ± %{error_y.array:.2f}
+ %{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
+ mjd: %{customdata}
+
+ """
+ idx = pdf_upperv['i:fid'] == fid
+ figure['data'].append(
+ {
+ 'x': dates_upperv[idx],
+ 'y': pdf_upperv['i:magpsf'][idx],
+ 'error_y': {
+ 'type': 'data',
+ 'array': pdf_upperv['i:sigmapsf'][idx],
+ 'visible': True,
+ 'width': 0,
+ 'opacity': 0.5,
+ 'color': color
+ },
+ 'mode': 'markers',
+ 'customdata': pdf_upperv['i:jd'].apply(lambda x: x - 2400000.5)[idx],
+ 'hovertemplate': hovertemplate_upperv,
+ "legendgroup": "{} band".format(fname),
+ 'marker': {
+ 'color': color,
+ 'symbol': 'triangle-up'
+ },
+ 'showlegend': False
+ }
+ )
+
+ elif switch == "DC magnitude":
+ if is_dc_corrected:
+ # Overplot the levels of nearby source magnitudes
+ idx = pdf['i:fid'] == fid
+ if np.sum(idx):
+ ref = np.mean(pdf['i:magnr'][idx])
+
+ figure['layout']['shapes'].append(
+ {
+ 'type': 'line',
+ 'yref': 'y',
+ 'y0': ref, 'y1': ref, # adding a horizontal line
+ 'xref': 'paper', 'x0': 0, 'x1': 1,
+ 'line': {'color': color, 'dash': 'dash', 'width': 1},
+ 'legendgroup': '{} band'.format(fname),
+ 'opacity': 0.3,
+ }
+ )
+
+ # Data release photometry
+ if not pdf_release.empty:
+ hovertemplate_release = r"""
+ Data release magnitude: %{y:.2f} ± %{error_y.array:.2f}
+ %{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
+ mjd: %{customdata}
+
+ """
+ idx = pdf_release['filtercode'] == 'z' + fname
+ figure['data'].append(
+ {
+ 'x': dates_release[idx],
+ 'y': pdf_release['mag'][idx],
+ 'error_y': {
+ 'type': 'data',
+ 'array': pdf_release['magerr'][idx],
+ 'visible': True,
+ 'width': 0,
+ 'opacity': 0.5,
+ 'color': color
+ },
+ 'mode': 'markers',
+ 'name': '',
+ 'customdata': pdf_release['mjd'][idx],
+ 'hovertemplate': hovertemplate_release,
+ "legendgroup": "{} band release".format(fname),
+ 'legendrank': 102 + 10*fid,
+ 'marker': {
+ 'color': color,
+ 'symbol': '.'
+ },
+ 'opacity': 0.5,
+ # 'showlegend': False
+ }
+ )
+
+ if show_color:
+ hovertemplate_gr = r"""
+ g - r: %{y:.2f} ± %{error_y.array:.2f}
%{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
mjd: %{customdata}
"""
- if not pdf_upper.empty:
- dates2 = pdf_upper['i:jd'].apply(lambda x: convert_jd(float(x), to='iso'))
- figure['data'].append(
- {
- 'x': dates2[pdf_upper['i:fid'] == 1],
- 'y': pdf_upper['i:diffmaglim'][pdf_upper['i:fid'] == 1],
- 'mode': 'markers',
- 'name': '',
- 'customdata': pdf_upper['i:jd'].apply(lambda x: x - 2400000.5)[pdf_upper['i:fid'] == 1],
- 'hovertemplate': hovertemplate_upper,
- "legendgroup": "g band upper",
- 'legendrank': 111,
- 'marker': {
- 'color': COLORS_ZTF[0],
- 'symbol': 'triangle-down-open'
- },
- # 'showlegend': False
- }
- )
- figure['data'].append(
- {
- 'x': dates2[pdf_upper['i:fid'] == 2],
- 'y': pdf_upper['i:diffmaglim'][pdf_upper['i:fid'] == 2],
- 'mode': 'markers',
- 'name': '',
- 'customdata': pdf_upper['i:jd'].apply(lambda x: x - 2400000.5)[pdf_upper['i:fid'] == 2],
- 'hovertemplate': hovertemplate_upper,
- "legendgroup": "r band upper",
- 'legendrank': 121,
- 'marker': {
- 'color': COLORS_ZTF[1],
- 'symbol': 'triangle-down-open'
- },
- # 'showlegend': False
- }
+
+ pdf_ = None
+
+ if switch == "Difference magnitude":
+ pdf_ = pd.concat([pdf, pdf_upperv])
+ elif switch == "DC magnitude":
+ pdf_ = pd.DataFrame(
+ {'i:jd': pdf['i:jd'], 'i:fid': pdf['i:fid'], 'i:magpsf': mag, 'i:sigmapsf': err}
)
- pdf_upperv = pd.read_json(object_uppervalid)
- # candid: %{customdata[0]}
not available in index tables...
- hovertemplate_upperv = r"""
- %{yaxis.title.text} (low quality): %{y:.2f} ± %{error_y.array:.2f}
- %{xaxis.title.text}: %{x|%Y/%m/%d %H:%M:%S.%L}
- mjd: %{customdata}
-
- """
- if not pdf_upperv.empty:
- dates2 = pdf_upperv['i:jd'].apply(lambda x: convert_jd(float(x), to='iso'))
- mask = np.array([False if i in pdf['i:jd'].values else True for i in pdf_upperv['i:jd'].values])
- dates2 = dates2[mask]
- pdf_upperv = pdf_upperv[mask]
+
+ if not pdf_release.empty:
+ pdf_ = pd.concat(
+ [
+ pdf_,
+ pd.DataFrame(
+ {
+ 'i:jd': pdf_release['mjd'] + 2400000.5,
+ 'i:fid': pdf_release['filtercode'].map({'zg': 1, 'zr': 2}),
+ 'i:magpsf': pdf_release['mag'],
+ 'i:sigmapsf': pdf_release['magerr'],
+ }
+ ),
+ ]
+ )
+
+ if pdf_ is not None:
+ pdf_gr = extract_color(pdf_)
+ dates_gr = pdf_gr['i:jd'].apply(lambda x: convert_jd(float(x), to='iso'))
+ color = '#3C8DFF'
+
figure['data'].append(
{
- 'x': dates2[pdf_upperv['i:fid'] == 1],
- 'y': pdf_upperv['i:magpsf'][pdf_upperv['i:fid'] == 1],
+ 'x': dates_gr,
+ 'y': pdf_gr['v:g-r'],
'error_y': {
'type': 'data',
- 'array': pdf_upperv['i:sigmapsf'][pdf_upperv['i:fid'] == 1],
+ 'array': pdf_gr['v:sigma_g-r'],
'visible': True,
- 'color': COLORS_ZTF[0]
+ 'width': 0,
+ 'opacity': 0.5,
+ 'color': color
},
'mode': 'markers',
- 'customdata': pdf_upperv['i:jd'].apply(lambda x: x - 2400000.5)[pdf_upperv['i:fid'] == 1],
- 'hovertemplate': hovertemplate_upperv,
- "legendgroup": "g band",
+ 'name': 'g - r',
+ 'customdata': pdf_gr['i:jd'].apply(lambda x: x - 2400000.5),
+ 'hovertemplate': hovertemplate_gr,
+ 'legendgroup': 'g-r',
'marker': {
- 'color': COLORS_ZTF[0],
- 'symbol': 'triangle-up'
+ 'color': color,
+ 'symbol': 'o',
},
- 'showlegend': False
+ 'showlegend': True,
+ 'xaxis': 'x',
+ 'yaxis': 'y2',
}
)
- figure['data'].append(
+
+ figure['layout']['yaxis2'] = {
+ 'automargin': True,
+ 'title': 'g-r',
+ 'domain': [0.0, 0.3],
+ 'zeroline': False,
+ }
+ figure['layout']['yaxis']['domain'] = [0.35, 1.0]
+ figure['layout']['xaxis']['anchor'] = 'free'
+
+ figure['layout']['shapes'].append(
{
- 'x': dates2[pdf_upperv['i:fid'] == 2],
- 'y': pdf_upperv['i:magpsf'][pdf_upperv['i:fid'] == 2],
- 'error_y': {
- 'type': 'data',
- 'array': pdf_upperv['i:sigmapsf'][pdf_upperv['i:fid'] == 2],
- 'visible': True,
- 'color': COLORS_ZTF[1]
- },
- 'mode': 'markers',
- 'customdata': pdf_upperv['i:jd'].apply(lambda x: x - 2400000.5)[pdf_upperv['i:fid'] == 2],
- 'hovertemplate': hovertemplate_upperv,
- "legendgroup": "r band",
- 'marker': {
- 'color': COLORS_ZTF[1],
- 'symbol': 'triangle-up'
- },
- 'showlegend': False
+ 'type': 'line',
+ 'yref': 'paper', 'y0':0.325, 'y1': 0.325,
+ 'xref': 'paper', 'x0': -0.1, 'x1': 1.05,
+ 'line': {'color': 'gray', 'width': 4},
+ 'opacity': 0.1,
}
)
+
return figure
@app.callback(
diff --git a/apps/summary.py b/apps/summary.py
index 1269389d..29cc40ce 100644
--- a/apps/summary.py
+++ b/apps/summary.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import dash
-from dash import html, dcc, Input, Output, State
+from dash import html, dcc, Input, Output, State, no_update
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
@@ -60,9 +60,21 @@
dcc.Location(id='url', refresh=False)
-def tab1_content(pdf, extra_div):
+def tab1_content(pdf):
""" Summary tab
"""
+
+ distnr = pdf['i:distnr'].values[0]
+ if is_source_behind(distnr):
+ extra_div = dbc.Alert(
+ "It looks like there is a source behind, at {:.1f} arcsec. You might want to check the DC magnitude, and get DR photometry to see its long-term behaviour.".format(distnr),
+ dismissable=True,
+ is_open=True,
+ color="light"
+ )
+ else:
+ extra_div = html.Div()
+
tab1_content_ = html.Div([
dmc.Space(h=10),
dbc.Row(
@@ -80,10 +92,21 @@ def tab1_content(pdf, extra_div):
),
], justify='around'
),
- dbc.Row([
- dbc.Col([extra_div, loading(card_lightcurve_summary())], md=8),
- dbc.Col(card_id(pdf), md=4)
- ], className='g-1'),
+ dbc.Row(
+ [
+ dbc.Col(
+ [
+ extra_div,
+ card_lightcurve_summary()
+ ],
+ md=8
+ ),
+ dbc.Col(
+ card_id(pdf),
+ md=4
+ )
+ ], className='g-1'
+ ),
])
out = tab1_content_
@@ -408,30 +431,20 @@ def tab6_content(object_tracklet):
return tab6_content_
def tabs(pdf):
- distnr = pdf['i:distnr'].values[0]
- if is_source_behind(distnr):
- extra_div = dbc.Alert(
- "It looks like there is a source behind. You might want to check the DC magnitude instead.",
- dismissable=True,
- is_open=True,
- color="light"
- )
- else:
- extra_div = html.Div()
tabs_ = dmc.Tabs(
[
dmc.TabsList(
[
dmc.Tab("Summary", value="Summary"),
- dmc.Tab("Supernovae", value="Supernovae"),
- dmc.Tab("Variable stars", value="Variable stars"),
- dmc.Tab("Microlensing", value="Microlensing"),
+ dmc.Tab("Supernovae", value="Supernovae", disabled=len(pdf.index) == 1),
+ dmc.Tab("Variable stars", value="Variable stars", disabled=len(pdf.index) == 1),
+ dmc.Tab("Microlensing", value="Microlensing", disabled=len(pdf.index) == 1),
dmc.Tab("Solar System", value="Solar System", disabled=not is_sso(pdf)),
dmc.Tab("Tracklets", value="Tracklets", disabled=not is_tracklet(pdf)),
dmc.Tab("GRB", value="GRB", disabled=True)
], position='right'
),
- dmc.TabsPanel(tab1_content(pdf, extra_div), value="Summary"),
+ dmc.TabsPanel(tab1_content(pdf), value="Summary"),
dmc.TabsPanel(tab2_content(), value="Supernovae"),
dmc.TabsPanel(tab3_content(), value="Variable stars"),
dmc.TabsPanel(tab4_content(), value="Microlensing"),
@@ -567,8 +580,54 @@ def store_query(name):
pdftracklet = pd.read_json(r.content)
else:
pdftracklet = pd.DataFrame()
+
return pdfs.to_json(), pdfsU.to_json(), pdfsUV.to_json(), pdfsso.to_json(), pdftracklet.to_json()
+@app.callback(
+ [
+ Output('object-release', 'children'),
+ Output('lightcurve_request_release', 'children'),
+ Output('switch-mag-flux', 'value'),
+ ],
+ Input('lightcurve_request_release', 'n_clicks'),
+ State('object-data', 'children'),
+ prevent_initial_call=True,
+ background=True,
+ running=[
+ (Output('lightcurve_request_release', 'disabled'), True, True),
+ (Output('lightcurve_request_release', 'loading'), True, False),
+ ],
+)
+def store_release_photometry(n_clicks, object_data):
+ if not n_clicks or not object_data:
+ raise PreventUpdate
+
+ pdf = pd.read_json(object_data)
+
+ mean_ra = np.mean(pdf['i:ra'])
+ mean_dec = np.mean(pdf['i:dec'])
+
+ try:
+ pdf_release = pd.read_csv(
+ 'https://irsa.ipac.caltech.edu/cgi-bin/ZTF/nph_light_curves?POS=CIRCLE%20{}%20{}%20{}&BAD_CATFLAGS_MASK=32768&FORMAT=CSV'.format(
+ mean_ra,
+ mean_dec,
+ 2.0/3600
+ )
+ )
+
+ if not pdf_release.empty:
+ pdf_release = pdf_release[['mjd', 'mag', 'magerr', 'filtercode']]
+
+ return pdf_release.to_json(), "DR photometry: {} points".format(len(pdf_release.index)), 'DC magnitude'
+
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+
+ return no_update, "No DR photometry", no_update
+
@app.callback(
Output('qrcode', 'children'),
[
@@ -640,6 +699,7 @@ def layout(name):
html.Div(id='object-uppervalid', style={'display': 'none'}),
html.Div(id='object-sso', style={'display': 'none'}),
html.Div(id='object-tracklet', style={'display': 'none'}),
+ html.Div(id='object-release', style={'display': 'none'}),
], className='bg-opaque-90'
)
diff --git a/apps/utils.py b/apps/utils.py
index 62822912..c4669603 100644
--- a/apps/utils.py
+++ b/apps/utils.py
@@ -560,6 +560,50 @@ def extract_delta_color(pdf: pd.DataFrame, filter_: int):
return vec, rate
+def extract_color(pdf: pd.DataFrame, tolerance: float = 0.3, colnames=None):
+ """ Extract g-r values for single object a pandas DataFrame
+
+ Parameters
+ ----------
+ pdf: pandas DataFrame
+ DataFrame containing alert parameters from an API call
+ tolerance: float
+ Maximal distance in days between data points to be associated
+ colnames: list
+ List of extra column names to keep in the output.
+
+ Returns
+ ----------
+ pdf_gr: pandas DataFrame
+ DataFrame containing the time and magnitudes of matched points,
+ along with g-r color and its error
+ """
+ if colnames is None:
+ colnames = ['i:jd', 'i:magpsf', 'i:sigmapsf']
+ else:
+ colnames = ['i:jd', 'i:magpsf', 'i:sigmapsf'] + colnames
+ colnames = list(np.unique(colnames))
+
+ pdf_g = pdf[pdf['i:fid'] == 1][colnames]
+ pdf_r = pdf[pdf['i:fid'] == 2][colnames]
+
+ # merge_asof expects sorted data
+ pdf_g = pdf_g.sort_values('i:jd')
+ pdf_r = pdf_r.sort_values('i:jd')
+
+ # As merge_asof does not keep the second jd column - let's make it manually
+ pdf_g['v:mjd'] = pdf_g['i:jd'] - 2400000.5
+ pdf_r['v:mjd'] = pdf_r['i:jd'] - 2400000.5
+
+ pdf_gr = pd.merge_asof(pdf_g, pdf_r, on='i:jd', suffixes=('_g', '_r'), direction='nearest', tolerance=0.3)
+ pdf_gr = pdf_gr[~pdf_gr.isna()['i:magpsf_r']] # Keep only matched rows
+
+ pdf_gr['v:g-r'] = pdf_gr['i:magpsf_g'] - pdf_gr['i:magpsf_r']
+ pdf_gr['v:sigma_g-r'] = np.hypot(pdf_gr['i:sigmapsf_g'], pdf_gr['i:sigmapsf_r'])
+ pdf_gr['v:delta_jd'] = pdf_gr['v:mjd_g'] - pdf_gr['v:mjd_r']
+
+ return pdf_gr
+
def queryMPC(number, kind='asteroid'):
"""Query MPC for information about object 'designation'.