diff --git a/.ipynb_checkpoints/create_new_monitor-checkpoint.ipynb b/.ipynb_checkpoints/create_new_monitor-checkpoint.ipynb new file mode 100644 index 0000000..7fec515 --- /dev/null +++ b/.ipynb_checkpoints/create_new_monitor-checkpoint.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/.ipynb_checkpoints/dark_monitor_plots-checkpoint.ipynb b/.ipynb_checkpoints/dark_monitor_plots-checkpoint.ipynb new file mode 100644 index 0000000..cef3ff0 --- /dev/null +++ b/.ipynb_checkpoints/dark_monitor_plots-checkpoint.ipynb @@ -0,0 +1,689 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import yaml\n", + "import cosmo\n", + "import numpy\n", + "from itertools import repeat\n", + "from glob import glob\n", + "from astropy.io import fits\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import FormatStrFormatter\n", + "from cosmo.filesystem import find_files, data_from_exposures, data_from_jitters\n", + "from cosmo.monitor_helpers import absolute_time, explode_df\n", + "from monitorframe.datamodel import BaseDataModel\n", + "from monitorframe.monitor import BaseMonitor" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# load settings\n", + "with open(os.environ['COSMO_CONFIG']) as yamlfile:\n", + " SETTINGS = yaml.safe_load(yamlfile)\n", + "\n", + "FILES_SOURCE = SETTINGS['filesystem']['source']\n", + "COS_MONITORING = SETTINGS['output']" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# working DataModel\n", + "class DarkDataModel(BaseDataModel):\n", + " cosmo_layout = False\n", + "# fuv_program_ids = ['15771/', '15533/', '14940/', '14520/', '14436/', '13968/', '13521/', '13121/', '12716/', '12423/',\n", + "# '11895/']\n", + "# nuv_program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + "# '13528/', '13126/', '12720/', '12420/', '11894/']\n", + " fuv_program_ids = [\"15771/\"]\n", + " nuv_program_ids = [\"15776/\"]\n", + " program_id = fuv_program_ids + nuv_program_ids\n", + "\n", + " def get_new_data(self): # this way when you get new data it will get all the data\n", + " header_request = {\n", + " 0: ['ROOTNAME', 'SEGMENT'],\n", + " 1: ['EXPTIME', 'EXPSTART']\n", + " }\n", + " table_request = {\n", + " 1: ['PHA', 'XCORR', 'YCORR', 'TIME'],\n", + " 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + " }\n", + "\n", + " files = []\n", + "\n", + " for prog_id in self.program_id:\n", + " new_files_source = os.path.join(FILES_SOURCE, prog_id)\n", + " files += find_files('*corrtag*', data_dir=new_files_source)\n", + "\n", + " if self.model is not None:\n", + " currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)]\n", + "\n", + " for file in currently_ingested:\n", + " files.remove(file)\n", + "\n", + " if not files: # No new files\n", + " return pd.DataFrame()\n", + "\n", + " data_results = data_from_exposures(\n", + " files,\n", + " header_request=header_request,\n", + " table_request=table_request\n", + " )\n", + "\n", + " return data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# base DarkMonitor\n", + "def dark_filter(df_row, filter_pha, location):\n", + " good_pha = (2, 23)\n", + " # time step stuff\n", + " time_step = 25\n", + " time_bins = df_row['TIME_3'][::time_step]\n", + " lat = df_row['LATITUDE'][::time_step][:-1]\n", + " lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # try commenting these out, since lat and lon don't seem to be used\n", + "# lat = df_row['LATITUDE'][::time_step][:-1]\n", + "# lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # filtering pha\n", + " if filter_pha:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME'])\n", + " else:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'TIME'])\n", + " \n", + " # creating event dataframe and filtering it by location on the detector\n", + " npix = (location[1] - location[0]) * (location[3] - location[2])\n", + " index = np.where((event_df['XCORR'] > location[0]) &\n", + " (event_df['XCORR'] < location[1]) &\n", + " (event_df['YCORR'] > location[2]) &\n", + " (event_df['YCORR'] < location[3]))\n", + " filtered_row = event_df.iloc[index].reset_index(drop=True)\n", + "\n", + " #filtered events only need to be further filtered by PHA if not NUV\n", + " if filter_pha:\n", + " filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & (filtered_row['PHA'] < good_pha[1])]\n", + "\n", + " counts = np.histogram(filtered_row.TIME, bins=time_bins)[0]\n", + "\n", + " date = absolute_time(\n", + " expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), time=time_bins.tolist()\n", + " ).to_datetime()[:-1]\n", + "\n", + " dark_rate = counts / npix / time_step\n", + "\n", + " return pd.DataFrame({'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date],\n", + " 'ROOTNAME': df_row['ROOTNAME']})" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# overwriting the plot function and making subplot of dark vs. time in base monitor\n", + "from plotly.subplots import make_subplots\n", + "import plotly.graph_objects as go\n", + "import plotly.express as px\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " def plot(self):\n", + " # make the interactive plots with sub-solar plots\n", + " self.figure = px.scatter(\n", + " self.data,\n", + " x=self.x,\n", + " y=self.y,\n", + " color=self.z,\n", + " color_continuous_scale=px.colors.sequential.Viridis,\n", + " hover_data=self.labels,\n", + " )\n", + " \n", + " self.figure.update_layout(\n", + " coloraxis_colorbar_len=0.8,\n", + " coloraxis_colorbar_yanchor='bottom',\n", + " coloraxis_colorbar_y=0\n", + " )\n", + " \n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "class FUVAInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for inner region\"\"\"\n", + " name = 'FUVA Dark Monitor - Inner'\n", + " segment = 'FUVA'\n", + " location = (1260, 15119, 375, 660)\n", + " \n", + "class NUVDarkMonitor(DarkMonitor):\n", + " name = \"NUV Dark Monitor\"\n", + " segment = \"N/A\"\n", + " location = (0, 1024, 0, 1024)\n", + " \n", + "fuv_inner_monitor = FUVAInnerDarkMonitor()\n", + "fuv_inner_monitor.monitor()\n", + "\n", + "nuv_monitor = NUVDarkMonitor()\n", + "nuv_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "# turn plot into a subplot and add another empty plot\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " \n", + " def plot(self):\n", + " # make the interactive plots with sub-solar plots\n", + " self.figure = make_subplots(rows=2, cols=1, subplot_titles=(self.name, \"Solar Flux\"))\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=self.data[self.x], \n", + " y=self.data[self.y],\n", + " mode=\"markers\",\n", + " hovertext=self.labels,\n", + " hoverinfo=\"x+y+text\"), \n", + " row=1, col=1)\n", + " \n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + " \n", + "fuv_inner_monitor = FUVBInnerDarkMonitor()\n", + "fuv_inner_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.time import Time\n", + "import scipy\n", + "from ftplib import FTP\n", + "\n", + "def grab_solar_files(file_dir):\n", + " \"\"\"Pull solar data files from NOAA website\n", + " Solar data is FTPd from NOAA and written to text files for use in plotting\n", + " and monitoring of COS dark-rates and TDS.\n", + " Parameters\n", + " ----------\n", + " file_dir : str\n", + " Directory to write the files to\n", + " \"\"\"\n", + " ftp = FTP('ftp.swpc.noaa.gov')\n", + " ftp.login()\n", + "\n", + " ftp.cwd('/pub/indices/old_indices/')\n", + "\n", + " for item in sorted(ftp.nlst()):\n", + " if item.endswith('_DSD.txt'):\n", + " year = int(item[:4])\n", + " if year >= 2000:\n", + " destination = os.path.join(file_dir, item)\n", + " if not os.path.exists(destination):\n", + " ftp.retrbinary('RETR {}'.format(item),\n", + " open(destination, 'wb').write)\n", + "\n", + " os.chmod(destination, 0o777)\n", + "\n", + "\n", + "def compile_solar_data(file_dir):\n", + " \"\"\"Pull desired columns from solar data text files\n", + " Parameters\n", + " ----------\n", + " file_dir : str\n", + " Returns\n", + " -------\n", + " date : np.ndarray\n", + " mjd of each measurements\n", + " flux : np.ndarray\n", + " solar flux measurements\n", + " \"\"\"\n", + " date = []\n", + " flux = []\n", + " input_list = glob(os.path.join(file_dir, '*DSD.txt'))\n", + " input_list.sort()\n", + "\n", + " for item in input_list:\n", + " # clean up Q4 files when year-long file exists\n", + " if ('Q4_' in item) and os.path.exists(item.replace('Q4_', '_')):\n", + " try:\n", + " os.remove(item)\n", + " except PermissionError:\n", + " continue\n", + " continue # i know this is stupid\n", + "\n", + " # astropy.ascii no longer returns an empty table for empty files\n", + " # Throws IndexError, we will go around it if empty.\n", + " try:\n", + " data = ascii.read(item, data_start=1, comment='[#,:]')\n", + " except IndexError:\n", + " continue\n", + "\n", + " for line in data:\n", + " line_date = Time('{}-{}-{} 00:00:00'.format(line['col1'],\n", + " line['col2'],\n", + " line['col3']),\n", + " scale='utc', format='iso').mjd\n", + "\n", + " line_flux = line[3]\n", + "\n", + " if line_flux > 0:\n", + " date.append(line_date)\n", + " flux.append(line_flux)\n", + " \n", + " solar_date = np.array(date)\n", + " solar_flux = np.array(flux)\n", + " solar_dec = Time(solar_date, format='mjd').decimalyear\n", + " solar_smooth = scipy.convolve(solar_flux, np.ones(81)/81.0, mode=\"same\")\n", + " \n", + "\n", + " return solar_flux, solar_dec, solar_smooth\n" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [], + "source": [ + "# add solar flux data to second subplot\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " \n", + " def plot(self):\n", + " # make the interactive plots with sub-solar plots\n", + " self.figure = make_subplots(rows=2, cols=1, subplot_titles=(self.name, \"Solar Radio Flux\"))\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=self.data[self.x], \n", + " y=self.data[self.y],\n", + " mode=\"markers\",\n", + " hovertext=self.labels,\n", + " hoverinfo=\"x+y+text\", \n", + " name=\"Dark Rates\"), \n", + " row=1, col=1)\n", + " \n", + " grab_solar_files('/grp/hst/cos2/solar_data')\n", + " solar_flux, solar_dec, solar_smooth = compile_solar_data('/grp/hst/cos2/solar_data')\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_dec, \n", + " y=solar_flux,\n", + " mode=\"lines\",\n", + " name=\"10.7 cm\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_dec[:-41], \n", + " y=solar_smooth[:-41],\n", + " mode=\"lines\",\n", + " name=\"10.7 cm Smoothed\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " date_min = self.data[self.x].min()\n", + " date_max = self.data[self.x].max()\n", + " print(date_min, date_max)\n", + "# self.figure.update_layout(\n", + "# xaxis2=dict(\n", + "# )\n", + "# )\n", + " \n", + " self.figure['layout']['xaxis1'].update(title=\"Year\")\n", + " self.figure['layout']['yaxis1'].update(title=\"Dark Rate\")\n", + " self.figure['layout']['xaxis2'].update(title=\"Year\", range=[date_min, date_max])\n", + " self.figure['layout']['yaxis2'].update(title=\"Solar Radio Flux\")\n", + "\n", + " \n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cmagness/miniconda3/envs/cosmos/lib/python3.7/site-packages/ipykernel_launcher.py:79: DeprecationWarning:\n", + "\n", + "scipy.convolve is deprecated and will be removed in SciPy 2.0.0, use numpy.convolve instead\n", + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2019-11-04 04:15:03.769408 2020-04-20 07:43:56.284317\n" + ] + } + ], + "source": [ + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + " \n", + "fuv_inner_monitor = FUVBInnerDarkMonitor()\n", + "fuv_inner_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [], + "source": [ + "# add histogram plotting function\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " \n", + " def plot(self):\n", + " # make the interactive plots with sub-solar plots\n", + " self.figure = make_subplots(rows=2, cols=1, subplot_titles=(self.name, \"Solar Radio Flux\"))\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=self.data[self.x], \n", + " y=self.data[self.y],\n", + " mode=\"markers\",\n", + " hovertext=self.labels,\n", + " hoverinfo=\"x+y+text\", \n", + " name=\"Dark Rates\"), \n", + " row=1, col=1)\n", + " \n", + " grab_solar_files('/grp/hst/cos2/solar_data')\n", + " solar_flux, solar_dec, solar_smooth = compile_solar_data('/grp/hst/cos2/solar_data')\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_dec, \n", + " y=solar_flux,\n", + " mode=\"lines\",\n", + " name=\"10.7 cm\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_dec[:-41], \n", + " y=solar_smooth[:-41],\n", + " mode=\"lines\",\n", + " name=\"10.7 cm Smoothed\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " date_min = self.data[self.x].min()\n", + " date_max = self.data[self.x].max()\n", + " print(date_min, date_max)\n", + " \n", + " self.figure['layout']['xaxis1'].update(title=\"Year\")\n", + " self.figure['layout']['yaxis1'].update(title=\"Dark Rate\")\n", + " self.figure['layout']['xaxis2'].update(title=\"Year\", range=[date_min, date_max])\n", + " self.figure['layout']['yaxis2'].update(title=\"Solar Radio Flux\")\n", + "\n", + " \n", + " def plot_histogram(self, nbins=30):\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " # self.data[self.y] should be all dark rates\n", + " counts, bins = np.histogram(self.data[self.y], bins=nbins)\n", + " cuml_dist = np.cumsum(counts)\n", + " count_99 = abs(cuml_dist / float(cuml_dist.max()) - .99).argmin()\n", + " count_95 = abs(cuml_dist / float(cuml_dist.max()) - .95).argmin()\n", + " \n", + " mean = self.data[self.y].mean()\n", + " med = np.median(self.data[self.y])\n", + " std = self.data[self.y].std() \n", + " onesig = med + std\n", + " twosig = med + (2 * std)\n", + " threesig = med + (3 * std)\n", + " dist95 = bins[count_95]\n", + " dist99 = bins[count_99]\n", + " lines = [mean, med, onesig, twosig, threesig, dist95, dist99]\n", + " \n", + " fig = go.Figure(data=[go.Histogram(x=self.data[self.y], nbinsx=nbins)])\n", + " for value in lines:\n", + " fig.add_shape(\n", + " dict(type=\"line\",\n", + " xref=\"x\",\n", + " yref=\"paper\",\n", + " x0=value,\n", + " y0=0,\n", + " x1=value,\n", + " y1=1) \n", + " )\n", + " \n", + " # fix this naming convention later\n", + " output = os.path.join(COS_MONITORING, \"FUVBDarkMonitor-Inner_hist_2020-05-15.html\") \n", + " fig.write_html(output)\n", + " \n", + " \n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [], + "source": [ + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + "\n", + "fuv_inner_monitor = FUVBInnerDarkMonitor()\n", + "fuv_inner_monitor.plot_histogram()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/.ipynb_checkpoints/nuv_dark_monitor-checkpoint.ipynb b/.ipynb_checkpoints/nuv_dark_monitor-checkpoint.ipynb new file mode 100644 index 0000000..30195bb --- /dev/null +++ b/.ipynb_checkpoints/nuv_dark_monitor-checkpoint.ipynb @@ -0,0 +1,905 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import yaml\n", + "import cosmo\n", + "import numpy\n", + "import itertools\n", + "from glob import glob\n", + "from astropy.io import fits\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import FormatStrFormatter\n", + "from cosmo.monitors.data_models import NUVDarkDataModel\n", + "from cosmo.filesystem import find_files, data_from_exposures, data_from_jitters\n", + "from cosmo.monitor_helpers import absolute_time, explode_df\n", + "from monitorframe.datamodel import BaseDataModel\n", + "from monitorframe.monitor import BaseMonitor" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "with open(os.environ['COSMO_CONFIG']) as yamlfile:\n", + " SETTINGS = yaml.safe_load(yamlfile)\n", + "\n", + "FILES_SOURCE = SETTINGS['filesystem']['source']\n", + "COS_MONITORING = SETTINGS['output']" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Filename: /grp/hst/cos2/cosmo/15776/le090edhq_corrtag.fits.gz\n", + "No. Name Ver Type Cards Dimensions Format\n", + " 0 PRIMARY 1 PrimaryHDU 166 () \n", + " 1 EVENTS 1 BinTableHDU 250 1440318R x 12C [1E, 1I, 1I, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1I, 1B] \n", + " 2 GTI 1 BinTableHDU 22 1R x 2C [1D, 1D] \n", + " 3 TIMELINE 1 BinTableHDU 264 1331R x 12C [1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E] \n" + ] + }, + { + "data": { + "text/plain": [ + "ColDefs(\n", + " name = 'TIME'; format = '1E'; unit = 's'; disp = 'F8.3'\n", + " name = 'LONGITUDE'; format = '1E'; unit = 'degree'; disp = 'F10.6'; coord_type = 'RA---TAN'; coord_unit = 'deg'; coord_ref_point = 512.0; coord_ref_value = 189.955952071\n", + " name = 'LATITUDE'; format = '1E'; unit = 'degree'; disp = 'F10.6'; coord_type = 'DEC--TAN'; coord_unit = 'deg'; coord_ref_point = 512.0; coord_ref_value = -52.86080947004\n", + " name = 'SUN_ALT'; format = '1E'; unit = 'degree'; disp = 'F6.2'\n", + " name = 'SUN_ZD'; format = '1E'; unit = 'degree'; disp = 'F6.2'\n", + " name = 'TARGET_ALT'; format = '1E'; unit = 'degree'; disp = 'F6.2'\n", + " name = 'RADIAL_VEL'; format = '1E'; unit = 'km /s'; disp = 'F7.5'\n", + " name = 'SHIFT1'; format = '1E'; unit = 'pixel'; disp = 'F7.3'\n", + " name = 'LY_ALPHA'; format = '1E'; unit = 'count /s'; disp = 'G15.6'\n", + " name = 'OI_1304'; format = '1E'; unit = 'count /s'; disp = 'G15.6'\n", + " name = 'OI_1356'; format = '1E'; unit = 'count /s'; disp = 'G15.6'\n", + " name = 'DARKRATE'; format = '1E'; unit = 'count /s /pixel'; disp = 'G15.6'\n", + ")" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "testfile = FILES_SOURCE + \"/15776/le090edhq_corrtag.fits.gz\"\n", + "with fits.open(testfile) as f:\n", + " f.info()\n", + " data = f[\"EVENTS\"].data\n", + " timeline = f[\"TIMELINE\"].data\n", + " \n", + "data.columns \n", + "timeline.columns" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "class NUVDarkDataModel(BaseDataModel):\n", + " \"\"\"Datamodel for NUV Dark files.\"\"\"\n", + " files_source = FILES_SOURCE\n", + " subdir_pattern = '?????'\n", + "\n", + " def get_new_data(self):\n", + " header_request = {\n", + " 0: ['ROOTNAME'],\n", + " 1: ['EXPSTART','EXPTIME']\n", + " }\n", + "\n", + " table_request = {\n", + " 1: ['TIME','XCORR','YCORR'],\n", + " 3: ['TIME','LATITUDE','LONGITUDE','DARKRATE']\n", + " }\n", + "\n", + " # any special data requests\n", + " # TODO: add spt support for temp, sun_lat, sun_long\n", + " # TODO: is this a good place to add solar data scraping in the future?\n", + "\n", + " # this is temporary to find the files from the dark programs until\n", + " # we can add the dark files to the monitor_data database\n", + " files = []\n", + " # 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17\n", + " program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + " '13528/', '13126/', '12720/', '12420/', '11894/']\n", + "# program_ids = ['15538/']\n", + " for program in program_ids:\n", + " new_files_source = os.path.join(FILES_SOURCE, program)\n", + " subfiles = glob(os.path.join(new_files_source, \"*corrtag*\"))\n", + " files += subfiles\n", + "\n", + " if not files: # No new files\n", + " return pd.DataFrame()\n", + "\n", + " # need to add any other keywords that need to be set\n", + " data_results = data_from_exposures(files,\n", + " header_request=header_request,\n", + " table_request=table_request)\n", + "\n", + " return data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ROOTNAMEEXPSTARTEXPTIMETIMEXCORRYCORRTIME_3LATITUDELONGITUDEDARKRATEFILENAME
0le0905b0q58819.1667181330.208[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...[885.0, 997.0, 347.0, 553.0, 114.0, 535.0, 784...[906.0, 881.0, 139.0, 68.0, 423.0, 762.0, 370....[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-18.725433, -18.702103, -18.678751, -18.65538...[21.055735, 21.113192, 21.170631, 21.228054, 2...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/15776/le0905b0q_corrtag.fi...
1le090ahtq58853.1303811330.208[0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0.032, 0.03...[91.0, 709.0, 745.0, 625.0, 1003.0, 254.0, 434...[513.0, 907.0, 243.0, 348.0, 476.0, 1001.0, 70...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-2.6593285, -2.6892054, -2.7190795, -2.748951...[0.16533001, 0.2166269, 0.26792637, 0.31922853...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/15776/le090ahtq_corrtag.fi...
2le090ldoq58936.5273261330.208[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...[140.0, 643.0, 812.0, 995.0, 461.0, 538.0, 830...[915.0, 752.0, 1011.0, 902.0, 539.0, 1017.0, 5...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-28.539606, -28.53875, -28.537857, -28.536926...[29.865618, 29.933025, 30.000433, 30.067837, 3...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/15776/le090ldoq_corrtag.fi...
3le0903bgq58805.1459541330.176[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...[73.0, 726.0, 521.0, 305.0, 35.0, 1019.0, 668....[931.0, 736.0, 737.0, 863.0, 917.0, 399.0, 308...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-23.0323, -23.01384, -22.995354, -22.97684, -...[122.1993, 122.26049, 122.321655, 122.38281, 1...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/15776/le0903bgq_corrtag.fi...
4le090hbhq58903.1459831330.208[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0...[844.0, 984.0, 367.0, 996.0, 44.0, 674.0, 383....[751.0, 901.0, 255.0, 239.0, 570.0, 420.0, 292...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-14.438115, -14.411661, -14.385194, -14.35871...[121.25305, 121.307846, 121.362625, 121.41739,...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/15776/le090hbhq_corrtag.fi...
....................................
595lb8r3khgq55487.5495591330.176[0.0, 0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0.032,...[742.0, 905.0, 340.0, 53.0, 980.0, 98.0, 901.0...[274.0, 726.0, 754.0, 382.0, 63.0, 444.0, 240....[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[10.690754, 10.662744, 10.634723, 10.606691, 1...[89.07274, 89.125565, 89.178375, 89.23118, 89....[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/11894/lb8r3khgq_corrtag.fi...
596lb8r1ce4q55207.9058831330.176[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...[812.0, 472.0, 585.0, 712.0, 396.0, 478.0, 498...[496.0, 695.0, 635.0, 550.0, 353.0, 259.0, 594...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-26.498741, -26.487095, -26.475414, -26.46370...[35.329605, 35.394123, 35.45863, 35.523125, 35...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/11894/lb8r1ce4q_corrtag.fi...
597lb8r3ic6q55480.5576151330.208[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0...[888.0, 835.0, 909.0, 848.0, 431.0, 239.0, 80....[384.0, 529.0, 539.0, 580.0, 627.0, 295.0, 803...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[27.989649, 27.984045, 27.978405, 27.972727, 2...[79.8855, 79.95197, 80.01843, 80.08489, 80.151...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/11894/lb8r3ic6q_corrtag.fi...
598lb8r2cixq55334.4542771330.176[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...[831.0, 692.0, 60.0, 931.0, 808.0, 712.0, 30.0...[210.0, 214.0, 427.0, 440.0, 825.0, 227.0, 242...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-10.666136, -10.638098, -10.61005, -10.581992...[13.996996, 14.049734, 14.102462, 14.155179, 1...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/11894/lb8r2cixq_corrtag.fi...
599lb8r3mefq55494.6222901330.176[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...[117.0, 269.0, 524.0, 985.0, 279.0, 289.0, 727...[943.0, 905.0, 765.0, 493.0, 321.0, 973.0, 947...[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ...[-26.237272, -26.224733, -26.21216, -26.199556...[144.99263, 145.0567, 145.12076, 145.1848, 145...[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, .../grp/hst/cos2/cosmo/11894/lb8r3mefq_corrtag.fi...
\n", + "

600 rows × 11 columns

\n", + "
" + ], + "text/plain": [ + " ROOTNAME EXPSTART EXPTIME \\\n", + "0 le0905b0q 58819.166718 1330.208 \n", + "1 le090ahtq 58853.130381 1330.208 \n", + "2 le090ldoq 58936.527326 1330.208 \n", + "3 le0903bgq 58805.145954 1330.176 \n", + "4 le090hbhq 58903.145983 1330.208 \n", + ".. ... ... ... \n", + "595 lb8r3khgq 55487.549559 1330.176 \n", + "596 lb8r1ce4q 55207.905883 1330.176 \n", + "597 lb8r3ic6q 55480.557615 1330.208 \n", + "598 lb8r2cixq 55334.454277 1330.176 \n", + "599 lb8r3mefq 55494.622290 1330.176 \n", + "\n", + " TIME \\\n", + "0 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "1 [0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0.032, 0.03... \n", + "2 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "3 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "4 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0... \n", + ".. ... \n", + "595 [0.0, 0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0.032,... \n", + "596 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "597 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.032, 0.032, 0... \n", + "598 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "599 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "\n", + " XCORR \\\n", + "0 [885.0, 997.0, 347.0, 553.0, 114.0, 535.0, 784... \n", + "1 [91.0, 709.0, 745.0, 625.0, 1003.0, 254.0, 434... \n", + "2 [140.0, 643.0, 812.0, 995.0, 461.0, 538.0, 830... \n", + "3 [73.0, 726.0, 521.0, 305.0, 35.0, 1019.0, 668.... \n", + "4 [844.0, 984.0, 367.0, 996.0, 44.0, 674.0, 383.... \n", + ".. ... \n", + "595 [742.0, 905.0, 340.0, 53.0, 980.0, 98.0, 901.0... \n", + "596 [812.0, 472.0, 585.0, 712.0, 396.0, 478.0, 498... \n", + "597 [888.0, 835.0, 909.0, 848.0, 431.0, 239.0, 80.... \n", + "598 [831.0, 692.0, 60.0, 931.0, 808.0, 712.0, 30.0... \n", + "599 [117.0, 269.0, 524.0, 985.0, 279.0, 289.0, 727... \n", + "\n", + " YCORR \\\n", + "0 [906.0, 881.0, 139.0, 68.0, 423.0, 762.0, 370.... \n", + "1 [513.0, 907.0, 243.0, 348.0, 476.0, 1001.0, 70... \n", + "2 [915.0, 752.0, 1011.0, 902.0, 539.0, 1017.0, 5... \n", + "3 [931.0, 736.0, 737.0, 863.0, 917.0, 399.0, 308... \n", + "4 [751.0, 901.0, 255.0, 239.0, 570.0, 420.0, 292... \n", + ".. ... \n", + "595 [274.0, 726.0, 754.0, 382.0, 63.0, 444.0, 240.... \n", + "596 [496.0, 695.0, 635.0, 550.0, 353.0, 259.0, 594... \n", + "597 [384.0, 529.0, 539.0, 580.0, 627.0, 295.0, 803... \n", + "598 [210.0, 214.0, 427.0, 440.0, 825.0, 227.0, 242... \n", + "599 [943.0, 905.0, 765.0, 493.0, 321.0, 973.0, 947... \n", + "\n", + " TIME_3 \\\n", + "0 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "1 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "2 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "3 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "4 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + ".. ... \n", + "595 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "596 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "597 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "598 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "599 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ... \n", + "\n", + " LATITUDE \\\n", + "0 [-18.725433, -18.702103, -18.678751, -18.65538... \n", + "1 [-2.6593285, -2.6892054, -2.7190795, -2.748951... \n", + "2 [-28.539606, -28.53875, -28.537857, -28.536926... \n", + "3 [-23.0323, -23.01384, -22.995354, -22.97684, -... \n", + "4 [-14.438115, -14.411661, -14.385194, -14.35871... \n", + ".. ... \n", + "595 [10.690754, 10.662744, 10.634723, 10.606691, 1... \n", + "596 [-26.498741, -26.487095, -26.475414, -26.46370... \n", + "597 [27.989649, 27.984045, 27.978405, 27.972727, 2... \n", + "598 [-10.666136, -10.638098, -10.61005, -10.581992... \n", + "599 [-26.237272, -26.224733, -26.21216, -26.199556... \n", + "\n", + " LONGITUDE \\\n", + "0 [21.055735, 21.113192, 21.170631, 21.228054, 2... \n", + "1 [0.16533001, 0.2166269, 0.26792637, 0.31922853... \n", + "2 [29.865618, 29.933025, 30.000433, 30.067837, 3... \n", + "3 [122.1993, 122.26049, 122.321655, 122.38281, 1... \n", + "4 [121.25305, 121.307846, 121.362625, 121.41739,... \n", + ".. ... \n", + "595 [89.07274, 89.125565, 89.178375, 89.23118, 89.... \n", + "596 [35.329605, 35.394123, 35.45863, 35.523125, 35... \n", + "597 [79.8855, 79.95197, 80.01843, 80.08489, 80.151... \n", + "598 [13.996996, 14.049734, 14.102462, 14.155179, 1... \n", + "599 [144.99263, 145.0567, 145.12076, 145.1848, 145... \n", + "\n", + " DARKRATE \\\n", + "0 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "1 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "2 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "3 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "4 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + ".. ... \n", + "595 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "596 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "597 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "598 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "599 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... \n", + "\n", + " FILENAME \n", + "0 /grp/hst/cos2/cosmo/15776/le0905b0q_corrtag.fi... \n", + "1 /grp/hst/cos2/cosmo/15776/le090ahtq_corrtag.fi... \n", + "2 /grp/hst/cos2/cosmo/15776/le090ldoq_corrtag.fi... \n", + "3 /grp/hst/cos2/cosmo/15776/le0903bgq_corrtag.fi... \n", + "4 /grp/hst/cos2/cosmo/15776/le090hbhq_corrtag.fi... \n", + ".. ... \n", + "595 /grp/hst/cos2/cosmo/11894/lb8r3khgq_corrtag.fi... \n", + "596 /grp/hst/cos2/cosmo/11894/lb8r1ce4q_corrtag.fi... \n", + "597 /grp/hst/cos2/cosmo/11894/lb8r3ic6q_corrtag.fi... \n", + "598 /grp/hst/cos2/cosmo/11894/lb8r2cixq_corrtag.fi... \n", + "599 /grp/hst/cos2/cosmo/11894/lb8r3mefq_corrtag.fi... \n", + "\n", + "[600 rows x 11 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nuv_model = NUVDarkDataModel()\n", + "data_results = nuv_model.get_new_data()\n", + "data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "class NUVDarkMonitor(BaseMonitor):\n", + " data_model = NUVDarkDataModel\n", + " # TODO: update docs\n", + " # docs = \"https://spacetelescope.github.io/cosmo/monitors.html#\"\n", + " output = COS_MONITORING\n", + "\n", + " run = 'monthly'\n", + " \n", + " def get_data(self):\n", + " # access data, perform any filtering required for analysis\n", + " data = self.model.new_data\n", + " dark_rate_column = []\n", + " dec_year_column = []\n", + " \n", + " xlim = [0, 1024]\n", + " ylim = [0, 1024]\n", + " \n", + " # parallelize, this is going to get bad when looking at a lot of data\n", + " for index, row in data.iterrows():\n", + " subdf = pd.DataFrame({\n", + " \"EXPSTART\": row[\"EXPSTART\"],\n", + " \"TIME\": [row[\"TIME\"]],\n", + " \"XCORR\": [row[\"XCORR\"]],\n", + " \"YCORR\": [row[\"YCORR\"]],\n", + " \"TIME_3\": [row[\"TIME_3\"]]\n", + " })\n", + " \n", + " # this is temporary until i understand james' suggestion to use df.apply and lambda functions\n", + " xcorr = subdf[\"XCORR\"][0]\n", + " ycorr = subdf[\"YCORR\"][0]\n", + " filtered_xcorr = xcorr[np.where((xcorr > xlim[0]) & (xcorr < xlim[1]))]\n", + " filtered_ycorr = ycorr[np.where((ycorr > ylim[0]) & (ycorr < ylim[1]))]\n", + " subdf[\"XCORR\"] = [filtered_xcorr]\n", + " subdf[\"YCORR\"] = [filtered_ycorr]\n", + " \n", + " dark_rate_array, dec_year_array = self.calculate_dark_rate(subdf, xlim, ylim)\n", + " dark_rate_column.append(dark_rate_array)\n", + " dec_year_column.append(dec_year_array)\n", + " \n", + " data[\"DARK_RATE\"] = dark_rate_column\n", + " data[\"DECIMAL_YEAR\"] = dec_year_column\n", + " \n", + " # when the monitor method of the monitor is called, it will initialize the self.data attribute\n", + " # with this method and then can be used by the other methods\n", + " return data\n", + " \n", + " def calculate_dark_rate(self, dataframe, xlim, ylim):\n", + " # calculate dark rate for one exposure, with a dataframe with TIME, XCORR, and YCORR values\n", + " \n", + " # need to set this somewhere\n", + " timestep = 25\n", + " time_bins = dataframe[\"TIME_3\"][0][::timestep]\n", + " \n", + " counts = np.histogram(dataframe[\"TIME\"][0], bins=time_bins)[0]\n", + " npix = float((xlim[1] - xlim[0]) * (ylim[1] - ylim[0]))\n", + " dark_rate_array = counts / npix / timestep\n", + " # save the whole histogram in time bins, and then plot each of them\n", + " \n", + " # make a decimal year array corresponding to the time bins of the dark rates\n", + " # do this with the expstart (mjd) and time array from the timeline extension\n", + " # taking the expstart, binning the time array by the timestep, \n", + " # removing the last element in the array (bin by front edge),\n", + " # and then multiplying by the conversion factor\n", + " # this is done by the absolute_time helper function\n", + " mjd_array = absolute_time(expstart=dataframe['EXPSTART'][0], time=dataframe['TIME_3'][0][::timestep][:-1])\n", + " dec_year_array = mjd_array.decimalyear\n", + "\n", + " return dark_rate_array, dec_year_array\n", + "\n", + " def track(self):\n", + " # track something. perhaps current dark rate?\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " plotdf = self.data[['DECIMAL_YEAR', 'DARK_RATE']]\n", + " \n", + " # i can't get explode_df to work so this is for now\n", + " # i think i know why it doesn't work and i fixed it i just haven't switched to using it\n", + " all_dec_year = []\n", + " all_dark_rates = []\n", + " for index, row in plotdf.iterrows():\n", + " all_dec_year = list(itertools.chain(all_dec_year, row[\"DECIMAL_YEAR\"]))\n", + " all_dark_rates = list(itertools.chain(all_dark_rates, row[\"DARK_RATE\"]))\n", + " \n", + " dark_counts = np.asarray(all_dark_rates)\n", + " fig = plt.figure(figsize=(12, 9))\n", + " # bin_size = 1e-5\n", + " # n_bins = int((dark_counts.max() - dark_counts.min()) / bin_size)\n", + " n_bins = 30\n", + " ax = fig.add_subplot(2, 1, 1)\n", + " ax.hist(dark_counts, bins=n_bins, align='mid', histtype='stepfilled')\n", + " counts, bins = np.histogram(dark_counts, bins=n_bins)\n", + " cuml_dist = np.cumsum(counts)\n", + " count_99 = abs(cuml_dist / float(cuml_dist.max()) - .99).argmin()\n", + " count_95 = abs(cuml_dist / float(cuml_dist.max()) - .95).argmin()\n", + "\n", + " mean = dark_counts.mean()\n", + " med = np.median(dark_counts)\n", + " std = dark_counts.std()\n", + " mean_obj = ax.axvline(x=mean, lw=2, ls='--', color='r', label='Mean ')\n", + " med_obj = ax.axvline(x=med, lw=2, ls='-', color='r', label='Median')\n", + " two_sig = ax.axvline(x=med + (2 * std), lw=2, ls='-', color='gold')\n", + " three_sig = ax.axvline(x=med + (3 * std), lw=2, ls='-', color='DarkOrange')\n", + " dist_95 = ax.axvline(x=bins[count_95], lw=2, ls='-', color='LightGreen')\n", + " dist_99 = ax.axvline(x=bins[count_99], lw=2, ls='-', color='DarkGreen')\n", + "\n", + " ax.grid(True, which='both')\n", + " ax.set_title('Histogram of Dark Rates', fontsize=15, fontweight='bold')\n", + " ax.set_ylabel('Frequency', fontsize=15, fontweight='bold')\n", + " ax.set_xlabel('Counts/pix/sec', fontsize=15, fontweight='bold')\n", + " ax.set_xlim(dark_counts.min(), dark_counts.max())\n", + " ax.xaxis.set_major_formatter(FormatStrFormatter('%3.2e'))\n", + "\n", + " ax = fig.add_subplot(2, 1, 2)\n", + " # log_bins = np.logspace(np.log10(dark.min()), np.log10(dark.max()), 100)\n", + " ax.hist(dark_counts, bins=n_bins, align='mid', log=True, histtype='stepfilled')\n", + "\n", + " ax.axvline(x=mean, lw=2, ls='--', color='r', label='Mean')\n", + " ax.axvline(x=med, lw=2, ls='-', color='r', label='Median')\n", + " ax.axvline(x=med + (2 * std), lw=2, ls='-', color='gold')\n", + " ax.axvline(x=med + (3 * std), lw=2, ls='-', color='DarkOrange')\n", + " ax.axvline(x=bins[count_95], lw=2, ls='-', color='LightGreen')\n", + " ax.axvline(x=bins[count_99], lw=2, ls='-', color='DarkGreen')\n", + "\n", + " # ax.set_xscale('log')\n", + " ax.grid(True, which='both')\n", + " ax.set_ylabel('Log Frequency', fontsize=15, fontweight='bold')\n", + " ax.set_xlabel('Counts/pix/sec', fontsize=15, fontweight='bold')\n", + " ax.set_xlim(dark_counts.min(), dark_counts.max())\n", + " ax.xaxis.set_major_formatter(FormatStrFormatter('%3.2e'))\n", + "\n", + " fig.legend([med_obj, mean_obj, two_sig, three_sig, dist_95, dist_99],\n", + " ['Median: {0:.2e}'.format(med),\n", + " 'Mean: {0:.2e}'.format(mean),\n", + " r'2$\\sigma$: {0:.2e}'.format(med + (2 * std)),\n", + " r'3$\\sigma$: {0:.2e}'.format(med + (3 * std)),\n", + " r'95$\\%$: {0:.2e}'.format(bins[count_95]),\n", + " r'99$\\%$: {0:.2e}'.format(bins[count_99])],\n", + " shadow=True,\n", + " numpoints=1,\n", + " bbox_to_anchor=[0.8, 0.8])\n", + "\n", + "\n", + " def plot(self):\n", + " # select the important columns from the dataframe\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " plotdf = pd.DataFrame({\n", + " \"DECIMAL_YEAR\": self.data[\"DECIMAL_YEAR\"],\n", + " \"DARK_RATE\": self.data[\"DARK_RATE\"]\n", + " })\n", + " \n", + " # i can't get explode_df to work so this is for now\n", + " all_dec_year = []\n", + " all_dark_rates = []\n", + " for index, row in plotdf.iterrows():\n", + " all_dec_year = list(itertools.chain(all_dec_year, row[\"DECIMAL_YEAR\"]))\n", + " all_dark_rates = list(itertools.chain(all_dark_rates, row[\"DARK_RATE\"]))\n", + " \n", + " # not sure what is happening here tbh, still figuring it out\n", + "# self.x = all_dec_year\n", + "# self.y = all_dark_rates\n", + " \n", + "# self.basic_scatter()\n", + " \n", + " fig = plt.figure(figsize=(12, 9))\n", + " plt.scatter(all_dec_year, all_dark_rates)\n", + " # plt.xlim(min(all_dec_year), max(all_dec_year))\n", + " plt.ylim(0, max(all_dark_rates) + 0.5e-5)\n", + " plt.ticklabel_format(axis='y', style='sci', scilimits=(-2, 2))\n", + " plt.ticklabel_format(axis='x', style='plain')\n", + " plt.xlabel(\"Decimal Year\")\n", + " plt.ylabel(\"Dark Rate (c/p/s)\")\n", + " plt.grid(True)\n", + "\n", + " def store_results(self):\n", + " # need to store results if not going in the database\n", + " pass\n", + "\n", + "nuv_monitor = NUVDarkMonitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtcAAAIhCAYAAACFRZZZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOzdf3hU53kn/O89wwgGDBbYMQkTYyhlSUoEqOYNOHR3hbcEx65dhdghBLJN39Tefdv6evkRJVKgAac4sFGD2W7bbe33TdO9TGwMwbN2RazSOmr2JYbGzkgodK0QbPxjSExiLGxgADF63j9GR8yMzjNzzsz5OfP9XBeXrWek0aM58+M+z7mf+xalFIiIiIiIqHoRvydARERERFQrGFwTERERETmEwTURERERkUMYXBMREREROYTBNRERERGRQxhcExERERE5pOaCaxH5loicEZGfOHBft4jISyLSKyLHReQ/OzFHIiIiIqpNUmt1rkXk3wE4D+B/KKU+UuV9NSD3GF0WkesA/ATAx5RSpx2YKhERERHVmJpbuVZK/QDA2fwxEZkjIs+NrEL/LxH5kMX7uqKUujzy5XjU4ONFRERERM6pl2DxUQAPKqVuBfBFAH9l9QdF5GYROQbgDQD/havWRERERKQzzu8JuG0kneNjAPaJiDE8fuS2VQC+ZvJjaaXUSgBQSr0BYIGIzACQFJH9Sqm33J85EREREYVNzQfXyK3ODyqlFhXfoJQ6AOCAlTtRSp0WkeMA/i2A/c5OkYiIiIhqQc2nhSil3gXwqojcBwCSs9DKz4rIB0UkPvL/UwEsAzDg2mSJiIiIKNRqLrgWkScAvABgnoi8KSJfALAWwBdEpA/AcQC/a/HuPgzg6MjP/TOAP1NK9bsxbyIiIiIKv5orxUdERERE5JeaW7kmIiIiIvILg2siIiIiIofUVLWQG2+8Uc2aNcvvadhy4cIFTJo0qeT3vPyL9zCUHR4zHotG8KH3T3ZralQBK8eTwoPHs7bweNYWHs/aErbj+dJLL/1KKfU+s9tqKrieNWsWXnzxRb+nYUtPTw9aWlpKfs/s9i6YZcYLgBd33uXGtKhCVo4nhQePZ23h8awtPJ61JWzHU0Re093GtJAQmNEYtzVORERERP5gcB0CbSvnIR6LFozFY1G0rZzn04yIiIiIyExNpYXUqtbmBACgs3sApwczmNEYR9vKeaPjRERERBQMDK5DorU5wWCaiIiIKOCYFkJERERE5BAG10REREREDmFwTURERETkEOZch0QyleaGRiIiIqKAY3AdAslUGh0H+pEZygIA0oMZdBzoBwAG2EREREQBwrSQEOjsHhgNrA2ZoSw6uwd8mhERERERmWFwHQKnBzO2xomIiIjIHwyuQ4Dtz4mIiIjCgcF1CLD9OREREVE4cENjCLD9OREREVE4MLgOCbY/JyIiIgo+poUQERERETmEwTURERERkUMYXBMREREROYTBNRERERGRQxhcExERERE5hNVCQiKZSrMUHxEREVHAMbgOgWQqjY4D/cgMZQEA6cEMOg70AwADbCIiIqIAYVpICHR2D4wG1obMUBad3QM+zYiIiIiIzDC4DoHTgxlb40RERETkDwbXITCjMW5rnIiIiIj8weA6BNpWzkM8Fi0Yi8eiaFs5z6cZEREREZEZbmgMAWPTIquFEBEREQUbg+uQaG1OMJgmIiIiCjimhRAREREROYTBNRERERGRQxhcExERERE5hME1EREREZFDGFwTERERETmEwTURERERkUMYXBMREREROYTBNRERERGRQxhcExERERE5hME1EREREZFDGFwTERERETmEwTURERERkUMYXBMREREROWSc3xMga5KpNDq7B3B6MIMZjXG0rZyH1uaE39MiIiIiojwMrkMgmUqj40A/MkNZAEB6MIOOA/0AwACbiIiIKECYFhICnd0Do4G1ITOURWf3gE8zIiIiIiIzDK5D4PRgxtY4EREREfmDwXUIzGiM2xonIiIiIn8wuA6BtpXzEI9FC8bisSjaVs7zaUZEREREZIYbGkPA2LTIaiFEREREwcbgOiRamxMMpomIiIgCzrXgWkS+BeB3AJxRSn3E5PY2AGvz5vFhAO9TSp0VkVMA3gOQBXBVKbXYrXkSERERETnFzZzrbwO4Q3ejUqpTKbVIKbUIQAeAf1ZKnc37luUjtzOwJiIiIqJQcC24Vkr9AMDZst+YswbAE27NhYiIiIjIC6KUcu/ORWYB+HuztJC875kI4E0Av26sXIvIqwDeAaAA/I1S6tESP/8AgAcAYPr06bc++eSTjs3fC+fPn8d1113n9zTIITyetYXHs7bweNYWHs/aErbjuXz58pd02RVB2NB4N4DDRSkh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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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duwY1hj279/PjTfeSEZGBj/84Q9LnjZZ1m9+8xuWLFlCbGwsTz31FA8++CDPPfcczzzzDG3btmXr1q2kpaVxxx13sHDhwmofv6r9b731Vi677LKTPs+GTj3fIiIiIiFw2WWXsXy51ylQ9vHthw4dYuLEifTt25cLL7yQJUuWAN7TJn/605/Su3dvevfuzbvvvgscf6DQVVddRbdu3bjmmmuo6sGIf//737nyyiv54Q9/CEBUVFS525kZBw4cALxH13fo0AGAJUuWMH78eACuuuoqVq5ciXOOoqIibr/9dvr27Ut8fDx//etfy623ov0BXnnlFbp06UJcXFwVrdj4KfkWiVS9e3svEZHGxqzi15w5x7ebM6fybYM0ZswY0tLSyM/PZ8OGDfTr169k3UMPPcSgQYNYt24dq1at4vbbb+fQoUNERUWxYsUKPvzwQxYuXMhvf/vbkn3Wr1/PzJkz2bRpE9u2bWP16tVAxY+B//TTT9m3bx/JyckkJibywgsvlBvnvHnzSElJITo6mhdffJE777wTgB07dtCpUycAmjVrxhlnnMGePXt45plnOOOMM1i3bh3r1q1j7ty5fP755yfUW9H+hw4d4uGHH+bee+8Nuk0bIw07EYlUxY+XFxGRkIiPjyc3N5fU1FRSUlJKrXvjjTdYunRpyXjn/Px8vvzySzp06MBNN91EdnY2TZs25dNPPy3ZJykpiejoaAASEhLIzc1lwIABPPDAA+Uev7CwkKysLFauXMnhw4e56KKL6N+/P//1X/9VarvHHnuM9PR0+vXrx5///Gd+97vfMW/evHJ71s2MN954gw0bNrBo0SLA6y3fsmULP/rRj0ptW9H+9957L7feeiutW7euqgkjgpJvERERaVyqGJ5RohYeNjZ8+HB+//vfk5mZyZ49ewJCcvzzn/+ka9eupba/7777aNeuHTk5ORw7doxTTjmlZF3Lli1L3jdt2pTCwsJKjx0dHc3ZZ5/NaaedxmmnncbAgQPJyckplXx//fXX5OTklPTKjx49mmHDhpXsv337dqKjoyksLOTbb7/lzDPPxDnHE088wdChQ0sdb9q0aSXDbLKzsyvc//3332fRokX84Q9/YP/+/TRp0oRTTjmFm266KZimbTQ07EREREQkRCZOnMg999xDz549S5UPHTqUJ554oqR3eP369YDXi9y+fXuaNGnC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H7JjZmarRcQjrPkAnp7Yt5lZWZx2YmZmZmZWEqedmJmZmZmVxD3fZmZmZmYlcePbzMzMzKwkbnybmZmZmZXEjW8zMzMzs5K48W1mZmZmVhI3vs3MzMzMStIzD9kZN25cTJo0qdXVqOmPf/wjG264Yaur0TMcb+B3v8um48c3d7sLFmTTadNWFznesPKvKwHYfMzmhe+rWfFe8FD2WU577bRhluwyz6dz+BXDH7fP7RF4PMV3QuPnleNdLsd75BYsWPD7iKj6H2zPjPO92267xZ133tnqatTU399PX19fq6vRMxzvAik9oDB3bXG84bynzgPgA5t8oPB9NSveOib7LOOrvfH/xGr3pXN4h+GP2+f2CJyb4nty4+eV410ux3vkJC2IiN2qzuv2xrekWcCsiRMnHvONb3yj1dWpadWqVYwdO7bV1egZjndx+qZPB6D/1ltXlznecM8u9wCwyz27FL6vZsV7+hXZZ3nrEbcOs2R36dsincOPDX/cPrcb17cgxXda4+eV410ux3vkpk+f3ruN7wHu+bY8xxu46KJseuyxzd2ue76rcs93B3HPd7Hc890xHO+Rc8+3e76tguNdvYe6qO063u757iTu+S6We747h+M9cu75xj3ftibHm6o91EVt1/F2z3dHcc93sdzz3TEc75EbqufbQw2amZmZmZWk64cazKWd0N/f3+rq1LRq1aq2rl+3cbyhL02bHYdq23W8gZRtUkYcmh3vXvvs+rbIpvUct8/txvWl6Uji5niXy/EuhtNO2oR/2imX443TTkrmtJMO4rSTYjntpGM43iPntBMzMzMzszbgxreZmZmZWUm6Pu3EQw1aNY53caoNNbjyyad5/E/N39fUiRs1f6MF8VCDncNDDRbLQw12Dsd75DzUIM75tjU53gWqkvM954rvcO6i5t/fvezsA5u+zaI457uDOOe7WM757hiO98g559vMzMzMrA10/VCDZlbDtGnZdMGC1tZjFCad+t1CtttJPepmZtZZuj7txDnfVo3jvXZu9qIVTzdlu8e/9xAA5nzjhtVlE9ankJzvohSRS+6c787hnO9iOee7czjeI+ecb5zzbWtyvFkrN7tZvcjLzpmZbe+UuavLTp76YiE530UpoufbOd8dxDnfxXLOd8dwvEfOOd9mZmZmZm3AjW8zMzMzs5K48W1mZmZmVhI3vs3MzMzM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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# nuv_monitor.model.new_data\n", + "# nuv_monitor.get_data()\n", + "nuv_monitor.plot()\n", + "nuv_monitor.track()\n", + "# nuv_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class NUVDarkMonitor(BaseMonitor):\n", + " data_model = NUVDarkDataModel\n", + " # TODO: update docs\n", + " # docs = \"https://spacetelescope.github.io/cosmo/monitors.html#\"\n", + " output = COS_MONITORING\n", + "\n", + " run = 'monthly'\n", + " \n", + " def get_data(self):\n", + " # access data, perform any filtering required for analysis\n", + " data = self.model.new_data\n", + " \n", + " xlim = (0, 1024)\n", + " ylim = (0, 1024)\n", + " \n", + " data = self.get_dark_rate(data)\n", + " \n", + " # when the monitor method of the monitor is called, it will initialize the self.data attribute\n", + " # with this method and then can be used by the other methods\n", + " return data\n", + " \n", + " def get_dark_rate(df, x_range=(0, 1024), y_range=(0, 1024), time_step=25):\n", + " \"\"\"Calculate the dark rate per input DataFrame row which should correspond to the \n", + " data from a single dark.\"\"\"\n", + " \n", + " def _calculate_darkrate_per_row(row):\n", + " time_bins = row['TIME_3'][::time_step]\n", + " npix = (x_range[1] - x_range[0]) * (y_range[1] - y_range[0])\n", + " \n", + " filtered_time = row.TIME[\n", + " (row.XCORR > x_range[0])\n", + " & (row.XCORR < x_range[1])\n", + " & (row.YCORR > y_range[0])\n", + " & (row.YCORR < y_range[1])\n", + " ]\n", + " counts, _ = np.histogram(filtered_time, bins=time_bins)\n", + " \n", + " return counts / npix / time_step\n", + " \n", + " def _calculate_datetime_bins_per_row(row):\n", + " time_bins = row['TIME_3'][::time_step]\n", + " \n", + " return absolute_time(expstart=row['EXPSTART'], time=time_bins).to_datetime()[:-1]\n", + " \n", + " df['DARK_RATE'] = df.apply(_calculate_darkrate_per_row, axis=1)\n", + " df['DR_TIME'] = df.apply(_calculate_datetime_bins_per_row, axis=1)\n", + " \n", + " return df\n", + "\n", + " def track(self):\n", + " # track something. perhaps current dark rate?\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " plotdf = self.data[['DECIMAL_YEAR', 'DARK_RATE']]\n", + " \n", + " # i can't get explode_df to work so this is for now\n", + " # i think i know why it doesn't work and i fixed it i just haven't switched to using it\n", + " all_dec_year = []\n", + " all_dark_rates = []\n", + " for index, row in plotdf.iterrows():\n", + " all_dec_year = list(itertools.chain(all_dec_year, row[\"DECIMAL_YEAR\"]))\n", + " all_dark_rates = list(itertools.chain(all_dark_rates, row[\"DARK_RATE\"]))\n", + " \n", + " dark_counts = np.asarray(all_dark_rates)\n", + " fig = plt.figure(figsize=(12, 9))\n", + "# bin_size = 1e-8\n", + "# n_bins = int((dark_counts.max() - dark_counts.min()) / bin_size)\n", + " n_bins = 30\n", + " ax = fig.add_subplot(2, 1, 1)\n", + " ax.hist(dark_counts, bins=n_bins, align='mid', histtype='stepfilled')\n", + " counts, bins = np.histogram(dark_counts, bins=n_bins)\n", + " cuml_dist = np.cumsum(counts)\n", + " count_99 = abs(cuml_dist / float(cuml_dist.max()) - .99).argmin()\n", + " count_95 = abs(cuml_dist / float(cuml_dist.max()) - .95).argmin()\n", + "\n", + " mean = dark_counts.mean()\n", + " med = np.median(dark_counts)\n", + " std = dark_counts.std()\n", + " mean_obj = ax.axvline(x=mean, lw=2, ls='--', color='r', label='Mean ')\n", + " med_obj = ax.axvline(x=med, lw=2, ls='-', color='r', label='Median')\n", + " two_sig = ax.axvline(x=med + (2 * std), lw=2, ls='-', color='gold')\n", + " three_sig = ax.axvline(x=med + (3 * std), lw=2, ls='-', color='DarkOrange')\n", + " dist_95 = ax.axvline(x=bins[count_95], lw=2, ls='-', color='LightGreen')\n", + " dist_99 = ax.axvline(x=bins[count_99], lw=2, ls='-', color='DarkGreen')\n", + "\n", + " ax.grid(True, which='both')\n", + " ax.set_title('Histogram of Dark Rates', fontsize=15, fontweight='bold')\n", + " ax.set_ylabel('Frequency', fontsize=15, fontweight='bold')\n", + " ax.set_xlabel('Counts/pix/sec', fontsize=15, fontweight='bold')\n", + " ax.set_xlim(dark_counts.min(), dark_counts.max())\n", + " ax.xaxis.set_major_formatter(FormatStrFormatter('%3.2e'))\n", + "\n", + " ax = fig.add_subplot(2, 1, 2)\n", + " # log_bins = np.logspace(np.log10(dark.min()), np.log10(dark.max()), 100)\n", + " ax.hist(dark_counts, bins=n_bins, align='mid', log=True, histtype='stepfilled')\n", + "\n", + " ax.axvline(x=mean, lw=2, ls='--', color='r', label='Mean')\n", + " ax.axvline(x=med, lw=2, ls='-', color='r', label='Median')\n", + " ax.axvline(x=med + (2 * std), lw=2, ls='-', color='gold')\n", + " ax.axvline(x=med + (3 * std), lw=2, ls='-', color='DarkOrange')\n", + " ax.axvline(x=bins[count_95], lw=2, ls='-', color='LightGreen')\n", + " ax.axvline(x=bins[count_99], lw=2, ls='-', color='DarkGreen')\n", + "\n", + " # ax.set_xscale('log')\n", + " ax.grid(True, which='both')\n", + " ax.set_ylabel('Log Frequency', fontsize=15, fontweight='bold')\n", + " ax.set_xlabel('Counts/pix/sec', fontsize=15, fontweight='bold')\n", + " ax.set_xlim(dark_counts.min(), dark_counts.max())\n", + " ax.xaxis.set_major_formatter(FormatStrFormatter('%3.2e'))\n", + "\n", + " fig.legend([med_obj, mean_obj, two_sig, three_sig, dist_95, dist_99],\n", + " ['Median: {0:.2e}'.format(med),\n", + " 'Mean: {0:.2e}'.format(mean),\n", + " r'2$\\sigma$: {0:.2e}'.format(med + (2 * std)),\n", + " r'3$\\sigma$: {0:.2e}'.format(med + (3 * std)),\n", + " r'95$\\%$: {0:.2e}'.format(bins[count_95]),\n", + " r'99$\\%$: {0:.2e}'.format(bins[count_99])],\n", + " shadow=True,\n", + " numpoints=1,\n", + " bbox_to_anchor=[0.8, 0.8])\n", + "\n", + "\n", + " def plot(self):\n", + " # select the important columns from the dataframe\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " plotdf = pd.DataFrame({\n", + " \"DECIMAL_YEAR\": self.data[\"DECIMAL_YEAR\"],\n", + " \"DARK_RATE\": self.data[\"DARK_RATE\"]\n", + " })\n", + " \n", + " # i can't get explode_df to work so this is for now\n", + " all_dec_year = []\n", + " all_dark_rates = []\n", + " for index, row in plotdf.iterrows():\n", + " all_dec_year = list(itertools.chain(all_dec_year, row[\"DECIMAL_YEAR\"]))\n", + " all_dark_rates = list(itertools.chain(all_dark_rates, row[\"DARK_RATE\"]))\n", + " \n", + " # not sure what is happening here tbh, still figuring it out\n", + "# self.x = all_dec_year\n", + "# self.y = all_dark_rates\n", + " \n", + "# self.basic_scatter()\n", + " \n", + " fig = plt.figure(figsize=(12, 9))\n", + " plt.scatter(all_dec_year, all_dark_rates)\n", + " # plt.xlim(min(all_dec_year), max(all_dec_year))\n", + " plt.ylim(0, max(all_dark_rates))\n", + " plt.ticklabel_format(axis='y', style='sci', scilimits=(-2, 2))\n", + " plt.ticklabel_format(axis='x', style='plain')\n", + " plt.xlabel(\"Decimal Year\")\n", + " plt.ylabel(\"Dark Rate (c/p/s)\")\n", + " plt.grid(True)\n", + "\n", + " def store_results(self):\n", + " # need to store results if not going in the database\n", + " pass\n", + "\n", + "nuv_monitor = NUVDarkMonitor()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nuv_monitor.plot()\n", + "nuv_monitor.track()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/.ipynb_checkpoints/nuv_dark_monitor_refactor-checkpoint.ipynb b/.ipynb_checkpoints/nuv_dark_monitor_refactor-checkpoint.ipynb new file mode 100644 index 0000000..43efff1 --- /dev/null +++ b/.ipynb_checkpoints/nuv_dark_monitor_refactor-checkpoint.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import yaml\n", + "import cosmo\n", + "import numpy\n", + "import itertools\n", + "from glob import glob\n", + "from astropy.io import fits\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import FormatStrFormatter\n", + "from cosmo.monitors.data_models import NUVDarkDataModel\n", + "from cosmo.filesystem import find_files, data_from_exposures, data_from_jitters\n", + "from cosmo.monitor_helpers import absolute_time, explode_df\n", + "from monitorframe.datamodel import BaseDataModel\n", + "from monitorframe.monitor import BaseMonitor" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "with open(os.environ['COSMO_CONFIG']) as yamlfile:\n", + " SETTINGS = yaml.safe_load(yamlfile)\n", + "\n", + "FILES_SOURCE = SETTINGS['filesystem']['source']\n", + "COS_MONITORING = SETTINGS['output']" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class NUVDarkDataModel(BaseDataModel):\n", + " cosmo_layout = False\n", + "# program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + "# '13528/', '13126/', '12720/', '12420/', '11894/']\n", + " program_ids = ['15538/']\n", + " # subdir_pattern = '?????'\n", + " # ^ this will need to be added back once we have an all corrtag model\n", + "\n", + " def get_new_data(self):\n", + " header_request = {\n", + " 0: ['ROOTNAME'],\n", + " 1: ['EXPTIME', 'EXPSTART']\n", + " }\n", + "\n", + " table_request = {\n", + " 1: ['XCORR', 'YCORR', 'TIME'],\n", + " 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + " }\n", + "\n", + " files = []\n", + "\n", + " for prog_id in self.program_ids:\n", + " new_files_source = os.path.join(FILES_SOURCE, prog_id)\n", + " files += find_files('*corrtag*', data_dir=new_files_source)\n", + "\n", + " if self.model is not None:\n", + " currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)]\n", + "\n", + " for file in currently_ingested:\n", + " files.remove(file)\n", + "\n", + " if not files: # No new files\n", + " return pd.DataFrame()\n", + "\n", + " data_results = data_from_exposures(\n", + " files,\n", + " header_request=header_request,\n", + " table_request=table_request\n", + " )\n", + "\n", + " return data_results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/.ipynb_checkpoints/refactor_FUV_and_NUV-checkpoint.ipynb b/.ipynb_checkpoints/refactor_FUV_and_NUV-checkpoint.ipynb new file mode 100644 index 0000000..2032863 --- /dev/null +++ b/.ipynb_checkpoints/refactor_FUV_and_NUV-checkpoint.ipynb @@ -0,0 +1,888 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import yaml\n", + "import cosmo\n", + "import numpy\n", + "from itertools import repeat\n", + "from glob import glob\n", + "from astropy.io import fits\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.ticker import FormatStrFormatter\n", + "from cosmo.filesystem import find_files, data_from_exposures, data_from_jitters\n", + "from cosmo.monitor_helpers import absolute_time, explode_df\n", + "from monitorframe.datamodel import BaseDataModel\n", + "from monitorframe.monitor import BaseMonitor" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/grp/hst/cos2/cosmo/\n" + ] + } + ], + "source": [ + "# load settings\n", + "with open(os.environ['COSMO_CONFIG']) as yamlfile:\n", + " SETTINGS = yaml.safe_load(yamlfile)\n", + "\n", + "FILES_SOURCE = SETTINGS['filesystem']['source']\n", + "COS_MONITORING = SETTINGS['output']\n", + "print(FILES_SOURCE)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Filename: /grp/hst/cos2/cosmo/15776/le090edhq_corrtag.fits.gz\n", + "No. Name Ver Type Cards Dimensions Format\n", + " 0 PRIMARY 1 PrimaryHDU 166 () \n", + " 1 EVENTS 1 BinTableHDU 250 1440318R x 12C [1E, 1I, 1I, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1I, 1B] \n", + " 2 GTI 1 BinTableHDU 22 1R x 2C [1D, 1D] \n", + " 3 TIMELINE 1 BinTableHDU 264 1331R x 12C [1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E, 1E] \n" + ] + } + ], + "source": [ + "# testfile\n", + "testfile = FILES_SOURCE + \"15776/le090edhq_corrtag.fits.gz\"\n", + "with fits.open(testfile) as f:\n", + " f.info()\n", + " data = f[\"EVENTS\"].data\n", + " timeline = f[\"TIMELINE\"].data\n", + " header = f[\"PRIMARY\"].header\n", + " \n", + "# data.columns \n", + "# timeline.columns\n", + "# header" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# # combined DataModel\n", + "# class DarkDataModel(BaseDataModel):\n", + "# cosmo_layout = False\n", + "# fuv_program_ids = ['15771', '15533/', '14940/', '14520/', '14436/', '13968/', '13521/', '13121/', '12716/', '12423/',\n", + "# '11895/']\n", + "# nuv_program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + "# '13528/', '13126/', '12720/', '12420/', '11894/']\n", + "# # subdir_pattern = '?????'\n", + "\n", + "# def get_new_data(self, segment=\"FUV\"): # this way when you get new data it will be based on whether it is fuv or nuv\n", + "# if segment == \"NUV\":\n", + "# header_request = {\n", + "# 0: ['ROOTNAME'],\n", + "# 1: ['EXPTIME', 'EXPSTART']\n", + "# }\n", + "# table_request = {\n", + "# 1: ['XCORR', 'YCORR', 'TIME'],\n", + "# 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + "# }\n", + "# program_id = nuv_program_ids\n", + "\n", + "# else: \n", + "# header_request = {\n", + "# 0: ['ROOTNAME', 'SEGMENT'],\n", + "# 1: ['EXPTIME', 'EXPSTART']\n", + "# }\n", + "# table_request = {\n", + "# 1: ['PHA', 'XCORR', 'YCORR', 'TIME'],\n", + "# 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + "# }\n", + "# program_id = fuv_program_ids\n", + "\n", + "# files = []\n", + "\n", + "# for prog_id in self.program_id:\n", + "\n", + "# new_files_source = os.path.join(FILES_SOURCE, prog_id)\n", + "# files += find_files('*corrtag*', data_dir=new_files_source)\n", + "\n", + "# if self.model is not None:\n", + "# currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)]\n", + "\n", + "# for file in currently_ingested:\n", + "# files.remove(file)\n", + "\n", + "# if not files: # No new files\n", + "# return pd.DataFrame()\n", + "\n", + "# data_results = data_from_exposures(\n", + "# files,\n", + "# header_request=header_request,\n", + "# table_request=table_request\n", + "# )\n", + "\n", + "# return data_results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Changes to DataModel (e.g. program ids):\n", + "- Re-run DataModel cell\n", + "- Run Monitor cell for FUV and/or NUV\n", + "- Run monitor execution cell for FUV and/or NUV\n", + "\n", + "#### Changes to Monitor:\n", + "- Run Monitor cell for FUV and/or NUV\n", + "- Run monitor execution cell for FUV and/or NUV\n", + "- If successful for FUV make changes as necessary for NUV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Working DataModel for both FUV and NUV" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# combined DataModel\n", + "class DarkDataModel(BaseDataModel):\n", + " cosmo_layout = False\n", + "# fuv_program_ids = ['15771/', '15533/', '14940/', '14520/', '14436/', '13968/', '13521/', '13121/', '12716/', '12423/',\n", + "# '11895/']\n", + "# nuv_program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + "# '13528/', '13126/', '12720/', '12420/', '11894/']\n", + " fuv_program_ids = [\"15771/\"]\n", + " nuv_program_ids = [\"15776/\"]\n", + " program_id = fuv_program_ids + nuv_program_ids\n", + " # subdir_pattern = '?????'\n", + "\n", + " def get_new_data(self): # this way when you get new data it will get all the data\n", + " header_request = {\n", + " 0: ['ROOTNAME', 'SEGMENT'],\n", + " 1: ['EXPTIME', 'EXPSTART']\n", + " }\n", + " table_request = {\n", + " 1: ['PHA', 'XCORR', 'YCORR', 'TIME'],\n", + " 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + " }\n", + "\n", + " files = []\n", + "\n", + " # this is not finding files for some reason. i need to explore that\n", + "# for prog_id in self.program_id:\n", + "# new_files_source = os.path.join(FILES_SOURCE, prog_id)\n", + "# print(new_files_source)\n", + "# files += find_files('*corrtag*', data_dir=new_files_source)\n", + "# print(files)\n", + "\n", + " for program in self.program_id:\n", + " new_files_source = os.path.join(FILES_SOURCE, program)\n", + " subfiles = glob(os.path.join(new_files_source, \"*corrtag*\"))\n", + " files += subfiles\n", + "\n", + " if self.model is not None:\n", + " currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)]\n", + "\n", + " for file in currently_ingested:\n", + " files.remove(file)\n", + "\n", + " if not files: # No new files\n", + " return pd.DataFrame()\n", + "\n", + " data_results = data_from_exposures(\n", + " files,\n", + " header_request=header_request,\n", + " table_request=table_request\n", + " )\n", + "\n", + " return data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# model = DarkDataModel()\n", + "# data_results = model.get_new_data()\n", + "# data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# FUV monitors\n", + "\n", + "def dark_filter(df_row, filter_pha, location):\n", + " good_pha = (2, 23)\n", + " time_step = 25\n", + " time_bins = df_row['TIME_3'][::time_step]\n", + " # try commenting these out, since lat and lon don't seem to be used\n", + " lat = df_row['LATITUDE'][::time_step][:-1]\n", + " lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME'])\n", + " npix = (location[1] - location[0]) * (location[3] - location[2])\n", + " index = np.where((event_df['XCORR'] > location[0]) &\n", + " (event_df['XCORR'] < location[1]) &\n", + " (event_df['YCORR'] > location[2]) &\n", + " (event_df['YCORR'] < location[3]))\n", + " filtered_row = event_df.iloc[index].reset_index(drop=True)\n", + "\n", + " if filter_pha:\n", + " filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & (filtered_row['PHA'] < good_pha[1])]\n", + "\n", + " counts = np.histogram(filtered_row.TIME, bins=time_bins)[0]\n", + "\n", + " date = absolute_time(\n", + " expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), time=time_bins.tolist()\n", + " ).to_datetime()[:-1]\n", + "\n", + " dark_rate = counts / npix / time_step\n", + "\n", + " return pd.DataFrame({'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date],\n", + " 'ROOTNAME': df_row['ROOTNAME']})\n", + "\n", + "\n", + "class FUVDarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + "# print(self.model.new_data)\n", + " for _, row in self.model.new_data.iterrows():\n", + "# print(row.SEGMENT, self.segment)\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment:\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + "\n", + "\n", + "class FUVABottomDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVA dark monitor for bottom edge\"\"\"\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 15250, 296, 375)\n", + " name = f'FUVA Dark Monitor - Bottom'\n", + "\n", + "\n", + "class FUVALeftDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVA dark monitor for left edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Left'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 1260, 296, 734)\n", + "\n", + "\n", + "class FUVATopDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVA dark monitor for top edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Top'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 15250, 660, 734)\n", + "\n", + "\n", + "class FUVARightDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVA dark monitor for right edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Right'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (15119, 15250, 296, 734)\n", + "\n", + "\n", + "class FUVAInnerDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVA dark monitor for inner region\"\"\"\n", + " name = 'FUVA Dark Monitor - Inner'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1260, 15119, 375, 660)\n", + "\n", + "\n", + "class FUVBBottomDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVB dark monitor for bottom edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Bottom'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 15182, 360, 405)\n", + "\n", + "\n", + "class FUVBLeftDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVB dark monitor for left edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Left'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 1000, 360, 785)\n", + "\n", + "\n", + "class FUVBTopDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVB dark monitor for top edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Top'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 15182, 740, 785)\n", + "\n", + "\n", + "class FUVBRightDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVB dark monitor for right edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Right'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (14990, 15182, 360, 785)\n", + "\n", + "\n", + "class FUVBInnerDarkMonitor(FUVDarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "fuv_monitor = FUVAInnerDarkMonitor()\n", + "fuv_monitor.monitor()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Working Dark Monitor for both FUV and NUV" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# include NUV\n", + "def dark_filter(df_row, filter_pha, location):\n", + " good_pha = (2, 23)\n", + " # time step stuff\n", + " time_step = 25\n", + " time_bins = df_row['TIME_3'][::time_step]\n", + " lat = df_row['LATITUDE'][::time_step][:-1]\n", + " lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # try commenting these out, since lat and lon don't seem to be used\n", + "# lat = df_row['LATITUDE'][::time_step][:-1]\n", + "# lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # filtering pha\n", + " if filter_pha:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME'])\n", + " else:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'TIME'])\n", + " \n", + " # creating event dataframe and filtering it by location on the detector\n", + " npix = (location[1] - location[0]) * (location[3] - location[2])\n", + " index = np.where((event_df['XCORR'] > location[0]) &\n", + " (event_df['XCORR'] < location[1]) &\n", + " (event_df['YCORR'] > location[2]) &\n", + " (event_df['YCORR'] < location[3]))\n", + " filtered_row = event_df.iloc[index].reset_index(drop=True)\n", + "\n", + " #filtered events only need to be further filtered by PHA if not NUV\n", + " if filter_pha:\n", + " filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & (filtered_row['PHA'] < good_pha[1])]\n", + "\n", + " counts = np.histogram(filtered_row.TIME, bins=time_bins)[0]\n", + "\n", + " date = absolute_time(\n", + " expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), time=time_bins.tolist()\n", + " ).to_datetime()[:-1]\n", + "\n", + " dark_rate = counts / npix / time_step\n", + "\n", + " return pd.DataFrame({'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date],\n", + " 'ROOTNAME': df_row['ROOTNAME']})\n", + "\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + "\n", + "\n", + "class FUVABottomDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for bottom edge\"\"\"\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 15250, 296, 375)\n", + " name = f'FUVA Dark Monitor - Bottom'\n", + "\n", + "\n", + "class FUVALeftDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for left edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Left'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 1260, 296, 734)\n", + "\n", + "\n", + "class FUVATopDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for top edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Top'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 15250, 660, 734)\n", + "\n", + "\n", + "class FUVARightDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for right edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Right'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (15119, 15250, 296, 734)\n", + "\n", + "\n", + "class FUVAInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for inner region\"\"\"\n", + " name = 'FUVA Dark Monitor - Inner'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1260, 15119, 375, 660)\n", + "\n", + "\n", + "class FUVBBottomDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for bottom edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Bottom'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 15182, 360, 405)\n", + "\n", + "\n", + "class FUVBLeftDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for left edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Left'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 1000, 360, 785)\n", + "\n", + "\n", + "class FUVBTopDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for top edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Top'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 15182, 740, 785)\n", + "\n", + "\n", + "class FUVBRightDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for right edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Right'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (14990, 15182, 360, 785)\n", + "\n", + "\n", + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + " \n", + " \n", + "class NUVDarkMonitor(DarkMonitor):\n", + " name = \"NUV Dark Monitor\"\n", + " segment = \"N/A\"\n", + " location = (0, 1024, 0, 1024)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "nuv_monitor = NUVDarkMonitor()\n", + "nuv_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# # run everything\n", + "# from tqdm import tqdm\n", + "# def run_all_dark_monitors():\n", + "# fuva_bottom_monitor = FUVABottomDarkMonitor()\n", + "# fuva_left_monitor = FUVALeftDarkMonitor()\n", + "# fuva_top_monitor = FUVATopDarkMonitor()\n", + "# fuva_right_monitor = FUVARightDarkMonitor()\n", + "# fuva_inner_monitor = FUVAInnerDarkMonitor()\n", + "# fuvb_bottom_monitor = FUVbBottomDarkMonitor()\n", + "# fuvb_left_monitor = FUVbLeftDarkMonitor()\n", + "# fuvb_top_monitor = FUVbTopDarkMonitor()\n", + "# fuvb_right_monitor = FUVbRightDarkMonitor()\n", + "# fuvb_inner_monitor = FUVbInnerDarkMonitor()\n", + "# nuv_monitor = NUVDarkMonitor()\n", + "# for monitor in tqdm([fuva_bottom_monitor, fuva_left_monitor, fuva_top_monitor, \n", + "# fuva_right_monitor, fuva_inner_monitor, fuvb_bottom_monitor,\n", + "# fuvb_left_monitor, fuvb_top_monitor, fuvb_right_monitor, \n", + "# fuvb_inner_monitor, nuv_monitor]):\n", + "# monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# combined DataModel\n", + "from tqdm import tqdm\n", + "\n", + "class DarkDataModel(BaseDataModel):\n", + " cosmo_layout = False\n", + "# fuv_program_ids = ['15771/', '15533/', '14940/', '14520/', '14436/', '13968/', '13521/', '13121/', '12716/', '12423/',\n", + "# '11895/']\n", + "# nuv_program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + "# '13528/', '13126/', '12720/', '12420/', '11894/']\n", + " fuv_program_ids = [\"15771/\"]\n", + " nuv_program_ids = [\"15776/\"]\n", + " program_id = fuv_program_ids + nuv_program_ids\n", + "\n", + " def get_new_data(self): # this way when you get new data it will get all the data\n", + " header_request = {\n", + " 0: ['ROOTNAME', 'SEGMENT'],\n", + " 1: ['EXPTIME', 'EXPSTART']\n", + " }\n", + " table_request = {\n", + " 1: ['PHA', 'XCORR', 'YCORR', 'TIME'],\n", + " 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + " }\n", + "\n", + " files = []\n", + "\n", + " for prog_id in tqdm(self.program_id):\n", + " new_files_source = os.path.join(FILES_SOURCE, prog_id)\n", + " files += find_files('*corrtag*', data_dir=new_files_source)\n", + "\n", + "# for program in self.program_id:\n", + "# new_files_source = os.path.join(FILES_SOURCE, program)\n", + "# subfiles = glob(os.path.join(new_files_source, \"*corrtag*\"))\n", + "# files += subfiles\n", + "\n", + " if self.model is not None:\n", + " currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)]\n", + "\n", + " for file in currently_ingested:\n", + " files.remove(file)\n", + "\n", + " if not files: # No new files\n", + " return pd.DataFrame()\n", + "\n", + " data_results = data_from_exposures(\n", + " files,\n", + " header_request=header_request,\n", + " table_request=table_request\n", + " )\n", + "\n", + " return data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# include NUV\n", + "def dark_filter(df_row, filter_pha, location):\n", + " good_pha = (2, 23)\n", + " # time step stuff\n", + " time_step = 25\n", + " time_bins = df_row['TIME_3'][::time_step]\n", + " lat = df_row['LATITUDE'][::time_step][:-1]\n", + " lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # try commenting these out, since lat and lon don't seem to be used\n", + "# lat = df_row['LATITUDE'][::time_step][:-1]\n", + "# lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # filtering pha\n", + " if filter_pha:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME'])\n", + " else:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'TIME'])\n", + " \n", + " # creating event dataframe and filtering it by location on the detector\n", + " npix = (location[1] - location[0]) * (location[3] - location[2])\n", + " index = np.where((event_df['XCORR'] > location[0]) &\n", + " (event_df['XCORR'] < location[1]) &\n", + " (event_df['YCORR'] > location[2]) &\n", + " (event_df['YCORR'] < location[3]))\n", + " filtered_row = event_df.iloc[index].reset_index(drop=True)\n", + "\n", + " #filtered events only need to be further filtered by PHA if not NUV\n", + " if filter_pha:\n", + " filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & (filtered_row['PHA'] < good_pha[1])]\n", + "\n", + " counts = np.histogram(filtered_row.TIME, bins=time_bins)[0]\n", + "\n", + " date = absolute_time(\n", + " expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), time=time_bins.tolist()\n", + " ).to_datetime()[:-1]\n", + "\n", + " dark_rate = counts / npix / time_step\n", + "\n", + " return pd.DataFrame({'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date],\n", + " 'ROOTNAME': df_row['ROOTNAME']})\n", + "\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + "\n", + "\n", + "class FUVABottomDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for bottom edge\"\"\"\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 15250, 296, 375)\n", + " name = f'FUVA Dark Monitor - Bottom'\n", + "\n", + "\n", + "class FUVALeftDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for left edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Left'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 1260, 296, 734)\n", + "\n", + "\n", + "class FUVATopDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for top edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Top'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1060, 15250, 660, 734)\n", + "\n", + "\n", + "class FUVARightDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for right edge\"\"\"\n", + " name = 'FUVA Dark Monitor - Right'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (15119, 15250, 296, 734)\n", + "\n", + "\n", + "class FUVAInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVA dark monitor for inner region\"\"\"\n", + " name = 'FUVA Dark Monitor - Inner'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVA'\n", + " location = (1260, 15119, 375, 660)\n", + "\n", + "\n", + "class FUVBBottomDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for bottom edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Bottom'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 15182, 360, 405)\n", + "\n", + "\n", + "class FUVBLeftDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for left edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Left'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 1000, 360, 785)\n", + "\n", + "\n", + "class FUVBTopDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for top edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Top'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (809, 15182, 740, 785)\n", + "\n", + "\n", + "class FUVBRightDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for right edge\"\"\"\n", + " name = 'FUVB Dark Monitor - Right'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (14990, 15182, 360, 785)\n", + "\n", + "\n", + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + "# data_model = FUVDarkDataModel\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + " \n", + " \n", + "class NUVDarkMonitor(DarkMonitor):\n", + " name = \"NUV Dark Monitor\"\n", + " segment = \"N/A\"\n", + " location = (0, 1024, 0, 1024)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nuv_monitor = NUVDarkMonitor()\n", + "nuv_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/.ipynb_checkpoints/solar_plots-checkpoint.ipynb b/.ipynb_checkpoints/solar_plots-checkpoint.ipynb new file mode 100644 index 0000000..8b4f6fc --- /dev/null +++ b/.ipynb_checkpoints/solar_plots-checkpoint.ipynb @@ -0,0 +1,996 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import yaml\n", + "import cosmo\n", + "import numpy\n", + "import scipy\n", + "import sunpy\n", + "import datetime\n", + "\n", + "import plotly.express as px\n", + "import matplotlib.pyplot as plt\n", + "import plotly.graph_objects as go\n", + "\n", + "from glob import glob\n", + "from ftplib import FTP\n", + "from astropy.io import fits\n", + "from itertools import repeat\n", + "from astropy.time import Time\n", + "from sunpy.net import Fido, attrs as a\n", + "from sunpy.timeseries import TimeSeries\n", + "from plotly.subplots import make_subplots\n", + "from monitorframe.monitor import BaseMonitor\n", + "from monitorframe.datamodel import BaseDataModel\n", + "from matplotlib.ticker import FormatStrFormatter\n", + "from cosmo.monitor_helpers import absolute_time, explode_df\n", + "from cosmo.filesystem import find_files, data_from_exposures, data_from_jitters" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# load settings\n", + "with open(os.environ['COSMO_CONFIG']) as yamlfile:\n", + " SETTINGS = yaml.safe_load(yamlfile)\n", + "\n", + "FILES_SOURCE = SETTINGS['filesystem']['source']\n", + "COS_MONITORING = SETTINGS['output']" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# working DataModel\n", + "class DarkDataModel(BaseDataModel):\n", + " cosmo_layout = False\n", + " fuv_program_ids = ['15771/', '15533/', '14940/', '14520/', '14436/', '13968/', '13521/', '13121/', '12716/', '12423/',\n", + " '11895/']\n", + " program_id = fuv_program_ids\n", + "# nuv_program_ids = ['15776/', '15538/', '14942/', '14521/', '14442/', '13974/', \n", + "# '13528/', '13126/', '12720/', '12420/', '11894/']\n", + "# fuv_program_ids = [\"15771/\"]\n", + "# nuv_program_ids = [\"15776/\"]\n", + "# program_id = fuv_program_ids + nuv_program_ids\n", + "\n", + " def get_new_data(self): # this way when you get new data it will get all the data\n", + " header_request = {\n", + " 0: ['ROOTNAME', 'SEGMENT'],\n", + " 1: ['EXPTIME', 'EXPSTART']\n", + " }\n", + " table_request = {\n", + " 1: ['PHA', 'XCORR', 'YCORR', 'TIME'],\n", + " 3: ['TIME', 'LATITUDE', 'LONGITUDE']\n", + " }\n", + "\n", + " files = []\n", + "\n", + " for prog_id in self.program_id:\n", + " new_files_source = os.path.join(FILES_SOURCE, prog_id)\n", + " files += find_files('*corrtag*', data_dir=new_files_source)\n", + "\n", + " if self.model is not None:\n", + " currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)]\n", + "\n", + " for file in currently_ingested:\n", + " files.remove(file)\n", + "\n", + " if not files: # No new files\n", + " return pd.DataFrame()\n", + "\n", + " data_results = data_from_exposures(\n", + " files,\n", + " header_request=header_request,\n", + " table_request=table_request\n", + " )\n", + "\n", + " return data_results" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# monitor functions\n", + "def dark_filter(df_row, filter_pha, location):\n", + " good_pha = (2, 23)\n", + " # time step stuff\n", + " time_step = 25\n", + " time_bins = df_row['TIME_3'][::time_step]\n", + " lat = df_row['LATITUDE'][::time_step][:-1]\n", + " lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # try commenting these out, since lat and lon don't seem to be used\n", + "# lat = df_row['LATITUDE'][::time_step][:-1]\n", + "# lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # filtering pha\n", + " if filter_pha:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME'])\n", + " else:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'TIME'])\n", + " \n", + " # creating event dataframe and filtering it by location on the detector\n", + " npix = (location[1] - location[0]) * (location[3] - location[2])\n", + " index = np.where((event_df['XCORR'] > location[0]) &\n", + " (event_df['XCORR'] < location[1]) &\n", + " (event_df['YCORR'] > location[2]) &\n", + " (event_df['YCORR'] < location[3]))\n", + " filtered_row = event_df.iloc[index].reset_index(drop=True)\n", + "\n", + " #filtered events only need to be further filtered by PHA if not NUV\n", + " if filter_pha:\n", + " filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & (filtered_row['PHA'] < good_pha[1])]\n", + "\n", + " counts = np.histogram(filtered_row.TIME, bins=time_bins)[0]\n", + "\n", + " date = absolute_time(\n", + " expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), time=time_bins.tolist()\n", + " ).to_datetime()[:-1]\n", + "\n", + " dark_rate = counts / npix / time_step\n", + "\n", + " return pd.DataFrame({'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date],\n", + " 'ROOTNAME': df_row['ROOTNAME']})\n", + "\n", + "# solar functions\n", + "def grab_solar_files(file_dir):\n", + " \"\"\"Pull solar data files from NOAA website\n", + " Solar data is FTPd from NOAA and written to text files for use in plotting\n", + " and monitoring of COS dark-rates and TDS.\n", + " Parameters\n", + " ----------\n", + " file_dir : str\n", + " Directory to write the files to\n", + " \"\"\"\n", + " ftp = FTP('ftp.swpc.noaa.gov')\n", + " ftp.login()\n", + "\n", + " ftp.cwd('/pub/indices/old_indices/')\n", + "\n", + " for item in sorted(ftp.nlst()):\n", + " if item.endswith('_DSD.txt'):\n", + " year = int(item[:4])\n", + " if year >= 2000:\n", + " destination = os.path.join(file_dir, item)\n", + " if not os.path.exists(destination):\n", + " ftp.retrbinary('RETR {}'.format(item),\n", + " open(destination, 'wb').write)\n", + "\n", + " os.chmod(destination, 0o777)\n", + "\n", + "\n", + "def compile_solar_data(file_dir):\n", + " \"\"\"Pull desired columns from solar data text files\n", + " Parameters\n", + " ----------\n", + " file_dir : str\n", + " Returns\n", + " -------\n", + " date : np.ndarray\n", + " mjd of each measurements\n", + " flux : np.ndarray\n", + " solar flux measurements\n", + " \"\"\"\n", + " date = []\n", + " flux = []\n", + " input_list = glob(os.path.join(file_dir, '*DSD.txt'))\n", + " input_list.sort()\n", + "\n", + " for item in input_list:\n", + " # clean up Q4 files when year-long file exists\n", + " if ('Q4_' in item) and os.path.exists(item.replace('Q4_', '_')):\n", + " try:\n", + " os.remove(item)\n", + " except PermissionError:\n", + " continue\n", + " continue # i know this is stupid\n", + "\n", + " # astropy.ascii no longer returns an empty table for empty files\n", + " # Throws IndexError, we will go around it if empty.\n", + " try:\n", + " data = ascii.read(item, data_start=1, comment='[#,:]')\n", + " except IndexError:\n", + " continue\n", + "\n", + " for line in data:\n", + " line_date = Time('{}-{}-{} 00:00:00'.format(line['col1'],\n", + " line['col2'],\n", + " line['col3']),\n", + " scale='utc', format='iso').mjd\n", + "\n", + " line_flux = line[3]\n", + "\n", + " if line_flux > 0:\n", + " date.append(line_date)\n", + " flux.append(line_flux)\n", + " \n", + " solar_date = np.array(date)\n", + " solar_flux = np.array(flux)\n", + " solar_dec = Time(solar_date, format='mjd').decimalyear\n", + " solar_smooth = scipy.convolve(solar_flux, np.ones(81)/81.0, mode=\"same\")\n", + " \n", + "\n", + " return solar_flux, solar_dec, solar_smooth\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# add histogram plotting function -- simple one\n", + "\n", + "# change solar plot\n", + "\n", + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted FUV Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#fuv-dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " \n", + " def plot(self):\n", + " # make the interactive plots with sub-solar plots\n", + " self.figure = make_subplots(rows=2, cols=1, subplot_titles=(self.name, \"Solar Radio Flux\"))\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=self.data[self.x], \n", + " y=self.data[self.y],\n", + " mode=\"markers\",\n", + " hovertext=self.labels,\n", + " hoverinfo=\"x+y+text\", \n", + " name=\"Dark Rates\"), \n", + " row=1, col=1)\n", + " \n", + " grab_solar_files('/grp/hst/cos2/solar_data')\n", + " solar_flux, solar_dec, solar_smooth = compile_solar_data('/grp/hst/cos2/solar_data')\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_dec, \n", + " y=solar_flux,\n", + " mode=\"lines\",\n", + " name=\"10.7 cm\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_dec[:-41], \n", + " y=solar_smooth[:-41],\n", + " mode=\"lines\",\n", + " name=\"10.7 cm Smoothed\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " date_min = self.data[self.x].min()\n", + " date_max = self.data[self.x].max()\n", + " print(date_min, date_max)\n", + " \n", + " self.figure['layout']['xaxis1'].update(title=\"Year\")\n", + " self.figure['layout']['yaxis1'].update(title=\"Dark Rate\")\n", + " self.figure['layout']['xaxis2'].update(title=\"Year\", range=[date_min, date_max])\n", + " self.figure['layout']['yaxis2'].update(title=\"Solar Radio Flux\")\n", + "\n", + " \n", + " def plot_histogram(self, nbins=30):\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " # self.data[self.y] should be all dark rates\n", + " counts, bins = np.histogram(self.data[self.y], bins=nbins)\n", + " cuml_dist = np.cumsum(counts)\n", + " count_99 = abs(cuml_dist / float(cuml_dist.max()) - .99).argmin()\n", + " count_95 = abs(cuml_dist / float(cuml_dist.max()) - .95).argmin()\n", + " \n", + " mean = self.data[self.y].mean()\n", + " med = np.median(self.data[self.y])\n", + " std = self.data[self.y].std() \n", + " onesig = med + std\n", + " twosig = med + (2 * std)\n", + " threesig = med + (3 * std)\n", + " dist95 = bins[count_95]\n", + " dist99 = bins[count_99]\n", + " lines = [mean, med, onesig, twosig, threesig, dist95, dist99]\n", + " \n", + " fig = go.Figure(data=[go.Histogram(x=self.data[self.y], nbinsx=nbins)])\n", + " for value in lines:\n", + " fig.add_shape(\n", + " dict(type=\"line\",\n", + " xref=\"x\",\n", + " yref=\"paper\",\n", + " x0=value,\n", + " y0=0,\n", + " x1=value,\n", + " y1=1) \n", + " )\n", + " \n", + " # fix this naming convention later\n", + " output = os.path.join(COS_MONITORING, \"FUVBDarkMonitor-Inner_hist_2020-05-15.html\") \n", + " fig.write_html(output)\n", + " \n", + " \n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + " \n", + "fuv_inner_monitor = FUVBInnerDarkMonitor()\n", + "fuv_inner_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# new functions for retrieving and organizing data with sunpy\n", + "\n", + "# a.Time takes datetime.datetime objects, strings, etc\n", + "def sunpy_retriever(start_date, end_date):\n", + " dummy_dates = [\"2009/1/1\", \"2009/2/1\"] \n", + " # ^ it literally does not matter what these dates are because of how Fido works\n", + " solar_flux_search = Fido.search(a.Time(dummy_dates[0], dummy_dates[1]),\n", + " a.Instrument('noaa-indices')) \n", + " solar_flux_results = Fido.fetch(solar_flux_search, path=SETTINGS[\"output\"])\n", + " solar_flux_file = solar_flux_results.data[0]\n", + " df_solar_flux = TimeSeries(solar_flux_file, source=\"NOAAIndices\").to_dataframe()\n", + " df_solar_flux_filtered = df_solar_flux[start_date: end_date]\n", + " \n", + " return df_solar_flux_filtered" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cmagness/miniconda3/envs/cosmos/lib/python3.7/site-packages/parfive/downloader.py:279: TqdmDeprecationWarning: This function will be removed in tqdm==5.0.0\n", + "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`\n", + " position=0)\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e29f5560404243a7a8f1923bcdfb3653", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(FloatProgress(value=0.0, description='Files Downloaded', max=1.0, style=ProgressStyle(descripti…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sunspot SWOsunspot RIsunspot ratiosunspot SWO smoothsunspot RI smoothradio fluxradio flux smoothgeomagnetic apgeomagnetic smooth
time
2019-01-0116.04.60.299.03.271.670.066.8
2019-02-01-1.00.5-1.008.73.070.669.876.7
2019-03-0114.85.60.398.32.871.569.766.6
2019-04-0111.55.50.487.92.672.469.666.7
2019-05-0118.15.90.347.42.371.369.676.7
2019-06-0111.60.70.067.32.268.169.656.5
2019-07-011.60.50.317.02.167.169.766.3
2019-08-012.50.30.167.02.167.069.876.2
2019-09-012.60.70.276.81.968.169.7106.2
2019-10-011.80.20.116.21.667.469.586.2
2019-11-011.10.30.27-1.0-0.670.2-1.04-1.0
2019-12-017.20.90.14-1.0-0.670.9-1.04-1.0
2020-01-019.23.80.41-1.0-0.672.3-1.05-1.0
\n", + "
" + ], + "text/plain": [ + " sunspot SWO sunspot RI sunspot ratio sunspot SWO smooth \\\n", + "time \n", + "2019-01-01 16.0 4.6 0.29 9.0 \n", + "2019-02-01 -1.0 0.5 -1.00 8.7 \n", + "2019-03-01 14.8 5.6 0.39 8.3 \n", + "2019-04-01 11.5 5.5 0.48 7.9 \n", + "2019-05-01 18.1 5.9 0.34 7.4 \n", + "2019-06-01 11.6 0.7 0.06 7.3 \n", + "2019-07-01 1.6 0.5 0.31 7.0 \n", + "2019-08-01 2.5 0.3 0.16 7.0 \n", + "2019-09-01 2.6 0.7 0.27 6.8 \n", + "2019-10-01 1.8 0.2 0.11 6.2 \n", + "2019-11-01 1.1 0.3 0.27 -1.0 \n", + "2019-12-01 7.2 0.9 0.14 -1.0 \n", + "2020-01-01 9.2 3.8 0.41 -1.0 \n", + "\n", + " sunspot RI smooth radio flux radio flux smooth geomagnetic ap \\\n", + "time \n", + "2019-01-01 3.2 71.6 70.0 6 \n", + "2019-02-01 3.0 70.6 69.8 7 \n", + "2019-03-01 2.8 71.5 69.7 6 \n", + "2019-04-01 2.6 72.4 69.6 6 \n", + "2019-05-01 2.3 71.3 69.6 7 \n", + "2019-06-01 2.2 68.1 69.6 5 \n", + "2019-07-01 2.1 67.1 69.7 6 \n", + "2019-08-01 2.1 67.0 69.8 7 \n", + "2019-09-01 1.9 68.1 69.7 10 \n", + "2019-10-01 1.6 67.4 69.5 8 \n", + "2019-11-01 -0.6 70.2 -1.0 4 \n", + "2019-12-01 -0.6 70.9 -1.0 4 \n", + "2020-01-01 -0.6 72.3 -1.0 5 \n", + "\n", + " geomagnetic smooth \n", + "time \n", + "2019-01-01 6.8 \n", + "2019-02-01 6.7 \n", + "2019-03-01 6.6 \n", + "2019-04-01 6.7 \n", + "2019-05-01 6.7 \n", + "2019-06-01 6.5 \n", + "2019-07-01 6.3 \n", + "2019-08-01 6.2 \n", + "2019-09-01 6.2 \n", + "2019-10-01 6.2 \n", + "2019-11-01 -1.0 \n", + "2019-12-01 -1.0 \n", + "2020-01-01 -1.0 " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "start_date = datetime.datetime(2019, 1, 1)\n", + "end_date = datetime.datetime(2020, 1, 1)\n", + "data_df = sunpy_retriever(start_date, end_date)\n", + "data_df" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "# new monitor functions\n", + "def dark_filter(df_row, filter_pha, location):\n", + " good_pha = (2, 23)\n", + " # time step stuff\n", + " time_step = 25\n", + " time_bins = df_row['TIME_3'][::time_step]\n", + " lat = df_row['LATITUDE'][::time_step][:-1]\n", + " lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # try commenting these out, since lat and lon don't seem to be used\n", + "# lat = df_row['LATITUDE'][::time_step][:-1]\n", + "# lon = df_row['LONGITUDE'][::time_step][:-1]\n", + " \n", + " # filtering pha\n", + " if filter_pha:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME'])\n", + " else:\n", + " event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'TIME']].to_frame().T\n", + " event_df = explode_df(event_df, ['XCORR', 'YCORR', 'TIME'])\n", + " \n", + " # creating event dataframe and filtering it by location on the detector\n", + " npix = (location[1] - location[0]) * (location[3] - location[2])\n", + " index = np.where((event_df['XCORR'] > location[0]) &\n", + " (event_df['XCORR'] < location[1]) &\n", + " (event_df['YCORR'] > location[2]) &\n", + " (event_df['YCORR'] < location[3]))\n", + " filtered_row = event_df.iloc[index].reset_index(drop=True)\n", + "\n", + " #filtered events only need to be further filtered by PHA if not NUV\n", + " if filter_pha:\n", + " filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & (filtered_row['PHA'] < good_pha[1])]\n", + "\n", + " counts = np.histogram(filtered_row.TIME, bins=time_bins)[0]\n", + "\n", + " date = absolute_time(\n", + " expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), time=time_bins.tolist()\n", + " ).to_datetime()[:-1]\n", + "\n", + " dark_rate = counts / npix / time_step\n", + "\n", + " return pd.DataFrame({'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date],\n", + " 'ROOTNAME': df_row['ROOTNAME']})\n", + "\n", + "\n", + "# a.Time takes datetime.datetime objects, strings, etc\n", + "# None default so there is no filtering if not necessary\n", + "def sunpy_retriever(start_date=None, end_date=None):\n", + " dummy_dates = [\"2009/1/1\", \"2009/2/1\"] \n", + " # ^ it literally does not matter what these dates are because of how Fido works\n", + " # still will get all available data and needs to be filtered later\n", + " # at least, as of the June 2020 version of sunpy\n", + " # can change that to start_date and end_date if sunpy changes\n", + " solar_flux_search = Fido.search(a.Time(dummy_dates[0], dummy_dates[1]),\n", + " a.Instrument('noaa-indices')) \n", + " solar_flux_results = Fido.fetch(solar_flux_search, path=SETTINGS[\"output\"])\n", + " solar_flux_file = solar_flux_results.data[0]\n", + " df_solar_flux = TimeSeries(solar_flux_file, source=\"NOAAIndices\").to_dataframe()\n", + " \n", + " if start_date and end_date:\n", + " df_solar_flux_filtered = df_solar_flux[start_date: end_date]\n", + " elif not end_date: # if there is a start date but no end date\n", + " df_solar_flux_filtered = df_solar_flux[start_date:]\n", + " elif not start_date: # if there is an end date but not start date\n", + " df_solar_flux_filtered = df_solar_flux[0:end_date]\n", + " \n", + " return df_solar_flux_filtered" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "class DarkMonitor(BaseMonitor):\n", + " \"\"\"Abstracted Dark Monitor. Not meant to be used directly but rather inherited by specific segment and region\n", + " dark monitors\"\"\"\n", + " labels = ['ROOTNAME']\n", + " output = COS_MONITORING\n", + " docs = \"https://spacetelescope.github.io/cosmo/monitors.html#dark-rate-monitors\"\n", + " segment = None\n", + " location = None\n", + " data_model = DarkDataModel\n", + " plottype = 'scatter'\n", + " x = 'date'\n", + " y = 'darks'\n", + "\n", + " def get_data(self): # -> Any: fix this later, should be fine in the monitor, just not in jupyter notebook\n", + " filtered_rows = []\n", + " for _, row in self.model.new_data.iterrows():\n", + " if row.EXPSTART == 0:\n", + " continue\n", + " if row.SEGMENT == self.segment: \n", + " if row.SEGMENT == \"N/A\": #NUV\n", + " filtered_rows.append(dark_filter(row, False, self.location))\n", + " else: # Any of the FUV situations\n", + " filtered_rows.append(dark_filter(row, True, self.location))\n", + " filtered_df = pd.concat(filtered_rows).reset_index(drop=True)\n", + "\n", + " return explode_df(filtered_df, ['darks', 'date'])\n", + "\n", + " \n", + " def plot(self):\n", + " # make the interactive plots with sub-solar plots\n", + " self.figure = make_subplots(rows=2, cols=1, subplot_titles=(self.name, \"Solar Radio Flux\"))\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=self.data[self.x], \n", + " y=self.data[self.y],\n", + " mode=\"markers\",\n", + " hovertext=self.labels,\n", + " hoverinfo=\"x+y+text\", \n", + " name=\"Dark Rates\"), \n", + " row=1, col=1)\n", + " \n", + " date_min = self.data[self.x].min()\n", + " date_max = self.data[self.x].max()\n", + " print(date_min, date_max)\n", + " \n", + " sunpy_data = sunpy_retriever(date_min, date_max)\n", + " solar_time = sunpy_data.index\n", + " solar_flux = sunpy_data[\"radio flux\"]\n", + " solar_flux_smooth = sunpy_data[\"radio flux smooth\"]\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_time, \n", + " y=solar_flux,\n", + " mode=\"lines\",\n", + " name=\"10.7 cm\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " self.figure.add_trace(\n", + " go.Scatter(x=solar_time, \n", + " y=solar_flux_smoothed,\n", + " mode=\"lines\",\n", + " name=\"10.7 cm Smoothed\"),\n", + " row=2, col=1\n", + " )\n", + " \n", + " self.figure['layout']['xaxis1'].update(title=\"Year\")\n", + " self.figure['layout']['yaxis1'].update(title=\"Dark Rate\")\n", + " self.figure['layout']['xaxis2'].update(title=\"Year\", range=[date_min, date_max])\n", + " self.figure['layout']['yaxis2'].update(title=\"Solar Radio Flux\")\n", + "\n", + " \n", + " def plot_histogram(self, nbins=30):\n", + " if self.data is None:\n", + " self.data = self.get_data()\n", + " \n", + " # self.data[self.y] should be all dark rates\n", + " counts, bins = np.histogram(self.data[self.y], bins=nbins)\n", + " cuml_dist = np.cumsum(counts)\n", + " count_99 = abs(cuml_dist / float(cuml_dist.max()) - .99).argmin()\n", + " count_95 = abs(cuml_dist / float(cuml_dist.max()) - .95).argmin()\n", + " \n", + " mean = self.data[self.y].mean()\n", + " med = np.median(self.data[self.y])\n", + " std = self.data[self.y].std() \n", + " onesig = med + std\n", + " twosig = med + (2 * std)\n", + " threesig = med + (3 * std)\n", + " dist95 = bins[count_95]\n", + " dist99 = bins[count_99]\n", + " lines = [mean, med, onesig, twosig, threesig, dist95, dist99]\n", + " \n", + " fig = go.Figure(data=[go.Histogram(x=self.data[self.y], nbinsx=nbins)])\n", + " for value in lines:\n", + " fig.add_shape(\n", + " dict(type=\"line\",\n", + " xref=\"x\",\n", + " yref=\"paper\",\n", + " x0=value,\n", + " y0=0,\n", + " x1=value,\n", + " y1=1) \n", + " )\n", + " \n", + " # fix this naming convention later\n", + " output = os.path.join(COS_MONITORING, \"FUVBDarkMonitor-Inner_hist_2020-05-15.html\") \n", + " fig.write_html(output)\n", + " \n", + " \n", + " def store_results(self):\n", + " # TODO: Define results to store\n", + " pass\n", + "\n", + " def track(self):\n", + " # TODO: Define something to track\n", + " pass\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "ename": "OperationalError", + "evalue": "unable to open database file", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mOperationalError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self, reuse_if_open)\u001b[0m\n\u001b[1;32m 3034\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0m__exception_wrapper__\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3035\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_connection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_connect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3036\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_version\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_connect\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 3372\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3373\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3374\u001b[0m \u001b[0;32mexcept\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/playhouse/sqlite_ext.py\u001b[0m in \u001b[0;36m_add_conn_hooks\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 964\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 965\u001b[0;31m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mSqliteExtDatabase\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 966\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_row_factory\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_add_conn_hooks\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 3382\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pragmas\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3383\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_set_pragmas\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3384\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_load_aggregates\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_set_pragmas\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 3397\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mpragma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pragmas\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3398\u001b[0;31m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'PRAGMA %s = %s;'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mpragma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3399\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mOperationalError\u001b[0m: unable to open database file", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mOperationalError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mexecute_sql\u001b[0;34m(self, sql, params, commit)\u001b[0m\n\u001b[1;32m 3096\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0m__exception_wrapper__\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3097\u001b[0;31m \u001b[0mcursor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcommit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3098\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mcursor\u001b[0;34m(self, commit)\u001b[0m\n\u001b[1;32m 3080\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mautoconnect\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3081\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3082\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self, reuse_if_open)\u001b[0m\n\u001b[1;32m 3037\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_set_server_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3038\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_initialize_connection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3039\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m__exit__\u001b[0;34m(self, exc_type, exc_value, traceback)\u001b[0m\n\u001b[1;32m 2872\u001b[0m \u001b[0mexc_args\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexc_value\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2873\u001b[0;31m \u001b[0mreraise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnew_type\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnew_type\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexc_value\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mexc_args\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2874\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mreraise\u001b[0;34m(tp, value, tb)\u001b[0m\n\u001b[1;32m 182\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__traceback__\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mtb\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 183\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwith_traceback\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 184\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self, reuse_if_open)\u001b[0m\n\u001b[1;32m 3034\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0m__exception_wrapper__\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3035\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_connection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_connect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3036\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_version\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_connect\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 3372\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3373\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3374\u001b[0m \u001b[0;32mexcept\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/playhouse/sqlite_ext.py\u001b[0m in \u001b[0;36m_add_conn_hooks\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 964\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 965\u001b[0;31m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mSqliteExtDatabase\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 966\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_row_factory\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_add_conn_hooks\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 3382\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pragmas\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3383\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_set_pragmas\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3384\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_load_aggregates\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_set_pragmas\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 3397\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mpragma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pragmas\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3398\u001b[0;31m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'PRAGMA %s = %s;'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mpragma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3399\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mOperationalError\u001b[0m: unable to open database file", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mOperationalError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mlocation\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m14990\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m405\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m740\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mfuv_inner_monitor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mFUVBInnerDarkMonitor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0mfuv_inner_monitor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmonitor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/monitorframe/monitor.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, find_new_data)\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresultcol\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 116\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 117\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmodel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata_model\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfind_new\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfind_new_data\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 118\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtoday\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 119\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_define_results_table\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/monitorframe/datamodel.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, find_new)\u001b[0m\n\u001b[1;32m 59\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 60\u001b[0m \u001b[0;31m# Attempt to create a database table model\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 61\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_generate_model\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 62\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 63\u001b[0m \u001b[0;31m# Read in and ingest new data\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/monitorframe/datamodel.py\u001b[0m in \u001b[0;36m_generate_model\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_generate_model\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;34m\"\"\"Return the database table model object if the table exists in the database.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 69\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_database\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtable_exists\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtable_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 70\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_database\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mdb\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 71\u001b[0m self.model = generate_models(\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mtable_exists\u001b[0;34m(self, table_name, schema)\u001b[0m\n\u001b[1;32m 3262\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3263\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtable_exists\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtable_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mschema\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3264\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mtable_name\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_tables\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mschema\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mschema\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3265\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3266\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_tables\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mschema\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mget_tables\u001b[0;34m(self, schema)\u001b[0m\n\u001b[1;32m 3588\u001b[0m \u001b[0mschema\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mschema\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;34m'main'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3589\u001b[0m cursor = self.execute_sql('SELECT name FROM \"%s\".sqlite_master WHERE '\n\u001b[0;32m-> 3590\u001b[0;31m 'type=? ORDER BY name' % schema, ('table',))\n\u001b[0m\u001b[1;32m 3591\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mrow\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mrow\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchall\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3592\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mexecute_sql\u001b[0;34m(self, sql, params, commit)\u001b[0m\n\u001b[1;32m 3104\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3105\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcommit\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0min_transaction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3106\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcommit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3107\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3108\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m__exit__\u001b[0;34m(self, exc_type, exc_value, traceback)\u001b[0m\n\u001b[1;32m 2871\u001b[0m \u001b[0mnew_type\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexceptions\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mexc_type\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2872\u001b[0m \u001b[0mexc_args\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexc_value\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2873\u001b[0;31m \u001b[0mreraise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnew_type\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnew_type\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexc_value\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mexc_args\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2874\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2875\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mreraise\u001b[0;34m(tp, value, tb)\u001b[0m\n\u001b[1;32m 181\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mreraise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 182\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__traceback__\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mtb\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 183\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwith_traceback\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 184\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 185\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mexecute_sql\u001b[0;34m(self, sql, params, commit)\u001b[0m\n\u001b[1;32m 3095\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3096\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0m__exception_wrapper__\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3097\u001b[0;31m \u001b[0mcursor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcommit\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3098\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3099\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msql\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mcursor\u001b[0;34m(self, commit)\u001b[0m\n\u001b[1;32m 3079\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_closed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3080\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mautoconnect\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3081\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3082\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3083\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mInterfaceError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Error, database connection not opened.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self, reuse_if_open)\u001b[0m\n\u001b[1;32m 3036\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_version\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3037\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_set_server_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3038\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_initialize_connection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3039\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3040\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m__exit__\u001b[0;34m(self, exc_type, exc_value, traceback)\u001b[0m\n\u001b[1;32m 2871\u001b[0m \u001b[0mnew_type\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexceptions\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mexc_type\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2872\u001b[0m \u001b[0mexc_args\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexc_value\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2873\u001b[0;31m \u001b[0mreraise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnew_type\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnew_type\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexc_value\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mexc_args\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2874\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2875\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mreraise\u001b[0;34m(tp, value, tb)\u001b[0m\n\u001b[1;32m 181\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mreraise\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 182\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__traceback__\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mtb\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 183\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwith_traceback\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 184\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 185\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self, reuse_if_open)\u001b[0m\n\u001b[1;32m 3033\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3034\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0m__exception_wrapper__\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3035\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_connection\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_connect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3036\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_version\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3037\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_set_server_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_state\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_connect\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 3371\u001b[0m isolation_level=None, **self.connect_params)\n\u001b[1;32m 3372\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3373\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3374\u001b[0m \u001b[0;32mexcept\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3375\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/playhouse/sqlite_ext.py\u001b[0m in \u001b[0;36m_add_conn_hooks\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 963\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 964\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 965\u001b[0;31m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mSqliteExtDatabase\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_add_conn_hooks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 966\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_row_factory\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 967\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrow_factory\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_row_factory\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_add_conn_hooks\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 3381\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_attach_databases\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3382\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pragmas\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3383\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_set_pragmas\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3384\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_load_aggregates\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3385\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_load_collations\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/cosmos/lib/python3.7/site-packages/peewee.py\u001b[0m in \u001b[0;36m_set_pragmas\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 3396\u001b[0m \u001b[0mcursor\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mconn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcursor\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3397\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mpragma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_pragmas\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3398\u001b[0;31m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexecute\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'PRAGMA %s = %s;'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mpragma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3399\u001b[0m \u001b[0mcursor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3400\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mOperationalError\u001b[0m: unable to open database file" + ] + } + ], + "source": [ + "class FUVBInnerDarkMonitor(DarkMonitor):\n", + " \"\"\"FUVB dark monitor for inner region\"\"\"\n", + " name = 'FUVB Dark Monitor - Inner'\n", + " segment = 'FUVB'\n", + " location = (1000, 14990, 405, 740)\n", + " \n", + "fuv_inner_monitor = FUVBInnerDarkMonitor()\n", + "fuv_inner_monitor.monitor()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/README.md b/README.md index cc4cfc8..f01e8e6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ COSMO is made up of a series of monitors and their data built on the light-weigh [monitorframe framework](https://github.com/spacetelescope/monitor-framework). These monitors are important for ensuring that COS is operating nominally as well as extending its mission lifetime. -Check out the COSMO documenation [here](https://spacetelescope.github.io/cosmo/) +Check out the COSMO documentation [here](https://spacetelescope.github.io +/cosmo/) ## Contributing Please open a new [issue](https://github.com/spacetelescope/cosmo/issues) or new diff --git a/cosmo/filesystem.py b/cosmo/filesystem.py index dae743c..7c0591d 100644 --- a/cosmo/filesystem.py +++ b/cosmo/filesystem.py @@ -278,7 +278,8 @@ def reduce_to_stat(self, description: dict): del filedata[key] -def find_files(file_pattern: str, data_dir: str = FILES_SOURCE, subdir_pattern: Union[str, None] = '?????') -> list: +def find_files(file_pattern: str, data_dir: str = FILES_SOURCE, + subdir_pattern: Union[str, None] = None) -> list: """Find COS data files from a source directory. The default is the cosmo data directory subdirectories layout pattern. A different subdirectory pattern can be used or """ diff --git a/cosmo/monitors/dark_monitors.py b/cosmo/monitors/dark_monitors.py new file mode 100644 index 0000000..63ceb6b --- /dev/null +++ b/cosmo/monitors/dark_monitors.py @@ -0,0 +1,601 @@ + +import os +import json +import datetime + +import numpy as np +import pandas as pd +import plotly.io as pio +import plotly.express as px +import plotly.graph_objs as go + +# from tqdm import tqdm +from typing import Any +from urllib import request +from itertools import repeat +from plotly.subplots import make_subplots +from monitorframe.monitor import BaseMonitor +from astropy.convolution import Box1DKernel, convolve + +from .. import SETTINGS +from .data_models import DarkDataModel +from ..monitor_helpers import explode_df, absolute_time + +COS_MONITORING = SETTINGS['output'] +NOAA_URL = 'https://services.swpc.noaa.gov/json/solar-cycle/observed-solar-cycle-indices.json' + +# ----------------------------------------------------------------------------# + + +# def run_all_dark_monitors(): +# fuva_bottom_monitor = FUVABottomDarkMonitor() +# fuva_left_monitor = FUVALeftDarkMonitor() +# fuva_top_monitor = FUVATopDarkMonitor() +# fuva_right_monitor = FUVARightDarkMonitor() +# fuva_inner_monitor = FUVAInnerDarkMonitor() +# fuvb_bottom_monitor = FUVBBottomDarkMonitor() +# fuvb_left_monitor = FUVBLeftDarkMonitor() +# fuvb_top_monitor = FUVBTopDarkMonitor() +# fuvb_right_monitor = FUVBRightDarkMonitor() +# fuvb_inner_monitor = FUVBInnerDarkMonitor() +# nuv_monitor = NUVDarkMonitor() +# for monitor in tqdm([fuva_bottom_monitor, fuva_left_monitor, +# fuva_top_monitor, fuva_right_monitor, +# fuva_inner_monitor, fuvb_bottom_monitor, +# fuvb_left_monitor, fuvb_top_monitor, +# fuvb_right_monitor, fuvb_inner_monitor, +# nuv_monitor]): +# monitor.monitor() + + +def dark_filter(df_row, filter_pha, location): + """Given a row corresponding to a dark corrtag file, filter it based on + the location and PHA (if FUV), and calculate dark rate information. Will + return the exploded dataframe with the correct dark information for that + one file.""" + good_pha = (2, 23) + # time step stuff + time_step = 25 + time_bins = df_row['TIME_3'][::time_step] + lat = df_row['LATITUDE'][::time_step][:-1] + lon = df_row['LONGITUDE'][::time_step][:-1] + + # filtering pha + if filter_pha: + event_df = df_row[ + ['SEGMENT', 'XCORR', 'YCORR', 'PHA', 'TIME']].to_frame().T + event_df = explode_df(event_df, ['XCORR', 'YCORR', 'PHA', 'TIME']) + else: + event_df = df_row[['SEGMENT', 'XCORR', 'YCORR', 'TIME']].to_frame().T + event_df = explode_df(event_df, ['XCORR', 'YCORR', 'TIME']) + + # creating event dataframe and filtering it by location on the detector + npix = (location[1] - location[0]) * (location[3] - location[2]) + index = np.where((event_df['XCORR'] > location[0]) & ( + event_df['XCORR'] < location[1]) & ( + event_df['YCORR'] > location[2]) & ( + event_df['YCORR'] < location[3])) + filtered_row = event_df.iloc[index].reset_index(drop=True) + + # filtered events only need to be further filtered by PHA if not NUV + if filter_pha: + filtered_row = filtered_row[(filtered_row['PHA'] > good_pha[0]) & ( + filtered_row['PHA'] < good_pha[1])] + + counts = np.histogram(filtered_row.TIME, bins=time_bins)[0] + + date = absolute_time( + expstart=list(repeat(df_row['EXPSTART'], len(time_bins))), + time=time_bins.tolist()).to_datetime()[:-1] + + dark_rate = counts / npix / time_step + + return pd.DataFrame({ + 'segment': df_row['SEGMENT'], 'darks': [dark_rate], 'date': [date], + 'rootname': df_row['ROOTNAME'], 'latitude': [lat], "longitude": [lon] + }) + + +def get_solar_data(url, datemin, datemax, box=4): + """Download the most recent solar data, save as file and dataframe, + filter dataframe to date range. Also replace -1 values in the smoothed + flux.""" + response = request.urlopen(url) + if response.status == 200: + data = json.loads(response.read()) + else: + print("Invalid response! HTTP Status Code: {}".format(response.status)) + df = pd.DataFrame(data) + dates = [datetime.datetime.strptime(val, '%Y-%m') for val in + df['time-tag']] + df.index = pd.DatetimeIndex(dates) + + todays_date = datetime.datetime.today().strftime('%b%d_%Y') + outfile = os.path.join(COS_MONITORING, + "noaa_solar_indices_{}.txt".format(todays_date)) + # print("Saving outfile: {}".format(outfile)) + df.to_csv(outfile, header=True, index=True) + + # print("Filtering the dataframe to the date range: {}, {}".format(datemin, + # datemax)) + df = df.loc[datemin:datemax] + # smoothing the f10.7 data + kernel = Box1DKernel(box) + smoothed_107 = convolve(df["f10.7"], kernel) + df["box_convolved_f10.7"] = smoothed_107 + + return df + + +class DarkMonitor(BaseMonitor): + """Abstracted Dark Monitor. Not meant to be used directly but rather + inherited by specific segment and region dark monitors""" + labels = ['rootname'] + output = COS_MONITORING + docs = "https://spacetelescope.github.io/cosmo/monitors.html#dark-rate" \ + "-monitors" + segment = None + location = None + data_model = DarkDataModel + plottype = 'scatter' + x = 'date' + y = 'darks' + # # # defaults, can change these but optional definitions in the Monitor + # objects # # # + multi = False + sub_names = None + filter_saa = True + inner_region = 4 # number region that corresponds to the inner_region, + + # for FUV use only. + + def __init__(self, datemin=None, datemax=None, *args): + """Class Initialization to subclass superclass __init__ and add + optional date parameters.""" + super().__init__(*args) + if datemin: + self.datemin = datemin + else: + self.datemin = "2009-01-01" # earliest start date + if datemax: + self.datemax = datemax + else: + self.datemax = self.date # this is defined in the superclass + + def get_data(self): # -> Any: fix this later, + """Required method to get the data necessary for plotting in the + correct format. Takes care of all data organization and "explosion" + of dataframes. Returns full dataframe with correct calculations and + columns.""" + # should be fine in the monitor, just not in jupyter notebook + if self.multi: + # prime the pump + exploded_df = self.filter_data(self.location[0]) + exploded_df["region"] = 0 + for index, location in enumerate(self.location[1:]): + sub_exploded_df = self.filter_data(location) + sub_exploded_df["region"] = index + 1 + exploded_df = exploded_df.append(sub_exploded_df) + + else: + exploded_df = self.filter_data(self.location) + + return exploded_df + + def filter_data(self, location): + """Given a location (region) on the detector, filter down the new + data from the DataModel as appropriate and perform dark filtering + and "explosion" of dataframe as necessary. Return the fully + "exploded" data for that location.""" + filtered_rows = [] + for _, row in self.model.new_data.iterrows(): + if row.EXPSTART == 0: + continue + if row.SEGMENT == self.segment: + if row.SEGMENT == "N/A": # NUV + filtered_rows.append(dark_filter(row, False, location)) + else: # Any of the FUV situations + filtered_rows.append(dark_filter(row, True, location)) + filtered_df = pd.concat(filtered_rows).reset_index(drop=True) + + exploded_df = explode_df(filtered_df, + ['darks', 'date', 'latitude', 'longitude']) + + # filtering on date + index = np.where((exploded_df["date"] >= self.datemin) & ( + exploded_df["date"] <= self.datemax)) + exploded_df = exploded_df.iloc[index].reset_index(drop=True) + + # after exploding, add SAA filtering if required + if self.filter_saa: + exploded_df["no_saa"] = np.where( + exploded_df.eval("latitude > 10 or longitude < 260"), 1, 0) + + return exploded_df + + def plot(self): + """Make the interactive subplots, including the solar plot, based on + how many locations (regions) are given. Write out the file to the + correct outpath.""" + # make the interactive plots with sub-solar plots + if self.multi: + rows = len(self.location) + 1 + self.sub_names += ["Solar Radio Flux"] + titles = tuple(self.sub_names) + else: + # only one region means two subplots + rows = 2 + titles = (self.name, "Solar Radio Flux") + + fig_height = 750 + delta = 250 + if rows > 3: + fig_height = delta * rows + + pio.templates.default = "simple_white" + + datemin = self.data[self.x].min() + datemax = self.data[self.x].max() + + self.figure = make_subplots(rows=rows, cols=1, shared_xaxes=True, + subplot_titles=titles, x_title="Year", + vertical_spacing=0.05) + self.figure.update_layout(height=fig_height, width=1200, + title_text=self.name + f": " + f"{ + datemin:%Y-%m-%d} - " + f"{datemax:%Y-%m-%d}") + + if self.multi: + # prime the pump again + region_x_data = self.data[self.x].where(self.data["region"] == 0) + region_y_data = self.data[self.y].where(self.data["region"] == 0) + self.figure.add_trace( + go.Scatter(x=region_x_data, y=region_y_data, mode="markers", + marker=dict(color=self.data["no_saa"], + colorscale=["red", "black"], size=5), + hovertext=self.labels, hoverinfo="x+y+text", + name="Mean Dark Rate"), row=1, col=1) + self.figure.update_yaxes( + title_text="Mean Dark Rate
(counts/pix/sec)", row=1, col=1) + for index, location in enumerate(self.location[1:]): + index = index + 1 + region_x_data = self.data[self.x].where( + self.data["region"] == index) + region_y_data = self.data[self.y].where( + self.data["region"] == index) + self.figure.add_trace( + go.Scatter(x=region_x_data, y=region_y_data, + showlegend=False, mode="markers", + marker=dict(color=self.data["no_saa"], + colorscale=["red", "black"], + size=5), hovertext=self.labels, + hoverinfo="x+y+text", name="Mean Dark Rate"), + row=index + 1, col=1) + self.figure.update_yaxes( + title_text="Mean Dark Rate
(counts/pix/sec)", + row=index + 1, col=1) + + else: + # single plot + self.figure.add_trace( + go.Scatter(x=self.data[self.x], y=self.data[self.y], + mode="markers", + marker=dict(color=self.data["no_saa"], + colorscale=["red", "black"], size=5), + hovertext=self.labels, hoverinfo="x+y+text", + name="Mean Dark Rate"), row=1, col=1) + self.figure.update_yaxes( + title_text="Mean Dark Rate
(counts/pix/sec)", row=1, col=1) + + ## this is solar stuff only until the next ## + + # sunpy_data = sunpy_retriever(date_min, date_max) + solar_data = get_solar_data(NOAA_URL, datemin, datemax) + solar_time = solar_data.index + solar_flux = solar_data["f10.7"] + solar_flux_smooth = solar_data["box_convolved_f10.7"] + + self.figure.add_trace( + go.Scatter(x=solar_time, y=solar_flux, mode="lines", + line=dict(dash="dot", color="#0F2080"), name="10.7 cm"), + row=rows, col=1) + + self.figure.add_trace( + go.Scatter(x=solar_time, y=solar_flux_smooth, mode="lines", + line=dict(color="#85C0F9"), name="10.7 cm Smoothed"), + row=rows, col=1) + + self.figure.update_yaxes(title_text="Solar Radio Flux", row=rows, + col=1) + + ## + + self.figure.update_xaxes(showgrid=True, showline=True, mirror=True) + self.figure.update_yaxes(showgrid=True, showline=True, mirror=True) + + def plot_histogram(self, nbins=100): + """Make the interactive histogram which displays the distribution of + the data and the ETC dark rates. Write out the file to the + correct outpath.""" + if self.data is None: + self.data = self.get_data() + + dist995, dark_column, lines = self.calculate_histogram(nbins) + full_names = [f"Mean: {lines[0]:.2e}", f"Median: {lines[1]:.2e}", + f"2 sigma: {lines[3]:.2e}", f"3 sigma: {lines[4]:.2e}", + f"95%: {lines[5]:.2e}", f"99%: {lines[6]:.2e}"] + + # histogram + fig = go.Figure( + data=[go.Histogram(x=dark_column, nbinsx=nbins, showlegend=False)]) + + # value lines--have to do a shape and trace for both of them until + # plotly adds vertical line plotting features (because shapes can't + # be in the legend, only traces) + # also the indexing is all weird but it is correct (has to do with + # the onesig) + # should fix this later + fig.add_trace( + go.Scatter(x=[lines[0], lines[0]], y=[0, 1], mode="lines", + line=dict(color="#DC267F"), name=full_names[0])) + fig.add_shape( + dict(type="line", xref="x", yref="paper", x0=lines[0], y0=0, + x1=lines[0], y1=1, line=dict(color="#DC267F"))) + + fig.add_trace( + go.Scatter(x=[lines[1], lines[1]], y=[0, 1], mode="lines", + line=dict(color="#DC267F", dash="dash"), + name=full_names[1])) + fig.add_shape( + dict(type="line", xref="x", yref="paper", x0=lines[1], y0=0, + x1=lines[1], y1=1, line=dict(color="#DC267F", dash="dash"))) + + fig.add_trace( + go.Scatter(x=[lines[3], lines[3]], y=[0, 1], mode="lines", + line=dict(color="#FE6100"), name=full_names[2])) + fig.add_shape( + dict(type="line", xref="x", yref="paper", x0=lines[3], y0=0, + x1=lines[3], y1=1, line=dict(color="#FE6100"))) + + fig.add_trace( + go.Scatter(x=[lines[4], lines[4]], y=[0, 1], mode="lines", + line=dict(color="#FE6100", dash="dash"), + name=full_names[3])) + fig.add_shape( + dict(type="line", xref="x", yref="paper", x0=lines[4], y0=0, + x1=lines[4], y1=1, line=dict(color="#FE6100", dash="dash"))) + + fig.add_trace( + go.Scatter(x=[lines[5], lines[5]], y=[0, 1], mode="lines", + line=dict(color="#FFB000"), name=full_names[4])) + fig.add_shape( + dict(type="line", xref="x", yref="paper", x0=lines[5], y0=0, + x1=lines[5], y1=1, line=dict(color="#FFB000"))) + + fig.add_trace( + go.Scatter(x=[lines[6], lines[6]], y=[0, 1], mode="lines", + line=dict(color="#FFB000", dash="dash"), + name=full_names[5])) + fig.add_shape( + dict(type="line", xref="x", yref="paper", x0=lines[6], y0=0, + x1=lines[6], y1=1, line=dict(color="#FFB000", dash="dash"))) + + datemin = self.data[self.x].min() + datemax = self.data[self.x].max() + fig.update_xaxes(range=[0, dist995], title_text="Counts/Pix/Sec", + showline=True) + fig.update_yaxes(rangemode="tozero", title_text="Frequency", + showline=True) + fig.update_layout( + title_text=self.name + f" Histogram: {datemin:%Y-%m-%d} - " + f"{datemax:%Y-%m-%d}") + fig.update_layout(xaxis=dict(showexponent='all', exponentformat='e')) + fig.update_layout(yaxis_showgrid=True) + + # fix this naming convention later + if not self.output: + output = f'{os.path.join(os.getcwd(), f"{self._filename}_hist.html")}' + else: + # you would think you could use self.output but that gets + # updated somewhere + # in the superclass to include the full plot name so we can't + # use that. + # kind of a bug + output = os.path.join(COS_MONITORING, + f"{self._filename}_hist.html") + + fig.write_html(output) + + def calculate_histogram(self, nbins=100): + """Calculate the histogram distribution for the important plot and + ETC values.""" + if self.data is None: + self.data = self.get_data() + + # filter out the flag == 0 / grab the flag == 1 from self.data[self.y] + if self.filter_saa: + dark_column = self.data[self.y].loc[self.data["no_saa"] == 1] + if "FUV" in self.segment: + dark_column = self.data[self.y].loc[ + (self.data["no_saa"] == 1) & ( + self.data["region"] == self.inner_region)] + else: + dark_column = self.data[self.y] + + counts, bins = np.histogram(dark_column, bins=nbins) + cuml_dist = np.cumsum(counts) + count_99 = abs(cuml_dist / float(cuml_dist.max()) - .99).argmin() + count_95 = abs(cuml_dist / float(cuml_dist.max()) - .95).argmin() + # only used for plotting + count995 = abs(cuml_dist / float(cuml_dist.max()) - .995).argmin() + + mean = dark_column.mean() + med = np.median(dark_column) + std = dark_column.std() + onesig = med + std + twosig = med + (2 * std) + threesig = med + (3 * std) + dist95 = bins[count_95] + dist99 = bins[count_99] + dist995 = bins[count995] + values = [mean, med, onesig, twosig, threesig, dist95, dist99] + + return dist995, dark_column, values + + def track(self, nbins=100): + "Tracking method to track ETC Dark Rate measurements." + _, _, track_list = self.calculate_histogram(nbins) + return track_list + + def plot_orbital_variation(self): + """Make the orbital variation plot and write out the file to the + correct outpath.""" + if self.data is None: + self.data = self.get_data() + + colormin = self.data["darks"].min() + colormax = self.data["darks"].max() + fig = go.Figure(data=[ + go.Scatter(x=self.data["longitude"], y=self.data["latitude"], + mode="markers", + marker=dict(color=self.data["darks"], size=2, + colorscale='Viridis', opacity=0.5, + colorbar=dict(thickness=20, + exponentformat="e", + title=dict(text="Dark Rate")), + cmin=colormin, cmax=colormax))]) + + datemin = self.data[self.x].min() + datemax = self.data[self.x].max() + fig.update_layout( + title_text=self.name + f" Orbital Variation: {datemin:%Y-%m-%d} - " + f"{datemax:%Y-%m-%d}") + fig.update_xaxes(title_text="Longitude", showline=True) + fig.update_yaxes(title_text="Latitude", showline=True) + + if not self.output: + output = os.path.join(os.getcwd(), + f"{self._filename}_orbital.html") + else: + # you would think you could use self.output but that gets + # updated somewhere + # in the superclass to include the full plot name so we can't + # use that. + # kind of a bug + output = os.path.join(COS_MONITORING, + f"{self._filename}_orbital.html") + + fig.write_html(output) + + def store_results(self): + # TODO: Define results to store + pass + + +# ----------------------------------------------------------------------------# + + +class FUVADarkMonitor(DarkMonitor): + """FUVA Dark Monitor for all edges and inner region.""" + name = 'FUVA Dark Monitor' + segment = 'FUVA' + multi = True + location = [(1060, 15250, 296, 375), (1060, 1260, 296, 734), + (1060, 15250, 660, 734), (15119, 15250, 296, 734), + (1260, 15119, 375, 660)] + sub_names = ["FUVA Dark Monitor - Bottom", "FUVA Dark Monitor - Left", + "FUVA Dark Monitor - Top", "FUVA Dark Monitor - Right", + "FUVA Dark Monitor - Inner"] + + +class FUVBDarkMonitor(DarkMonitor): + """FUVB Dark Monitor for all edges and inner region.""" + name = 'FUVB Dark Monitor' + segment = 'FUVB' + multi = True + location = [(809, 15182, 360, 405), (809, 1000, 360, 785), + (809, 15182, 740, 785), (14990, 15182, 360, 785), + (1000, 14990, 405, 740)] + sub_names = ["FUVB Dark Monitor - Bottom", "FUVB Dark Monitor - Left", + "FUVB Dark Monitor - Top", "FUVB Dark Monitor - Right", + "FUVB Dark Monitor - Inner"] + + +class FUVABottomDarkMonitor(DarkMonitor): + """FUVA Dark Monitor for bottom edge.""" + segment = 'FUVA' + location = (1060, 15250, 296, 375) + name = 'FUVA Dark Monitor - Bottom' + + +class FUVALeftDarkMonitor(DarkMonitor): + """FUVA Dark Monitor for left edge.""" + name = 'FUVA Dark Monitor - Left' + segment = 'FUVA' + location = (1060, 1260, 296, 734) + + +class FUVATopDarkMonitor(DarkMonitor): + """FUVA Dark Monitor for top edge.""" + name = 'FUVA Dark Monitor - Top' + segment = 'FUVA' + location = (1060, 15250, 660, 734) + + +class FUVARightDarkMonitor(DarkMonitor): + """FUVA Dark Monitor for right edge.""" + name = 'FUVA Dark Monitor - Right' + segment = 'FUVA' + location = (15119, 15250, 296, 734) + + +class FUVAInnerDarkMonitor(DarkMonitor): + """FUVA Dark Monitor for inner region.""" + name = 'FUVA Dark Monitor - Inner' + segment = 'FUVA' + location = (1260, 15119, 375, 660) + + +class FUVBBottomDarkMonitor(DarkMonitor): + """FUVB Dark Monitor for bottom edge.""" + name = 'FUVB Dark Monitor - Bottom' + segment = 'FUVB' + location = (809, 15182, 360, 405) + + +class FUVBLeftDarkMonitor(DarkMonitor): + """FUVB Dark Monitor for left edge.""" + name = 'FUVB Dark Monitor - Left' + segment = 'FUVB' + location = (809, 1000, 360, 785) + + +class FUVBTopDarkMonitor(DarkMonitor): + """FUVB Dark Monitor for top edge.""" + name = 'FUVB Dark Monitor - Top' + segment = 'FUVB' + location = (809, 15182, 740, 785) + + +class FUVBRightDarkMonitor(DarkMonitor): + """FUVB Dark Monitor for right edge.""" + name = 'FUVB Dark Monitor - Right' + segment = 'FUVB' + location = (14990, 15182, 360, 785) + + +class FUVBInnerDarkMonitor(DarkMonitor): + """FUVB Dark Monitor for inner region.""" + name = 'FUVB Dark Monitor - Inner' + segment = 'FUVB' + location = (1000, 14990, 405, 740) + + +class NUVDarkMonitor(DarkMonitor): + """NUV Dark Monitor for full detector.""" + name = "NUV Dark Monitor" + segment = "N/A" + location = (0, 1024, 0, 1024) + + diff --git a/cosmo/monitors/data_models.py b/cosmo/monitors/data_models.py index 503251e..b0a34f2 100644 --- a/cosmo/monitors/data_models.py +++ b/cosmo/monitors/data_models.py @@ -1,5 +1,7 @@ import pandas as pd import numpy as np +import os +from glob import glob from typing import List from monitorframe.datamodel import BaseDataModel @@ -10,12 +12,16 @@ from .. import SETTINGS FILES_SOURCE = SETTINGS['filesystem']['source'] +PROGRAMS = SETTINGS['dark_programs'] def dgestar_to_fgs(results: List[dict]) -> None: """Add a FGS key to each row dictionary.""" for item in results: - item.update({'FGS': item['DGESTAR'][-2:]}) # The dominant guide star key is the last 2 values in the string + item.update({ + 'FGS': item['DGESTAR'][-2:] + }) # The dominant guide star key is the last 2 # values in the + # string class AcqDataModel(BaseDataModel): @@ -26,37 +32,26 @@ class AcqDataModel(BaseDataModel): def get_new_data(self): header_request = { - 0: [ - 'ACQSLEWX', - 'ACQSLEWY', - 'ROOTNAME', - 'PROPOSID', - 'OBSTYPE', - 'SHUTTER', - 'LAMPEVNT', - 'ACQSTAT', - 'EXTENDED', - 'LINENUM', - 'APERTURE', - 'OPT_ELEM', - 'LIFE_ADJ', - 'CENWAVE', - 'DETECTOR', - 'EXPTYPE' - ], - 1: ['EXPSTART', 'NEVENTS'] - } + 0: ['ACQSLEWX', 'ACQSLEWY', 'ROOTNAME', 'PROPOSID', 'OBSTYPE', + 'SHUTTER', 'LAMPEVNT', 'ACQSTAT', 'EXTENDED', 'LINENUM', + 'APERTURE', 'OPT_ELEM', 'LIFE_ADJ', 'CENWAVE', 'DETECTOR', + 'EXPTYPE'], 1: ['EXPSTART', 'NEVENTS'] + } # Different ACQ types may not have the full set - header_defaults = {'ACQSLEWX': 0.0, 'ACQSLEWY': 0.0, 'NEVENTS': 0.0, 'LAMPEVNT': 0.0} + header_defaults = { + 'ACQSLEWX': 0.0, 'ACQSLEWY': 0.0, 'NEVENTS': 0.0, 'LAMPEVNT': 0.0 + } # SPT file header keys, extensions spt_header_request = {0: ['DGESTAR']} - files = find_files('*rawacq*', data_dir=self.files_source, subdir_pattern=self.subdir_pattern) + files = find_files('*rawacq*', data_dir=self.files_source, + subdir_pattern=self.subdir_pattern) if self.model is not None: - currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)] + currently_ingested = [item.FILENAME for item in + self.model.select(self.model.FILENAME)] for file in currently_ingested: files.remove(file) @@ -64,12 +59,10 @@ def get_new_data(self): if not files: # No new files return pd.DataFrame() - data_results = data_from_exposures( - files, - header_request=header_request, - header_defaults=header_defaults, - spt_header_request=spt_header_request, - ) + data_results = data_from_exposures(files, + header_request=header_request, + header_defaults=header_defaults, + spt_header_request=spt_header_request, ) dgestar_to_fgs(data_results) @@ -88,9 +81,9 @@ class OSMDataModel(BaseDataModel): def get_new_data(self): """Retrieve data.""" header_request = { - 0: ['ROOTNAME', 'DETECTOR', 'LIFE_ADJ', 'OPT_ELEM', 'CENWAVE', 'FPPOS', 'PROPOSID', 'OBSET_ID'], - 1: ['EXPSTART'] - } + 0: ['ROOTNAME', 'DETECTOR', 'LIFE_ADJ', 'OPT_ELEM', 'CENWAVE', + 'FPPOS', 'PROPOSID', 'OBSET_ID'], 1: ['EXPSTART'] + } table_request = {1: ['TIME', 'SHIFT_DISP', 'SHIFT_XDISP', 'SEGMENT']} @@ -98,58 +91,64 @@ def get_new_data(self): 'LAMPTAB': { 'match_keys': ['OPT_ELEM', 'CENWAVE', 'FPOFFSET'], 'table_request': {1: ['SEGMENT', 'FP_PIXEL_SHIFT']}, - }, - 'WCPTAB': {'match_keys': ['OPT_ELEM'], 'table_request': {1: ['XC_RANGE', 'SEARCH_OFFSET']}} - } + }, 'WCPTAB': { + 'match_keys': ['OPT_ELEM'], + 'table_request': {1: ['XC_RANGE', 'SEARCH_OFFSET']} + } + } - files = find_files('*lampflash*', data_dir=self.files_source, subdir_pattern=self.subdir_pattern) + files = find_files('*lampflash*', data_dir=self.files_source, + subdir_pattern=self.subdir_pattern) if self.model is not None: - currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)] + currently_ingested = [item.FILENAME for item in + self.model.select(self.model.FILENAME)] for file in currently_ingested: files.remove(file) - if not files: # No new files + if not files: # No new files return pd.DataFrame() data_results = pd.DataFrame( - data_from_exposures( - files, - header_request=header_request, - table_request=table_request, - reference_request=reference_request - ) - ) + data_from_exposures(files, header_request=header_request, + table_request=table_request, + reference_request=reference_request)) # Remove any rows that have empty data columns - data_results = data_results.drop( - data_results[data_results.apply(lambda x: not bool(len(x.SHIFT_DISP)), axis=1)].index.values - ).reset_index(drop=True) + data_results = data_results.drop(data_results[data_results.apply( + lambda x: not bool(len(x.SHIFT_DISP)), + axis=1)].index.values).reset_index(drop=True) # Add tsince data from SMSTable. try: sms_data = pd.DataFrame( - SMSTable.select(SMSTable.ROOTNAME, SMSTable.TSINCEOSM1, SMSTable.TSINCEOSM2).where( - # x << y -> x IN y (y must be a list) - SMSTable.ROOTNAME + 'q' << data_results.ROOTNAME.to_list()).dicts() - ) + SMSTable.select(SMSTable.ROOTNAME, SMSTable.TSINCEOSM1, + SMSTable.TSINCEOSM2).where( + # x << y -> x IN y (y must be a list) + SMSTable.ROOTNAME + 'q' << + data_results.ROOTNAME.to_list()).dicts()) except OperationalError as e: raise type(e)(str(e) + '\nSMS database is required.') - # It's possible that there could be a lag in between when the SMS data is updated and when new lampflashes + # It's possible that there could be a lag in between when the SMS + # data is updated and when new lampflashes # are added. - # Returning the empty data frame ensures that only files with a match in the SMS data are added... + # Returning the empty data frame ensures that only files with a + # match in the SMS data are added... # This may not be the best idea if sms_data.empty: return sms_data - # Need to add the 'q' at the end of the rootname.. For some reason those are missing from the SMS rootnames + # Need to add the 'q' at the end of the rootname.. For some reason + # those are missing from the SMS rootnames sms_data.ROOTNAME += 'q' - # Combine the data from the files with the data from the SMS table with an inner merge between the two. - # NOTE: this means that if a file does not have a corresponding entry in the SMSTable, it will not be in the + # Combine the data from the files with the data from the SMS table + # with an inner merge between the two. + # NOTE: this means that if a file does not have a corresponding + # entry in the SMSTable, it will not be in the # dataset used for monitoring. merged = pd.merge(data_results, sms_data, on='ROOTNAME') @@ -165,30 +164,85 @@ def get_new_data(self): extension_header_keys = ('EXPNAME',) data_keys = ('SI_V2_AVG', 'SI_V3_AVG') - reduce = {'SI_V2_AVG': ('mean', 'std', 'max'), 'SI_V3_AVG': ('mean', 'std', 'max')} + reduce = { + 'SI_V2_AVG': ('mean', 'std', 'max'), + 'SI_V3_AVG': ('mean', 'std', 'max') + } - files = find_files('*jit*', data_dir=self.files_source, subdir_pattern=self.subdir_pattern) + files = find_files('*jit*', data_dir=self.files_source, + subdir_pattern=self.subdir_pattern) if self.model is not None: - currently_ingested = [item.FILENAME for item in self.model.select(self.model.FILENAME)] + currently_ingested = [item.FILENAME for item in + self.model.select(self.model.FILENAME)] for file in currently_ingested: files.remove(file) - if not files: # No new files + if not files: # No new files return pd.DataFrame() data_results = pd.DataFrame( - data_from_jitters( - files, - primary_header_keys, - extension_header_keys, - data_keys, - reduce_to_stats=reduce - ) - ) - - # Remove any NaNs or inf that may occur from the statistics calculations. - data_results = data_results.replace([np.inf, -np.inf], np.nan).dropna().reset_index(drop=True) - - return data_results[~data_results.EXPTYPE.str.contains('ACQ|DARK|FLAT')] + data_from_jitters(files, primary_header_keys, + extension_header_keys, data_keys, + reduce_to_stats=reduce)) + + # Remove any NaNs or inf that may occur from the statistics + # calculations. + data_results = data_results.replace([np.inf, -np.inf], + np.nan).dropna().reset_index( + drop=True) + + return data_results[ + ~data_results.EXPTYPE.str.contains('ACQ|DARK|FLAT')] + + +def get_program_ids(pid_file): + """Retrieve the program IDs from the given text file.""" + programs_df = pd.read_csv(pid_file, delim_whitespace=True) + all_programs = [] + for col, col_data in programs_df.iteritems(): + all_programs += col_data.to_numpy(dtype=str).tolist() + + return all_programs + + +class DarkDataModel(BaseDataModel): + """DataModel for dark corrtag files.""" + cosmo_layout = False + + def get_new_data(self): + """Set the model for what data is to be retrieved from each dark + file.""" + # this way when you get new data it will get all the data + header_request = { + 0: ['ROOTNAME', 'SEGMENT'], 1: ['EXPTIME', 'EXPSTART'] + } + table_request = { + 1: ['PHA', 'XCORR', 'YCORR', 'TIME'], + 3: ['TIME', 'LATITUDE', 'LONGITUDE'] + } + + files = [] + + program_ids = get_program_ids(PROGRAMS) + + for prog_id in program_ids: + new_files_source = os.path.join(FILES_SOURCE, prog_id) + files += find_files('*corrtag*', data_dir=new_files_source) + + if self.model is not None: + currently_ingested = [item.FILENAME for item in + self.model.select(self.model.FILENAME)] + + for file in currently_ingested: + files.remove(file) + + if not files: # No new files + return pd.DataFrame() + + data_results = data_from_exposures(files, + header_request=header_request, + table_request=table_request) + + return data_results diff --git a/setup.py b/setup.py index 4ee0534..31a344c 100644 --- a/setup.py +++ b/setup.py @@ -16,15 +16,18 @@ 'setuptools', 'numpy>=1.11.1', 'astropy>=1.0.1', - 'plotly>=4.0.0', + 'plotly>=4.9.0', 'dask', 'pandas>=0.25.0', 'pytest', 'pyyaml', - 'peewee', + 'peewee>=3.13.3', # needs this for database model reasons 'crds', + 'sunpy[all]', 'monitorframe @ git+https://github.com/spacetelescope/monitor-framework@v1.2.0#egg=monitorframe' ], + dependency_links=['http://github.com/spacetelescope/monitor-framework' + '/tarball/master#egg=monitorframe'], package_data={'cosmo': ['pytest.ini']}, entry_points={ 'console_scripts': diff --git a/tests/cosmoconfig_test.yaml b/tests/cosmoconfig_test.yaml index 3ccc9dc..d9f3e19 100644 --- a/tests/cosmoconfig_test.yaml +++ b/tests/cosmoconfig_test.yaml @@ -12,6 +12,7 @@ sms: synchronous: 0 output: '' +dark_programs: './data/programs_test.txt' # Monitor data database data: diff --git a/tests/data/programs_test.txt b/tests/data/programs_test.txt new file mode 100644 index 0000000..3d488b5 --- /dev/null +++ b/tests/data/programs_test.txt @@ -0,0 +1,2 @@ +FUV NUV +15771 15776 \ No newline at end of file diff --git a/tests/test_dark_monitors.py b/tests/test_dark_monitors.py new file mode 100644 index 0000000..1fffda2 --- /dev/null +++ b/tests/test_dark_monitors.py @@ -0,0 +1,52 @@ +import os +import pytest + +from cosmo.monitors.dark_monitors import DarkMonitor, FUVADarkMonitor, \ + FUVBDarkMonitor, NUVDarkMonitor +from cosmo.monitors.data_models import DarkDataModel + +TEST_DATA = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data/') + +@pytest.fixture(params=[False, True]) +def set_darkmonitor(request, data_dir, here): + if request.param: + model = DarkDataModel() + model.ingest() + + def _set_monitor(monitor): + DarkDataModel.files_source = data_dir + DarkDataModel.subdir_pattern = None + + monitor.data_model = DarkDataModel + monitor.output = here + + active = monitor() + + return active + + return _set_monitor + + +class TestDarkMonitors: + + @pytest.fixture(autouse=True, params=[FUVADarkMonitor, + FUVBDarkMonitor, + NUVDarkMonitor]) + def darkmonitor(self, request, set_darkmonitor): + darkmonitor = set_darkmonitor(request.param) + + request.cls.darkmonitor = darkmonitor + + yield + + if request.cls.darkmonitor.model.model is not None: + request.cls.darkmonitor.model.model.drop_table(safe=True) + + def test_monitor_steps(self): + self.darkmonitor.initialize_data() + self.darkmonitor.run_analysis() + self.darkmonitor.plot() + self.darkmonitor.write_figure() + self.darkmonitor.store_results() + + assert os.path.exists(self.darkmonitor.output) diff --git a/tests/test_data_models.py b/tests/test_data_models.py index 6ed9a88..6d5c53c 100644 --- a/tests/test_data_models.py +++ b/tests/test_data_models.py @@ -2,7 +2,8 @@ import numpy as np import pytest -from cosmo.monitors.data_models import AcqDataModel, OSMDataModel +from cosmo.monitors.data_models import AcqDataModel, OSMDataModel, \ + DarkDataModel from cosmo.sms import SMSFinder @@ -119,3 +120,35 @@ def test_data_ingest(self): assert self.acqmodel.model is not None assert len(list(self.acqmodel.model.select())) == 9 + + +class TestDarkDataModel: + + @pytest.fixture(autouse=True) + def darkmodel(self, request, make_datamodel): + darkmodel = make_datamodel(DarkDataModel) + + request.cls.darkmodel = darkmodel # Add the data model to the test + # class + + yield + + if request.cls.darkmodel.model is not None: + request.cls.darkmodel.model.drop_table(safe=True) + + def test_data_collection(self): + assert isinstance(self.darkmodel.new_data, pd.DataFrame) + assert len(self.darkmodel.new_data) == 9 # There are 9 test data sets + + def test_content_collected(self): + keys_that_should_be_there = ( + # Header keywords + "ROOTNAME", "SEGMENT", "EXPTIME", "EXPSTART", + # Table keywords data extension + "PHA", "XCORR", "YCORR", "TIME", + # Table keywords third extension + "TIME_3", "LATITUDE", "LONGITUDE") + # seems like TIME_3 might not work...should check that + + for key in keys_that_should_be_there: + assert key in self.darkmodel.new_data