├── requirements.txt ├── README.md ├── helper.py └── app.py /requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit==1.37.0 2 | gwosc==0.7.1 3 | gwpy==3.0.8 4 | matplotlib==3.5.3 5 | astropy==5.3.3 6 | numpy==1.23.5 7 | scipy==1.10.0 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Gravitational Wave Quickview App 2 | 3 | This app downloads and displays a few seconds of data from the [Gravitational Wave Open Science Center](https://gwosc.org). 4 | 5 | [![Open in Streamlit](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://share.streamlit.io/jkanner/streamlit-dataview/app.py) 6 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | 2 | from scipy import signal 3 | from scipy.io import wavfile 4 | import io 5 | import numpy as np 6 | 7 | # -- Helper function to make audio file 8 | 9 | def make_audio_file(bp_data, t0=None): 10 | # -- window data for gentle on/off 11 | window = signal.windows.tukey(len(bp_data), alpha=1.0/10) 12 | win_data = bp_data*window 13 | 14 | # -- Normalize for 16 bit audio 15 | win_data = np.int16(win_data/np.max(np.abs(win_data)) * 32767 * 0.9) 16 | 17 | fs=1/win_data.dt.value 18 | virtualfile = io.BytesIO() 19 | wavfile.write(virtualfile, int(fs), win_data) 20 | 21 | return virtualfile 22 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pandas as pd 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | import requests, os 7 | from gwpy.timeseries import TimeSeries 8 | from gwosc.locate import get_urls 9 | from gwosc import datasets 10 | from gwosc.api import fetch_event_json 11 | 12 | from copy import deepcopy 13 | import base64 14 | 15 | from helper import make_audio_file 16 | 17 | # Use the non-interactive Agg backend, which is recommended as a 18 | # thread-safe backend. 19 | # See https://matplotlib.org/3.3.2/faq/howto_faq.html#working-with-threads. 20 | import matplotlib as mpl 21 | mpl.use("agg") 22 | 23 | ############################################################################## 24 | # Workaround for the limited multi-threading support in matplotlib. 25 | # Per the docs, we will avoid using `matplotlib.pyplot` for figures: 26 | # https://matplotlib.org/3.3.2/faq/howto_faq.html#how-to-use-matplotlib-in-a-web-application-server. 27 | # Moreover, we will guard all operations on the figure instances by the 28 | # class-level lock in the Agg backend. 29 | ############################################################################## 30 | from matplotlib.backends.backend_agg import RendererAgg 31 | _lock = RendererAgg.lock 32 | 33 | 34 | # -- Set page config 35 | apptitle = 'GW Quickview' 36 | 37 | st.set_page_config(page_title=apptitle, page_icon=":eyeglasses:") 38 | 39 | # -- Default detector list 40 | detectorlist = ['H1','L1', 'V1'] 41 | 42 | # Title the app 43 | st.title('Gravitational Wave Quickview') 44 | 45 | st.markdown(""" 46 | * Use the menu at left to select data and set plot parameters 47 | * Your plots will appear below 48 | """) 49 | 50 | @st.cache_data(max_entries=5) #-- Magic command to cache data 51 | def load_gw(t0, detector, fs=4096): 52 | strain = TimeSeries.fetch_open_data(detector, t0-14, t0+14, sample_rate = fs, cache=False) 53 | return strain 54 | 55 | @st.cache_data(max_entries=10) #-- Magic command to cache data 56 | def get_eventlist(): 57 | allevents = datasets.find_datasets(type='events') 58 | eventset = set() 59 | for ev in allevents: 60 | name = fetch_event_json(ev)['events'][ev]['commonName'] 61 | if name[0:2] == 'GW': 62 | eventset.add(name) 63 | eventlist = list(eventset) 64 | eventlist.sort() 65 | return eventlist 66 | 67 | st.sidebar.markdown("## Select Data Time and Detector") 68 | 69 | # -- Get list of events 70 | eventlist = get_eventlist() 71 | 72 | #-- Set time by GPS or event 73 | select_event = st.sidebar.selectbox('How do you want to find data?', 74 | ['By event name', 'By GPS']) 75 | 76 | if select_event == 'By GPS': 77 | # -- Set a GPS time: 78 | str_t0 = st.sidebar.text_input('GPS Time', '1126259462.4') # -- GW150914 79 | t0 = float(str_t0) 80 | 81 | st.sidebar.markdown(""" 82 | Example times in the H1 detector: 83 | * 1126259462.4 (GW150914) 84 | * 1187008882.4 (GW170817) 85 | * 1128667463.0 (hardware injection) 86 | * 1132401286.33 (Koi Fish Glitch) 87 | """) 88 | 89 | else: 90 | chosen_event = st.sidebar.selectbox('Select Event', eventlist) 91 | t0 = datasets.event_gps(chosen_event) 92 | detectorlist = list(datasets.event_detectors(chosen_event)) 93 | detectorlist.sort() 94 | st.subheader(chosen_event) 95 | st.write('GPS:', t0) 96 | 97 | # -- Experiment to display masses 98 | try: 99 | jsoninfo = fetch_event_json(chosen_event) 100 | for name, nameinfo in jsoninfo['events'].items(): 101 | st.write('Mass 1:', nameinfo['mass_1_source'], 'M$_{\odot}$') 102 | st.write('Mass 2:', nameinfo['mass_2_source'], 'M$_{\odot}$') 103 | st.write('Network SNR:', int(nameinfo['network_matched_filter_snr'])) 104 | eventurl = 'https://gwosc.org/eventapi/html/event/{}'.format(chosen_event) 105 | st.markdown('Event page: {}'.format(eventurl)) 106 | st.write('\n') 107 | except: 108 | pass 109 | 110 | 111 | #-- Choose detector as H1, L1, or V1 112 | detector = st.sidebar.selectbox('Detector', detectorlist) 113 | 114 | # -- Select for high sample rate data 115 | fs = 4096 116 | maxband = 1200 117 | high_fs = st.sidebar.checkbox('Full sample rate data') 118 | if high_fs: 119 | fs = 16384 120 | maxband = 2000 121 | 122 | 123 | # -- Create sidebar for plot controls 124 | st.sidebar.markdown('## Set Plot Parameters') 125 | dtboth = st.sidebar.slider('Time Range (seconds)', 0.1, 8.0, 1.0) # min, max, default 126 | dt = dtboth / 2.0 127 | 128 | st.sidebar.markdown('#### Whitened and band-passed data') 129 | whiten = st.sidebar.checkbox('Whiten?', value=True) 130 | freqrange = st.sidebar.slider('Band-pass frequency range (Hz)', min_value=10, max_value=maxband, value=(30,400)) 131 | 132 | 133 | # -- Create sidebar for Q-transform controls 134 | st.sidebar.markdown('#### Q-tranform plot') 135 | vmax = st.sidebar.slider('Colorbar Max Energy', 10, 500, 25) # min, max, default 136 | qcenter = st.sidebar.slider('Q-value', 5, 120, 5) # min, max, default 137 | qrange = (int(qcenter*0.8), int(qcenter*1.2)) 138 | 139 | #-- Create a text element and let the reader know the data is loading. 140 | strain_load_state = st.text('Loading data...this may take a minute') 141 | try: 142 | strain_data = load_gw(t0, detector, fs) 143 | except: 144 | st.warning('{0} data are not available for time {1}. Please try a different time and detector pair.'.format(detector, t0)) 145 | st.stop() 146 | 147 | strain_load_state.text('Loading data...done!') 148 | 149 | #-- Make a time series plot 150 | 151 | cropstart = t0-0.2 152 | cropend = t0+0.1 153 | 154 | cropstart = t0 - dt 155 | cropend = t0 + dt 156 | 157 | st.subheader('Raw data') 158 | center = int(t0) 159 | strain = deepcopy(strain_data) 160 | 161 | with _lock: 162 | fig1 = strain.crop(cropstart, cropend).plot() 163 | #fig1 = cropped.plot() 164 | st.pyplot(fig1, clear_figure=True) 165 | 166 | 167 | # -- Try whitened and band-passed plot 168 | # -- Whiten and bandpass data 169 | st.subheader('Whitened and Band-passed Data') 170 | 171 | if whiten: 172 | white_data = strain.whiten() 173 | bp_data = white_data.bandpass(freqrange[0], freqrange[1]) 174 | else: 175 | bp_data = strain.bandpass(freqrange[0], freqrange[1]) 176 | 177 | bp_cropped = bp_data.crop(cropstart, cropend) 178 | 179 | with _lock: 180 | fig3 = bp_cropped.plot() 181 | st.pyplot(fig3, clear_figure=True) 182 | 183 | # -- Allow data download 184 | download = {'Time':bp_cropped.times, 'Strain':bp_cropped.value} 185 | df = pd.DataFrame(download) 186 | csv = df.to_csv(index=False) 187 | b64 = base64.b64encode(csv.encode()).decode() # some strings <-> bytes conversions necessary here 188 | fn = detector + '-STRAIN' + '-' + str(int(cropstart)) + '-' + str(int(cropend-cropstart)) + '.csv' 189 | href = f'Download Data as CSV File' 190 | st.markdown(href, unsafe_allow_html=True) 191 | 192 | # -- Make audio file 193 | st.audio(make_audio_file(bp_cropped), format='audio/wav') 194 | 195 | # -- Notes on whitening 196 | with st.expander("See notes"): 197 | st.markdown(""" 198 | * Whitening is a process that re-weights a signal, so that all frequency bins have a nearly equal amount of noise. 199 | * A band-pass filter uses both a low frequency cutoff and a high frequency cutoff, and only passes signals in the frequency band between these values. 200 | 201 | See also: 202 | * [Signal Processing Tutorial](https://share.streamlit.io/jkanner/streamlit-audio/main/app.py) 203 | """) 204 | 205 | 206 | st.subheader('Q-transform') 207 | 208 | hq = strain.q_transform(outseg=(t0-dt, t0+dt), qrange=qrange) 209 | 210 | with _lock: 211 | fig4 = hq.plot() 212 | ax = fig4.gca() 213 | fig4.colorbar(label="Normalised energy", vmax=vmax, vmin=0) 214 | ax.grid(False) 215 | ax.set_yscale('log') 216 | ax.set_ylim(bottom=15) 217 | st.pyplot(fig4, clear_figure=True) 218 | 219 | 220 | with st.expander("See notes"): 221 | 222 | st.markdown(""" 223 | A Q-transform plot shows how a signal’s frequency changes with time. 224 | 225 | * The x-axis shows time 226 | * The y-axis shows frequency 227 | 228 | The color scale shows the amount of “energy” or “signal power” in each time-frequency pixel. 229 | 230 | A parameter called “Q” refers to the quality factor. A higher quality factor corresponds to a larger number of cycles in each time-frequency pixel. 231 | 232 | For gravitational-wave signals, binary black holes are most clear with lower Q values (Q = 5-20), where binary neutron star mergers work better with higher Q values (Q = 80 - 120). 233 | 234 | See also: 235 | 236 | * [GWpy q-transform](https://gwpy.github.io/docs/latest/examples/timeseries/qscan/) 237 | * [Reading Time-frequency plots](https://labcit.ligo.caltech.edu/~jkanner/aapt/web/math.html#tfplot) 238 | * [Shourov Chatterji PhD Thesis](https://dspace.mit.edu/handle/1721.1/34388) 239 | """) 240 | 241 | 242 | st.subheader("About this app") 243 | st.markdown(""" 244 | This app displays data from LIGO, Virgo, and GEO downloaded from 245 | the Gravitational Wave Open Science Center at https://gwosc.org . 246 | 247 | 248 | You can see how this works in the [Quickview Jupyter Notebook](https://github.com/losc-tutorial/quickview) or 249 | [see the code](https://github.com/jkanner/streamlit-dataview). 250 | 251 | """) 252 | --------------------------------------------------------------------------------