{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Initial imports and data download if you need the KPF input files" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "KP.20250208.17485.59.fits already exists. Skipping download.\n", "KP.20250208.17485.59_L1.fits already exists. Skipping download.\n" ] } ], "source": [ "# Jupyter auto-reload source code\n", "%load_ext autoreload\n", "%autoreload 2\n", "\n", "import os\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from astropy.io import fits\n", "from rvdata.core.models.level2 import RV2\n", "\n", "\n", "#To download the input files for this example, set download=1 to run the code block below\n", "download=1\n", "\n", "if download == 1:\n", " import requests\n", "\n", " def download_file(url, filename):\n", " response = requests.get(url)\n", " response.raise_for_status() # Check if the request was successful\n", " with open(filename, \"wb\") as file:\n", " file.write(response.content)\n", "\n", " #Download the native, input FITS files for this example if not already on your computer\n", " file_urls = {\n", " \"KPF\": [\n", " \"http://grinnell.as.arizona.edu/~rvdata/kpf/KP.20250208.17485.59.fits\",\n", " \"http://grinnell.as.arizona.edu/~rvdata/kpf/KP.20250208.17485.59_L1.fits\",\n", " ]\n", " }\n", "\n", " for file in file_urls[\"KPF\"]:\n", " filename = file.split(\"/\")[-1].split(\"?\")[0]\n", " if not os.path.exists(filename):\n", " print(f\"Downloading {filename}...\")\n", " download_file(file, filename)\n", " else:\n", " print(f\"{filename} already exists. Skipping download.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using the L2 translator to go from native KPF data format to the EPRV Standard Format" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Translating a KPF File [change file destination to where you have placed the L0 and L1 KPF files from Zenodo]\n", "l1file = 'KP.20250208.17485.59_L1.fits'\n", "l0file = 'KP.20250208.17485.59.fits'\n", "\n", "with fits.open(l1file) as hdul:\n", " datetime = hdul[0].header['DATE-BEG']\n", " timestamp, seconds = datetime.split('.')\n", "\n", "filetime = timestamp.replace(\":\",\"\").replace(\"-\",\"\")\n", "kpf_l2_filename = './docs/tutorials/KPFL2_'+filetime.replace(\"-:\",\"\")+'.fits'\n", "\n", "kpf_l2 = RV2.from_fits(l1file, l0file=l0file, instrument=\"KPF\")\n", "kpf_l2.to_fits(kpf_l2_filename)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Examining What's Inside the Community Standard L2 File\n", "\n", "Once you have created the community standard L2 fits file, you can read it into python either as a traditional FITS object using astropy's fits.open() function, or as a python object using the from_fits() function within RV2: \n", "\n", "`from astropy.io import fits`
\n", "`l2 = fits.open(kpf_l2_filename)`\n", "\n", "OR \n", "\n", "`from rvdata.core.models.level2 import RV2`
\n", "`l2_obj = RV2.from_fits(l2_standard)`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#In this example we'll use the astropy fits.open() approach\n", "\n", "l2 = fits.open(kpf_l2_filename)\n", "\n", "#The primary header contains useful info about what was observed, what's in each trace, etc. The keywords will be the same regardless\n", "#of what facility was used to take the data, so you can always query the file in the same way.\n", "primary_header = l2[0].header\n", "print('What telescope produced this observation? ',primary_header['TELESCOP'])\n", "print('What instrument was used? ',primary_header['INSTRUME'])\n", "print('What object was observed? ',primary_header['OBJECT'],)\n", "print('How many traces are produced by the spectrograph? ',primary_header['NUMTRACE'] )\n", "print('What is in each of the traces? ')\n", "for i in range(1,primary_header['NUMTRACE']+1):\n", " print('Trace',i,primary_header['TRACE'+str(i)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Extension Description table, located in the \"EXT_DESCRIPT\" extension, lists all of the FITS extensions in the L2 file" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "EXT_DESCRIPT = pd.DataFrame(l2['EXT_DESCRIPT'].data)\n", "EXT_DESCRIPT" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### And the Order Table, located in the \"ORDER_TABLE\" extension, details the start and end wavelengths of each echelle order" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ORDER_TABLE = pd.DataFrame(l2['ORDER_TABLE'].data)\n", "ORDER_TABLE" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting Examples of L2 Spectra\n", "\n", "From the Extension Description table, we know that the stellar data is contained in Traces 2, 3, and 4 -- so we can take a look at those" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#specify what order you'd like to plot\n", "order = 20\n", "\n", "#standardize the y-axis range based on the order with this highest flux measurements so it's easier to compare\n", "flux_max = np.max([np.max(l2['TRACE2_FLUX'].data[order]), np.max(l2['TRACE3_FLUX'].data[order]), np.max(l2['TRACE4_FLUX'].data[order])])\n", "ymax = flux_max*1.05\n", "\n", "#Calculate scaling factors between the blaze extensions and the flux extensions for easier visual comparison\n", "blz_scale2 = np.nanmax(l2['TRACE2_FLUX'].data[order]/np.nanmax(l2['TRACE2_BLAZE'].data[order]))\n", "blz_scale3 = np.nanmax(l2['TRACE3_FLUX'].data[order]/np.nanmax(l2['TRACE3_BLAZE'].data[order]))\n", "blz_scale4 = np.nanmax(l2['TRACE4_FLUX'].data[order]/np.nanmax(l2['TRACE4_BLAZE'].data[order]))\n", "\n", "fig = plt.figure(1,[10,8])\n", "\n", "ax1 = fig.add_subplot(3,1,1)\n", "ax1.scatter(l2['TRACE2_WAVE'].data[order],l2['TRACE2_FLUX'].data[order],marker='.',s=5)\n", "ax1.plot(l2['TRACE2_WAVE'].data[order],l2['TRACE2_BLAZE'].data[order]*blz_scale2,color='orange')\n", "ax1.set_ylim(0,ymax)\n", "ax1.set_ylabel('Counts',fontsize=15,fontweight='bold')\n", "\n", "ax2 = fig.add_subplot(3,1,2)\n", "ax2.scatter(l2['TRACE3_WAVE'].data[order],l2['TRACE3_FLUX'].data[order],marker='.',s=5)\n", "ax2.plot(l2['TRACE3_WAVE'].data[order],l2['TRACE3_BLAZE'].data[order]*blz_scale3,color='orange')\n", "ax2.set_ylim(0,ymax)\n", "ax2.set_ylabel('Counts',fontsize=15,fontweight='bold')\n", "\n", "ax3 = fig.add_subplot(3,1,3)\n", "ax3.scatter(l2['TRACE4_WAVE'].data[order],l2['TRACE4_FLUX'].data[order],marker='.',s=5)\n", "ax3.plot(l2['TRACE4_WAVE'].data[order],l2['TRACE4_BLAZE'].data[order]*blz_scale4,color='orange')\n", "ax3.set_ylim(0,ymax)\n", "ax3.set_ylabel('Counts',fontsize=15,fontweight='bold')\n", "ax3.set_xlabel('Wavelength (A)',fontsize=15,fontweight='bold')\n", "\n", "filename_pieces = kpf_l2_filename.split('/')\n", "\n", "fig.suptitle('Science Traces for Order '+str(order)+' of file '+filename_pieces[-1],fontsize=15,fontweight='bold')\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read in data from multiple instruments on the same star\n", "\n", "## Generate an EPRV Standard L2 file for NEID & KPF" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from rvdata.instruments.neid.level2 import RV2, NEIDRV2\n", "\n", "neid_L2_Filename = 'NEIDL2_20240921T052641.fits'\n", "\n", "neid_L2_obj = NEIDRV2.from_fits('neidL2_20240921T052641.fits', instrument=\"NEID\")\n", "neid_L2_obj.to_fits(neid_L2_Filename)\n", "\n", "kpf_l1file = 'KP.20240920.37616.85_L1.fits'\n", "kpf_l0file = 'KP.20240920.37616.85.fits'\n", "\n", "kpf_l2_filename = 'KPFL2_20240920T102656.fits'\n", "\n", "kpf_l2 = RV2.from_fits(l1file, l0file=l0file, instrument=\"KPF\")\n", "kpf_l2.to_fits(kpf_l2_filename)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plot an overlapping order, first with the blaze function and then after dividing it out" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "kpf_obs = fits.open('./docs/tutorials/KPFL2_20240920T102656.fits')\n", "neid_obs = fits.open('./docs/tutorials/NEIDL2_20240921T052641.fits')\n", "\n", "kpf_order = 19\n", "neid_order1 = 55\n", "\n", "fig = plt.figure(1,[12,6])\n", "ax1 = fig.add_subplot(1,1,1)\n", "ax1.plot(kpf_obs['TRACE2_WAVE'].data[kpf_order],kpf_obs['TRACE2_FLUX'].data[kpf_order],color='blue')\n", "ax1.plot(neid_obs['TRACE1_WAVE'].data[neid_order1],neid_obs['TRACE1_FLUX'].data[neid_order1],color='orange')\n", "ax1.set_ylabel('Counts',fontsize=15,fontweight='bold')\n", "ax1.set_xlabel('Wavelength (A)',fontsize=15,fontweight='bold')\n", "\n", "ax1.text(5145,90000,'KPF obs of HD 4268',color='blue',fontsize=20,fontweight='bold')\n", "ax1.text(5145,80000,'NEID obs of HD 4268',color='darkorange',fontsize=20,fontweight='bold')\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "kpf_obs = fits.open('./docs/tutorials/KPFL2_20240920T102656.fits')\n", "neid_obs = fits.open('./docs/tutorials/NEIDL2_20240921T052641.fits')\n", "\n", "kpf_order = 19\n", "neid_order = 55\n", "\n", "#Calculate scaling factors between the blaze extensions and the flux extensions for easier visual comparison\n", "blz_scale_kpf = np.nanmax(kpf_obs['TRACE2_FLUX'].data[kpf_order]/np.nanmax(kpf_obs['TRACE2_BLAZE'].data[kpf_order]))\n", "blz_scale_neid = np.nanmax(neid_obs['TRACE1_FLUX'].data[neid_order]/np.nanmax(neid_obs['TRACE1_BLAZE'].data[neid_order]))\n", "\n", "fig = plt.figure(1,[12,6])\n", "ax1 = fig.add_subplot(1,1,1)\n", "ax1.plot(neid_obs['TRACE1_WAVE'].data[neid_order],neid_obs['TRACE1_FLUX'].data[neid_order] / \n", " (neid_obs['TRACE1_BLAZE'].data[neid_order] * blz_scale_neid) ,color='orange')\n", "ax1.plot(kpf_obs['TRACE2_WAVE'].data[kpf_order],kpf_obs['TRACE2_FLUX'].data[kpf_order] / \n", " (kpf_obs['TRACE2_BLAZE'].data[kpf_order] * blz_scale_kpf) ,color='blue',alpha=.6)\n", "ax1.set_ylabel('Counts',fontsize=15,fontweight='bold')\n", "ax1.set_xlabel('Wavelength (A)',fontsize=15,fontweight='bold')\n", "ax1.set_ylim(0,1.3)\n", "\n", "ax1.text(5145,1.2,'KPF obs of HD 4268 - deblazed',color='blue',fontsize=16,fontweight='bold')\n", "ax1.text(5145,1.12,'NEID obs of HD 4268 - deblazed',color='darkorange',fontsize=16,fontweight='bold')\n", "\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.12.10" } }, "nbformat": 4, "nbformat_minor": 2 }