{"action":"created","comment":{"url":"https://api.github.com/repos/pypeit/PypeIt/pulls/comments/217880426","pull_request_review_id":155708621,"id":217880426,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNzg4MDQyNg==","diff_hunk":"@@ -3,209 +3,592 @@\n from __future__ import (print_function, absolute_import, division, unicode_literals)\n \n import glob\n-\n import numpy as np\n import scipy\n \n from pkg_resources import resource_filename\n \n from astropy import units\n+from astropy import constants\n from astropy import coordinates\n from astropy.table import Table, Column\n+from astropy.io import ascii\n from astropy.io import fits\n+from astropy.stats import sigma_clipped_stats\n+from matplotlib import pyplot as plt\n+\n+from matplotlib import pyplot as plt\n \n try:\n     from linetools.spectra.xspectrum1d import XSpectrum1D\n except ImportError:\n     pass\n \n+from pypeit.core import pydl\n from pypeit import msgs\n from pypeit import utils\n from pypeit import debugger\n+from pypeit.core import qa\n \n+TINY = 1e-15\n+MAGFUNC_MAX = 25.0\n+MAGFUNC_MIN = -25.0\n+SN2_MAX = (20.0) ** 2\n \n-def apply_sensfunc(spec_obj, sensfunc, airmass, exptime, extinction_data, MAX_EXTRAP=0.05):\n+def apply_sensfunc(spec_obj, sensfunc, airmass, exptime, \n+                   spectrograph, MAX_EXTRAP=0.05):\n     \"\"\" Apply the sensitivity function to the data\n     We also correct for extinction.\n \n     Parameters\n     ----------\n+    spec_obj : dict\n+      SpecObj\n+    sensfunc : dict\n+      Sens Function dict\n+    airmass : float\n+      Airmass\n+    exptime : float\n+      Exposure time in seconds\n+    spectrograph : dict\n+      Instrument specific dict\n+      Used for extinction correction\n     MAX_EXTRAP : float, optional [0.05]\n       Fractional amount to extrapolate sensitivity function\n     \"\"\"\n-    # Load extinction data\n-    #extinct = load_extinction_data(settings_spec)\n-    #airmass = fitsdict['airmass'][scidx]\n \n+    # ToDo Is MAX_EXTRAP necessary?\n     # Loop on extraction modes\n     for extract_type in ['boxcar', 'optimal']:\n         extract = getattr(spec_obj, extract_type)\n         if len(extract) == 0:\n             continue\n         msgs.info(\"Fluxing {:s} extraction for:\".format(extract_type) + msgs.newline() +\n                   \"{}\".format(spec_obj))\n-        wave = extract['wave']  # for convenience\n-        scale = np.zeros(wave.size)\n-        # Allow for some extrapolation\n-        dwv = sensfunc['wave_max']-sensfunc['wave_min']\n-        inds = ((wave >= sensfunc['wave_min']-dwv*MAX_EXTRAP)\n-            & (wave <= sensfunc['wave_max']+dwv*MAX_EXTRAP))\n-        mag_func = utils.func_val(sensfunc['c'], wave[inds],\n-                                    sensfunc['func'])\n-        sens = 10.0**(0.4*mag_func)\n-        # Extinction\n-        ext_corr = extinction_correction(wave[inds], airmass, extinction_data)\n-        scale[inds] = sens*ext_corr\n-        # Fill\n-        extract['flam'] = extract['counts']*scale/exptime\n-        extract['flam_var'] = (extract['var']*(scale/exptime)**2)\n-\n-'''\n-def apply_sensfunc(slf, det, scidx, fitsdict, MAX_EXTRAP=0.05, standard=False):\n-    \"\"\" Apply the sensitivity function to the data\n-    We also correct for extinction.\n-\n-    Parameters\n+        wave = np.copy(np.array(extract['WAVE']))\n+        magfit, _ = sensfunc['mag_set'].value(wave)\n+        sensfit = np.power(10.0, 0.4 * np.maximum(np.minimum(magfit, MAGFUNC_MAX), MAGFUNC_MIN))\n+        \n+        msgs.warn(\"Extinction correction applyed only if the spectra covers <10000Ang.\")\n+        # Apply Extinction if optical bands\n+        if np.max(wave) < 10000.:\n+            msgs.info(\"Applying extinction correction\")\n+            extinct = load_extinction_data(spectrograph.telescope['longitude'],\n+                                           spectrograph.telescope['latitude'])\n+            ext_corr = extinction_correction(wave* units.AA, airmass, extinct)\n+            sensfit = sensfit * ext_corr\n+        else:\n+            msgs.info(\"Extinction correction not applied\")\n+        extract['FLAM'] = extract['COUNTS'] * sensfit / exptime\n+        extract['FLAM_SIG'] = (sensfit / exptime) / (np.sqrt(extract['COUNTS_IVAR']))\n+        extract['FLAM_IVAR'] = extract['COUNTS_IVAR'] / (sensfit / exptime) **2 \n+\n+def generate_sensfunc(wave, counts, counts_ivar, airmass, exptime, spectrograph, telluric=False, star_type=None,\n+                      star_mag=None, RA=None, DEC=None, BALM_MASK_WID=5., nresln=None):\n+    \"\"\" Function to generate the sensitivity function.\n+    This can work in different regimes:\n+    - If telluric=False and RA=None and Dec=None\n+      the code creates a sintetic standard star spectrum using the Kurucz models,\n+      and from this it generates a sens func using nresln=20.0 and masking out\n+      telluric regions.\n+    - If telluric=False and RA and Dec are assigned\n+      the standard star spectrum is extracted from the archive, and a sens func \n+      is generated using nresln=20.0 and masking out telluric regions.\n+    - If telluric=True\n+      the code creates a sintetic standard star spectrum using the Kurucz models,\n+      the sens func is created setting nresln=1.5 it contains the correction for\n+      telluric lines.\n+\n+    Parameters:\n     ----------\n-    MAX_EXTRAP : float, optional [0.05]\n-      Fractional amount to extrapolate sensitivity function\n+    wave : array\n+      Wavelength of the star with units\n+    counts : array\n+      Flux (in counts) of the star\n+    counts_ivar : array\n+      Inverse variance of the star\n+    airmass : float\n+      Airmass\n+    exptime : float\n+      Exposure time in seconds\n+    spectrograph : dict\n+      Instrument specific dict\n+      Used for extinction correction\n+    telluric : bool\n+      if True performs a telluric correction\n+    star_type : str\n+      Spectral type of the telluric star (used if telluric=True)\n+    star_mag : float\n+      Apparent magnitude of telluric star (used if telluric=True)\n+    RA : float\n+      deg, RA of the telluric star\n+      if assigned, the standard star spectrum will be extracted from\n+      the archive\n+    DEC : float\n+      deg, DEC of the telluric star\n+      if assigned, the standard star spectrum will be extracted from\n+      the archive\n+    BALM_MASK_WID : float\n+      Mask parameter for Balmer absorption. A region equal to\n+      BALM_MASK_WID*resln is masked wher resln is the estimate\n+      for the spectral resolution.\n+    nresln : float\n+      Number of resolution elements for break-point placement.\n+      If assigned, overwrites the settings imposed by the code.\n+\n+    Returns:\n+    -------\n+    sens_dict : dict\n+      sensitivity function described by a dict\n     \"\"\"\n-    # Load extinction data\n-    extinct = load_extinction_data()\n-    airmass = fitsdict['airmass'][scidx]\n-    # Allow application to standard\n-    if standard:\n-        specobjs = slf._msstd[det-1]['spobjs']\n+\n+    # Create copy of the arrays to avoid modification and convert to \n+    # electrons / s\n+    wave_star = wave.copy()\n+    flux_star = counts.copy() / exptime\n+    ivar_star = counts_ivar.copy() * exptime ** 2\n+\n+    # ToDo\n+    # This should be changed. At the moment the extinction correction procedure\n+    # requires the spectra to be in the optical. For the NIR is probably enough\n+    # to extend the tables to longer wavelength setting the extinction to 0.0mag.\n+    msgs.warn(\"Extinction correction applyed only if the spectra covers <10000Ang.\")\n+    # Apply Extinction if optical bands\n+    if np.max(wave_star) < 10000. * units.AA:\n+        msgs.info(\"Applying extinction correction\")\n+        extinct = load_extinction_data(spectrograph.telescope['longitude'],\n+                                       spectrograph.telescope['latitude'])\n+        ext_corr = extinction_correction(wave_star, airmass, extinct)\n+        # Correct for extinction\n+        flux_star = flux_star * ext_corr\n+        ivar_star = ivar_star / ext_corr ** 2\n     else:\n-        specobjs = slf._specobjs[det-1]\n-    if specobjs is None:\n-        msgs.warn(\"No objects extracted.  Nothing to apply flux standard to.\")\n-        return\n-    # Loop on slits\n-    for sl in range(len(specobjs)):\n-        # Loop on objects\n-        for spobj in specobjs[sl]:\n-            # Loop on extraction modes\n-            for extract_type in ['boxcar', 'optimal']:\n-                extract = getattr(spobj, extract_type)\n-                if len(extract) == 0:\n-                    continue\n-#                try:\n-#                    extract = getattr(spobj, extract_type)\n-#                except AttributeError:\n-#                    continue\n-                msgs.info(\"Fluxing {:s} extraction for:\".format(extract_type) + msgs.newline() +\n-                          \"{}\".format(spobj))\n-                wave = extract['wave']  # for convenience\n-                scale = np.zeros(wave.size)\n-                # Allow for some extrapolation\n-                dwv = slf._sensfunc['wave_max']-slf._sensfunc['wave_min']\n-                inds = ((wave >= slf._sensfunc['wave_min']-dwv*MAX_EXTRAP)\n-                        & (wave <= slf._sensfunc['wave_max']+dwv*MAX_EXTRAP))\n-                mag_func = utils.func_val(slf._sensfunc['c'], wave[inds],\n-                                            slf._sensfunc['func'])\n-                sens = 10.0**(0.4*mag_func)\n-                # Extinction\n-                ext_corr = extinction_correction(wave[inds], airmass, extinct)\n-                scale[inds] = sens*ext_corr\n-                # Fill\n-                extract['flam'] = extract['counts']*scale/fitsdict['exptime'][scidx]\n-                extract['flam_var'] = (extract['var']*\n-                                       (scale/fitsdict['exptime'][scidx])**2)\n-'''\n-\n-def bspline_magfit(wave, flux, var, flux_std, bspline_par=None):\n+        msgs.info(\"Extinction correction not applied\")\n+\n+    # Create star model\n+    if (RA is not None) and (DEC is not None):\n+        # Pull star spectral model from archive\n+        msgs.info(\"Get standard model\")\n+        # Grab closest standard within a tolerance\n+        std_dict = find_standard_file((RA, DEC))\n+        # Load standard\n+        load_standard_file(std_dict)\n+        # Interpolate onto observed wavelengths\n+        std_xspec = XSpectrum1D.from_tuple((std_dict['wave'], std_dict['flux']))\n+        xspec = std_xspec.rebin(wave_star)  # Conserves flambda\n+        flux_true = xspec.flux.value\n+    else:\n+        # Create star spectral model\n+        msgs.info(\"Creating standard model\")\n+        # Create star model\n+        star_loglam, star_flux, std_dict = telluric_sed(star_mag, star_type)\n+        star_lam = 10 ** star_loglam\n+        # Generate a dict matching the output of find_standard_file\n+        std_dict = dict(file='KuruczTelluricModel', name=star_type, fmt=1,\n+                        ra=None, dec=None)\n+        std_dict['wave'] = star_lam * units.AA\n+        std_dict['flux'] = 1e17 * star_flux * units.erg / units.s / units.cm ** 2 / units.AA\n+        # ToDO If the Kuruck model is used, rebin create weird features\n+        # I using scipy interpolate to avoid this\n+        flux_true = scipy.interpolate.interp1d(std_dict['wave'], std_dict['flux'], fill_value='extrapolate')(wave_star)\n+    \n+    \"\"\"\n+    plt.figure(1)\n+    plt.plot(std_dict['wave'],std_dict['flux'],label='Orig')\n+    plt.scatter(wave_star,flux_true,s=5,c='red', label='rebin scipy')\n+    plt.plot(wave_star,flux_true2,label='rebin X')\n+\n+    plt.xlim(np.min(wave_star.value),np.max(wave_star.value))\n+    plt.ylim(np.min(flux_true),np.max(flux_true))\n+    plt.legend()\n+    plt.show()\n+    \"\"\"\n+    \n+    if np.min(flux_true) == 0.:\n+        msgs.warn('Your spectrum extends beyond calibrated standard star.')\n+\n+    # Set nresln\n+    if nresln is None:\n+        if telluric:\n+            nresln = 1.5\n+            msgs.info(\"Set nresln to 1.5\")\n+        else:\n+            nresln = 20.0\n+            msgs.info(\"Set nresln to 20.0\")\n+\n+    # ToDo\n+    # Compute an effective resolution for the standard. This could be improved\n+    # to setup an array of breakpoints based on the resolution. At the\n+    # moment we are using only one number\n+    msgs.work(\"Should pull resolution from arc line analysis\")\n+    msgs.work(\"At the moment the resolution is taken as the PixelScale\")\n+    msgs.work(\"This needs to be changed!\")\n+    std_pix = np.median(np.abs(wave_star - np.roll(wave_star, 1)))\n+    std_res = std_pix\n+    resln = std_res\n+    if (nresln * std_res) < std_pix:\n+        msgs.warn(\"Bspline breakpoints spacing shoud be larger than 1pixel\")\n+        msgs.warn(\"Changing input nresln to fix this\")\n+        nresln = std_res / std_pix\n+\n+    # Mask bad pixels, edges, and Balmer, Paschen, Brackett, and Pfund lines\n+    # Mask (True = good pixels)\n+    msgs.info(\"Masking spectral regions:\")\n+    msk_star = np.ones_like(flux_star).astype(bool)\n+\n+    # Mask bad pixels\n+    msgs.info(\" Masking bad pixels\")\n+    msk_star[ivar_star <= 0.] = False\n+    msk_star[flux_star <= 0.] = False\n+\n+    # Mask edges\n+    msgs.info(\" Masking edges\")\n+    msk_star[:1] = False\n+    msk_star[-1:] = False\n+\n+    # Mask Balmer\n+    msgs.info(\" Masking Balmer\")\n+    lines_balm = np.array([3836.4, 3969.6, 3890.1, 4102.8, 4102.8, 4341.6, 4862.7, 5407.0,\n+                           6564.6, 8224.8, 8239.2]) * units.AA\n+    for line_balm in lines_balm:\n+        ibalm = np.abs(wave_star - line_balm) <= BALM_MASK_WID * resln\n+        msk_star[ibalm] = False\n+\n+    # Mask Paschen\n+    msgs.info(\" Masking Paschen\")\n+    # air wavelengths from:\n+    # https://www.subarutelescope.org/Science/Resources/lines/hi.html\n+    lines_pasc = np.array([8203.6, 9229.0, 9546.0, 10049.4, 10938.1,\n+                           12818.1, 18751.0]) * units.AA\n+    for line_pasc in lines_pasc:\n+        ipasc = np.abs(wave_star - line_pasc) <= BALM_MASK_WID * resln\n+        msk_star[ipasc] = False\n+\n+    # Mask Brackett\n+    msgs.info(\" Masking Brackett\")\n+    # air wavelengths from:\n+    # https://www.subarutelescope.org/Science/Resources/lines/hi.html\n+    lines_brac = np.array([14584.0, 18174.0, 19446.0, 21655.0,\n+                           26252.0, 40512.0]) * units.AA\n+    for line_brac in lines_brac:\n+        ibrac = np.abs(wave_star - line_brac) <= BALM_MASK_WID * resln\n+        msk_star[ibrac] = False\n+\n+    # Mask Pfund\n+    msgs.info(\" Masking Pfund\")\n+    # air wavelengths from:\n+    # https://www.subarutelescope.org/Science/Resources/lines/hi.html\n+    lines_pfund = np.array([22788.0, 32961.0, 37395.0, 46525.0,\n+                            74578.0]) * units.AA\n+    for line_pfund in lines_pfund:\n+        ipfund = np.abs(wave_star - line_pfund) <= BALM_MASK_WID * resln\n+        msk_star[ipfund] = False\n+\n+    # Mask Atm. cutoff\n+    msgs.info(\" Masking Below the atmospheric cutoff\")\n+    atms_cutoff = wave_star <= 3000.0 * units.AA\n+    msk_star[atms_cutoff] = False\n+\n+    if telluric == False:\n+        # Mask telluric absorption\n+        msgs.info(\"Masking Telluric\")\n+        tell = np.any([((wave >= 7580.00 * units.AA) & (wave <= 7750.00 * units.AA)),\n+                       ((wave >= 7160.00 * units.AA) & (wave <= 7340.00 * units.AA)),\n+                       ((wave >= 6860.00 * units.AA) & (wave <= 6930.00 * units.AA)),\n+                       ((wave >= 9310.00 * units.AA) & (wave <= 9665.00 * units.AA)),\n+                       ((wave >= 11120.0 * units.AA) & (wave <= 11615.0 * units.AA)),\n+                       ((wave >= 12610.0 * units.AA) & (wave <= 12720.0 * units.AA)),\n+                       ((wave >= 13160.0 * units.AA) & (wave <= 15065.0 * units.AA)),\n+                       ((wave >= 15700.0 * units.AA) & (wave <= 15770.0 * units.AA)),\n+                       ((wave >= 16000.0 * units.AA) & (wave <= 16100.0 * units.AA)),\n+                       ((wave >= 16420.0 * units.AA) & (wave <= 16580.0 * units.AA)),\n+                       ((wave >= 17310.0 * units.AA) & (wave <= 20775.0 * units.AA)),\n+                       (wave >= 22680.0 * units.AA)], axis=0)\n+        msk_star[tell] = False\n+\n+    # Apply mask\n+    ivar_star[~msk_star] = 0.0\n+\n+    # Fit in magnitudes\n+    kwargs_bspline = {'bkspace': resln.value * nresln}\n+    kwargs_reject = {'maxrej': 5}\n+    mag_set = bspline_magfit(wave_star.value, flux_star, ivar_star, flux_true, inmask=msk_star,\n+                             kwargs_bspline=kwargs_bspline, kwargs_reject=kwargs_reject)\n+\n+    # Creating the dict\n+    msgs.work(\"Is min, max and wave_min, wave_max a duplicate?\")\n+    sens_dict = dict(bspline=mag_set, func='bspline', min=None, max=None, std=std_dict)\n+\n+    # Add in wavemin,wavemax\n+    sens_dict['wave_min'] = np.min(wave_star)\n+    sens_dict['wave_max'] = np.max(wave_star)\n+    sens_dict['wave'] = wave_star\n+    sens_dict['msk_star'] = msk_star\n+    sens_dict['mag_set'] = mag_set\n+\n+    \"\"\"\n+    # Write the sens_dict to a json file\n+    import json\n+    msgs.info(\"Writing sens_dict into .json file\")\n+    with open('sens_dict.json', 'w') as fp:\n+        json.dump(sens_dict, fp, sort_keys=True, indent=4)\n+    \"\"\"\n+\n+    return sens_dict\n+\n+\n+def bspline_magfit(wave, flux, ivar, flux_std, inmask=None, maxiter=35, upper=2, lower=2,\n+                   kwargs_bspline={}, kwargs_reject={}, debug=False):\n     \"\"\"\n     Perform a bspline fit to the flux ratio of standard to\n-    observed counts.  Used to generate a sensitivity function.\n+    observed counts. Used to generate a sensitivity function.\n \n     Parameters\n     ----------\n     wave : ndarray\n+      wavelength as observed\n     flux : ndarray\n       counts/s as observed\n-    var : ndarray\n-      variance\n+    ivar : ndarray\n+      inverse variance\n     flux_std : Quantity array\n       standard star true flux (erg/s/cm^2/A)\n-    bspline_par : dict, optional\n-      keywords for robust_polyfit\n+    inmask : ndarray\n+      bspline mask\n+    maxiter : integer\n+      maximum number of iterations for bspline_iterfit\n+    upper : integer\n+      number of sigma for rejection in bspline_iterfit\n+    lower : integer\n+      number of sigma for rejection in bspline_iterfit\n+    kwargs_bspline : dict, optional\n+      keywords for bspline_iterfit\n+    kwargs_reject : dict, optional\n+      keywords for bspline_iterfit\n+    debug : bool\n+      if True shows some dubugging plots\n \n     Returns\n     -------\n+    bset_log1\n     \"\"\"\n-    invvar = (var > 0.)/(np.max(var,0))\n-    invvar[np.where(var==0.0)] = 0.0\n-    nx = wave.size\n-    pos_error = 1./np.sqrt(np.maximum(invvar,0.) + (invvar == 0))\n-    pos_mask = (flux > pos_error/10.0) & (invvar > 0) & (flux_std > 0.0)\n-    #pos = pos_mask==1 npos)\n-\n-    fluxlog = 2.5*np.log10(np.maximum(flux,pos_error/10))\n-    logivar = invvar * flux**2 * pos_mask*1.08574\n-\n-    # cap the magfunc so that sensfunc < 1.0e10\n-    magfunc = 2.5*np.log10(np.maximum(flux_std,1.0e-2)) - fluxlog\n-    magfunc = np.minimum(magfunc,25.0)\n-    sensfunc = 10.0**(0.4*magfunc)*pos_mask\n-\n-    ''' Ported from LowRedux but this is a bad idea (and wasn't used there anyhow)\n-    # Interpolate over masked pixels\n-    if not nointerp:\n-        bad = logivar <= 0.\n-        if np.sum(bad) > 0:\n-            f = scipy.interpolate.InterpolatedUnivariateSpline(wave[~bad], magfunc[~bad], k=2)\n-            magfunc[bad] = f(wave[bad])\n-            fi = scipy.interpolate.InterpolatedUnivariateSpline(wave[~bad], logivar[~bad], k=2)\n-            logivar[bad] = fi(wave[bad])\n-    '''\n-\n-    #  First iteration\n-    mask, tck = utils.robust_polyfit(wave, magfunc, 3, function='bspline', weights=np.sqrt(logivar),\n-                                       bspline_par=bspline_par)\n-    logfit1 = utils.func_val(tck,wave,'bspline')\n-    modelfit1 = 10.0**(0.4*(logfit1))\n-\n-    residual = sensfunc/(modelfit1 + (modelfit1 == 0)) - 1.\n-    new_mask = pos_mask & (sensfunc > 0)\n-    residual_ivar = (modelfit1*flux/(sensfunc + (sensfunc == 0.0)))**2*invvar\n-    residual_ivar = residual_ivar*new_mask\n-\n-    ''' Ported from LowRedux but this is a bad idea (and wasn't used there anyhow)\n-    # Interpolate over masked pixels\n-    if not nointerp:\n-        if np.sum(bad) > 0:\n-            f = scipy.interpolate.InterpolatedUnivariateSpline(wave[~bad], residual[~bad], k=2)\n-            residual[bad] = f(wave[bad])\n-            fi = scipy.interpolate.InterpolatedUnivariateSpline(wave[~bad], residual_ivar[~bad], k=2)\n-            residual_ivar[bad] = fi(wave[bad])\n-    '''\n-\n-    #  Now do one more fit to the ratio of data/model - 1.\n-    # Fuss with the knots first ()\n-    inner_knots = utils.bspline_inner_knots(tck[0])\n-    #\n-    if bspline_par is None:\n-        bspline_par = {}\n-    bspline_par['knots'] = inner_knots # This over-rides everyn\n-    mask, tck_residual = utils.robust_polyfit(wave, residual, 3, function='bspline',\n-                                                weights=np.sqrt(residual_ivar), bspline_par=bspline_par) #**kwargs)\n-    if tck_residual[1].size != tck[1].size:\n-        msgs.error('Problem with bspline knots in bspline_magfit')\n-    #bset_residual = bspline_iterfit(wave, residual, weights=np.sqrt(residual_ivar), knots = tck[0])\n-\n-    tck_log1 = list(tck)\n-    tck_log1[1] = tck[1] + tck_residual[1]\n-\n-    sensfit = 10.0**(0.4*(utils.func_val(tck_log1,wave, 'bspline')))\n-\n-    absdev = np.median(np.abs(sensfit/modelfit1-1))\n+    # Create copy of the arrays to avoid modification\n+    wave_obs = wave.copy()\n+    flux_obs = flux.copy()\n+    ivar_obs = ivar.copy()\n+\n+    # preparing arrays to run in bspline_iterfit\n+\n+    if np.all(~np.isfinite(ivar_obs)):\n+        msgs.warn(\"NaN are present in the inverse variance\")\n+\n+    # Preparing arrays to run in bspline_iterfit\n+\n+    if np.all(~np.isfinite(ivar_obs)):\n+        msgs.warn(\"NaN are present in the inverse variance\")\n+\n+    # Removing outliers\n+\n+    # Calculate log of flux_obs setting a floor at TINY\n+    logflux_obs = 2.5 * np.log10(np.maximum(flux_obs, TINY))\n+    # Set a fix value fro the variance of logflux\n+    logivar_obs = np.ones_like(logflux_obs) * (10.0 ** 2)\n+\n+    # Calculate log of flux_std model setting a floor at TINY\n+    logflux_std = 2.5 * np.log10(np.maximum(flux_std, TINY))\n+\n+    # Calculate ratio setting a floor at MAGFUNC_MIN and a ceiling at\n+    # MAGFUNC_MAX\n+    magfunc = logflux_std - logflux_obs\n+    magfunc = np.maximum(np.minimum(magfunc, MAGFUNC_MAX), MAGFUNC_MIN)\n+    magfunc_mask = (magfunc < 0.99 * MAGFUNC_MAX) & (magfunc > 0.99 * MAGFUNC_MIN)\n+\n+    # Mask outliners\n+    if inmask is None:\n+        masktot = (ivar_obs > 0.0) & np.isfinite(logflux_obs) & np.isfinite(ivar_obs) & \\\n+                  np.isfinite(logflux_std) & magfunc_mask\n+    else:\n+        masktot = inmask & (ivar_obs > 0.0) & np.isfinite(logflux_obs) & np.isfinite(ivar_obs) & \\\n+                  np.isfinite(logflux_std) & magfunc_mask\n+    logivar_obs[~masktot] = 0.\n+\n+\n+    # Calculate sensfunc\n+    sensfunc = 10.0 ** (0.4 * magfunc)\n+\n+    msgs.info(\"Initialize bspline for flux calibration\")\n+\n+    init_bspline = pydl.bspline(wave_obs, bkspace=kwargs_bspline['bkspace'])\n+    fullbkpt = init_bspline.breakpoints\n+\n+    # TESTING turning off masking for now\n+    # remove masked regions from breakpoints\n+    msk_obs = np.ones_like(wave_obs).astype(bool)\n+    msk_obs[~masktot] = False\n+    msk_bkpt = scipy.interpolate.interp1d(wave_obs, msk_obs, kind='nearest', fill_value='extrapolate')(fullbkpt)\n+    init_breakpoints = fullbkpt[msk_bkpt > 0.999]\n+\n+    # init_breakpoints = fullbkpt\n+\n+    #  First round of the fit:\n+    msgs.info(\"Bspline fit: step 1\")\n+    bset1, bmask = pydl.iterfit(wave_obs, magfunc, invvar=logivar_obs, inmask=masktot, upper=upper, lower=lower,\n+                                fullbkpt=init_breakpoints, maxiter=maxiter, kwargs_bspline=kwargs_bspline,\n+                                kwargs_reject=kwargs_reject)\n+    logfit1, _ = bset1.value(wave_obs)\n+    logfit_bkpt, _ = bset1.value(init_breakpoints)\n+\n+    if debug:\n+        # Check for calibration\n+        plt.figure(1)\n+        plt.plot(wave_obs, magfunc, drawstyle='steps-mid', color='black', label='magfunc')\n+        plt.plot(wave_obs, logfit1, color='cornflowerblue', label='logfit1')\n+        plt.plot(wave_obs[~masktot], magfunc[~masktot], '+', color='red', markersize=5.0, label='masked magfunc')\n+        plt.plot(wave_obs[~masktot], logfit1[~masktot], '+', color='red', markersize=5.0, label='masked logfit1')\n+        plt.plot(init_breakpoints, logfit_bkpt, '.', color='green', markersize=4.0, label='breakpoints')\n+        plt.plot(init_breakpoints, np.interp(init_breakpoints, wave_obs, magfunc), '.', color='green', markersize=4.0,\n+                 label='breakpoints')\n+        plt.plot(wave_obs, 1.0 / np.sqrt(logivar_obs), color='orange', label='sigma')\n+        plt.legend()\n+        plt.xlabel('Wavelength [ang]')\n+        plt.ylim(0.0, 1.2 * MAGFUNC_MAX)\n+        plt.title('1st Bspline fit')\n+        plt.show()\n+\n+    modelfit1 = np.power(10.0, 0.4 * np.maximum(np.minimum(logfit1, MAGFUNC_MAX), MAGFUNC_MIN))\n+    residual = sensfunc / (modelfit1 + (modelfit1 == 0)) - 1.\n+    # new_mask = masktot & (sensfunc > 0)\n+\n+    # residual_ivar = (modelfit1 * flux_obs / (sensfunc + (sensfunc == 0.0))) ** 2 * ivar_obs\n+    residual_ivar = np.ones_like(residual) / (0.1 ** 2)\n+    residual_ivar = residual_ivar * masktot\n+\n+    (mean, med, stddev) = sigma_clipped_stats(residual[masktot], sigma_lower=3.0, sigma_upper=3.0)\n+\n+    if np.median(stddev > 0.01):\n+        #  Second round of the fit:\n+        msgs.info(\"Bspline fit: step 2\")\n+        #  Now do one more fit to the ratio of data/model - 1.\n+        bset_residual, bmask2 = pydl.iterfit(wave_obs, residual, invvar=residual_ivar, inmask=masktot, upper=upper,\n+                                             lower=lower, maxiter=maxiter, fullbkpt=bset1.breakpoints,\n+                                             kwargs_bspline=kwargs_bspline, kwargs_reject=kwargs_reject)\n+        bset_log1 = bset1.copy()\n+        bset_log1.coeff = bset_log1.coeff + bset_residual.coeff\n+        if debug:\n+            # Check for calibration\n+            resid_fit, _ = bset_residual.value(wave_obs)\n+            logfit2, _ = bset_log1.value(wave_obs)\n+            logfit2_bkpt, _ = bset_log1.value(bset1.breakpoints)\n+            plt.figure(1)\n+            plt.plot(wave_obs, residual, drawstyle='steps-mid', color='black', label='residual')\n+            plt.plot(wave_obs, resid_fit, color='cornflowerblue', label='resid_fit')\n+            plt.plot(wave_obs[~masktot], residual[~masktot], '+', color='red', markersize=5.0, label='masked residual')\n+            plt.plot(wave_obs[~masktot], resid_fit[~masktot], '+', color='red', markersize=5.0, label='masked resid_fit')\n+            plt.plot(init_breakpoints, logfit2_bkpt, '.', color='green', markersize=4.0, label='breakpoints')\n+            plt.plot(wave_obs, 1.0 / np.sqrt(residual_ivar), color='orange', label='sigma')\n+            plt.legend()\n+            plt.xlabel('Wavelength [ang]')\n+            plt.ylim(-0.1, 0.1)\n+            plt.title('2nd Bspline fit')\n+            plt.show()\n+    else:\n+        bset_log1 = bset1.copy()\n+\n+    # Create sensitivity function\n+    newlogfit, _ = bset_log1.value(wave_obs)\n+    sensfit = np.power(10.0, 0.4 * np.maximum(np.minimum(newlogfit, MAGFUNC_MAX), MAGFUNC_MIN))\n+\n+    sensfit[~magfunc_mask] = 0.0\n+\n+    if debug:\n+\n+        # Check for calibration\n+        plt.figure(1)\n+        plt.plot(wave_obs, sensfunc, drawstyle='steps-mid', color='black', label='sensfunc')\n+        plt.plot(wave_obs, sensfit, color='cornflowerblue', label='sensfunc fit')\n+        plt.plot(wave_obs[~masktot], sensfunc[~masktot], '+', color='red', markersize=5.0, label='masked sensfunc')\n+        plt.plot(wave_obs[~masktot], sensfit[~masktot], '+', color='red', markersize=5.0, label='masked sensfuncfit')\n+        plt.legend()\n+        plt.xlabel('Wavelength [ang]')\n+        plt.ylim(0.0, 100.0)\n+        plt.show()\n+\n+    # Check quality of the fit\n+    absdev = np.median(np.abs(sensfit / modelfit1 - 1))\n     msgs.info('Difference between fits is {:g}'.format(absdev))\n \n+    # Check for residual of the fit\n+    if debug:\n+\n+        # scale = np.power(10.0, 0.4 * sensfit)\n+        flux_cal = flux_obs * sensfit\n+        ivar_cal = ivar_obs / sensfit ** 2.\n+\n+        plt.rcdefaults()\n+        plt.rcParams['font.family']= 'times new roman'\n+        plt.figure(figsize=(11, 8.5))\n+        plt.clf()\n+        plt.plot(wave_obs,flux_cal, label='Calibrated Spectrum')\n+        plt.plot(wave_obs,flux_std, label='Model')\n+        plt.plot(wave_obs,np.sqrt(1/ivar_cal))\n+        plt.legend()\n+        plt.xlabel('Wavelength [ang]')\n+        plt.ylabel('Flux [erg/s/cm2/Ang.]')\n+        plt.ylim(0,np.median(flux_std)*2.5)\n+        plt.show()\n+        plt.close()\n+\n+\n+\n+\n     # QA\n     msgs.work(\"Add QA for sensitivity function\")\n+    qa_bspline_magfit(wave_obs, bset_log1, magfunc, masktot)\n+    \n+    \n+    \"\"\"\n+    bspline_magfit_new_qa(wave_obs, magfunc, logfit1,\n+                          newlogfit, bset1.breakpoints,\n+                          outfile=None, title=None)\n+    \"\"\"\n \n-    return tck_log1\n+    return bset_log1","path":"pypeit/core/flux.py","position":698,"original_position":698,"commit_id":"fb954a6d0943854bc9dbed069557a95302206615","original_commit_id":"fb954a6d0943854bc9dbed069557a95302206615","user":{"login":"jhennawi","id":8774563,"node_id":"MDQ6VXNlcjg3NzQ1NjM=","avatar_url":"https://avatars2.githubusercontent.com/u/8774563?v=4","gravatar_id":"","url":"https://api.github.com/users/jhennawi","html_url":"https://github.com/jhennawi","followers_url":"https://api.github.com/users/jhennawi/followers","following_url":"https://api.github.com/users/jhennawi/following{/other_user}","gists_url":"https://api.github.com/users/jhennawi/gists{/gist_id}","starred_url":"https://api.github.com/users/jhennawi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jhennawi/subscriptions","organizations_url":"https://api.github.com/users/jhennawi/orgs","repos_url":"https://api.github.com/users/jhennawi/repos","events_url":"https://api.github.com/users/jhennawi/events{/privacy}","received_events_url":"https://api.github.com/users/jhennawi/received_events","type":"User","site_admin":false},"body":"We don't understand how to write out QA in PypeIt. Maybe you can help us with this. ","created_at":"2018-09-15T09:12:55Z","updated_at":"2018-09-15T09:12:55Z","html_url":"https://github.com/pypeit/PypeIt/pull/495#discussion_r217880426","pull_request_url":"https://api.github.com/repos/pypeit/PypeIt/pulls/495","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/pypeit/PypeIt/pulls/comments/217880426"},"html":{"href":"https://github.com/pypeit/PypeIt/pull/495#discussion_r217880426"},"pull_request":{"href":"https://api.github.com/repos/pypeit/PypeIt/pulls/495"}},"in_reply_to_id":217879353},"pull_request":{"url":"https://api.github.com/repos/pypeit/PypeIt/pulls/495","id":215483978,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE1NDgzOTc4","html_url":"https://github.com/pypeit/PypeIt/pull/495","diff_url":"https://github.com/pypeit/PypeIt/pull/495.diff","patch_url":"https://github.com/pypeit/PypeIt/pull/495.patch","issue_url":"https://api.github.com/repos/pypeit/PypeIt/issues/495","number":495,"state":"open","locked":false,"title":"Peak finding improvements to get NIRES running","user":{"login":"jhennawi","id":8774563,"node_id":"MDQ6VXNlcjg3NzQ1NjM=","avatar_url":"https://avatars2.githubusercontent.com/u/8774563?v=4","gravatar_id":"","url":"https://api.github.com/users/jhennawi","html_url":"https://github.com/jhennawi","followers_url":"https://api.github.com/users/jhennawi/followers","following_url":"https://api.github.com/users/jhennawi/following{/other_user}","gists_url":"https://api.github.com/users/jhennawi/gists{/gist_id}","starred_url":"https://api.github.com/users/jhennawi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jhennawi/subscriptions","organizations_url":"https://api.github.com/users/jhennawi/orgs","repos_url":"https://api.github.com/users/jhennawi/repos","events_url":"https://api.github.com/users/jhennawi/events{/privacy}","received_events_url":"https://api.github.com/users/jhennawi/received_events","type":"User","site_admin":false},"body":"This PR attempts to address some issues associated with getting NIRES to work.\r\n\r\n1) The first is that arclines cannot find enough lines to solve its bluest order because of shortcomings in the detect_lines algorithm. This has now been replaced by a much better peak finding algorithm. \r\n\r\n2) The second is that arclines was not able to find line lists outside of the NIST directory anymore. This has been fixed. \r\n\r\n3) We have revamped the fluxing algorithm to deal with telluric corrections, and generally tuned things up. \r\n\r\nThe test suite wavelength solutions are unchanged. However, this PR influences the behavior of arclines because we now find more lines (as we should be). \r\n\r\nThe test in the dev suite run fine. If anything the wavelength fits appear to be working as good or better. That said, arc lines is now doing very poorly on the LRIS_red multi_1200_9000_d680 setup. I will file a separate issue on that. \r\n","created_at":"2018-09-14T05:19:19Z","updated_at":"2018-09-15T09:12:55Z","closed_at":null,"merged_at":null,"merge_commit_sha":"68a651ffc10552346483a34160e389845955614d","assignee":null,"assignees":[],"requested_reviewers":[{"login":"rcooke-ast","id":12585758,"node_id":"MDQ6VXNlcjEyNTg1NzU4","avatar_url":"https://avatars2.githubusercontent.com/u/12585758?v=4","gravatar_id":"","url":"https://api.github.com/users/rcooke-ast","html_url":"https://github.com/rcooke-ast","followers_url":"https://api.github.com/users/rcooke-ast/followers","following_url":"https://api.github.com/users/rcooke-ast/following{/other_user}","gists_url":"https://api.github.com/users/rcooke-ast/gists{/gist_id}","starred_url":"https://api.github.com/users/rcooke-ast/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rcooke-ast/subscriptions","organizations_url":"https://api.github.com/users/rcooke-ast/orgs","repos_url":"https://api.github.com/users/rcooke-ast/repos","events_url":"https://api.github.com/users/rcooke-ast/events{/privacy}","received_events_url":"https://api.github.com/users/rcooke-ast/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/pypeit/PypeIt/pulls/495/commits","review_comments_url":"https://api.github.com/repos/pypeit/PypeIt/pulls/495/comments","review_comment_url":"https://api.github.com/repos/pypeit/PypeIt/pulls/comments{/number}","comments_url":"https://api.github.com/repos/pypeit/PypeIt/issues/495/comments","statuses_url":"https://api.github.com/repos/pypeit/PypeIt/statuses/fb954a6d0943854bc9dbed069557a95302206615","head":{"label":"pypeit:nires_feige","ref":"nires_feige","sha":"fb954a6d0943854bc9dbed069557a95302206615","user":{"login":"pypeit","id":41153172,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQxMTUzMTcy","avatar_url":"https://avatars1.githubusercontent.com/u/41153172?v=4","gravatar_id":"","url":"https://api.github.com/users/pypeit","html_url":"https://github.com/pypeit","followers_url":"https://api.github.com/users/pypeit/followers","following_url":"https://api.github.com/users/pypeit/following{/other_user}","gists_url":"https://api.github.com/users/pypeit/gists{/gist_id}","starred_url":"https://api.github.com/users/pypeit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pypeit/subscriptions","organizations_url":"https://api.github.com/users/pypeit/orgs","repos_url":"https://api.github.com/users/pypeit/repos","events_url":"https://api.github.com/users/pypeit/events{/privacy}","received_events_url":"https://api.github.com/users/pypeit/received_events","type":"Organization","site_admin":false},"repo":{"id":36958428,"node_id":"MDEwOlJlcG9zaXRvcnkzNjk1ODQyOA==","name":"PypeIt","full_name":"pypeit/PypeIt","owner":{"login":"pypeit","id":41153172,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQxMTUzMTcy","avatar_url":"https://avatars1.githubusercontent.com/u/41153172?v=4","gravatar_id":"","url":"https://api.github.com/users/pypeit","html_url":"https://github.com/pypeit","followers_url":"https://api.github.com/users/pypeit/followers","following_url":"https://api.github.com/users/pypeit/following{/other_user}","gists_url":"https://api.github.com/users/pypeit/gists{/gist_id}","starred_url":"https://api.github.com/users/pypeit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pypeit/subscriptions","organizations_url":"https://api.github.com/users/pypeit/orgs","repos_url":"https://api.github.com/users/pypeit/repos","events_url":"https://api.github.com/users/pypeit/events{/privacy}","received_events_url":"https://api.github.com/users/pypeit/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/pypeit/PypeIt","description":"The Python Spectroscopic Data Reduction Pipeline","fork":false,"url":"https://api.github.com/repos/pypeit/PypeIt","forks_url":"https://api.github.com/repos/pypeit/PypeIt/forks","keys_url":"https://api.github.com/repos/pypeit/PypeIt/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pypeit/PypeIt/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pypeit/PypeIt/teams","hooks_url":"https://api.github.com/repos/pypeit/PypeIt/hooks","issue_events_url":"https://api.github.com/repos/pypeit/PypeIt/issues/events{/number}","events_url":"https://api.github.com/repos/pypeit/PypeIt/events","assignees_url":"https://api.github.com/repos/pypeit/PypeIt/assignees{/user}","branches_url":"https://api.github.com/repos/pypeit/PypeIt/branches{/branch}","tags_url":"https://api.github.com/repos/pypeit/PypeIt/tags","blobs_url":"https://api.github.com/repos/pypeit/PypeIt/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pypeit/PypeIt/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pypeit/PypeIt/git/refs{/sha}","trees_url":"https://api.github.com/repos/pypeit/PypeIt/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pypeit/PypeIt/statuses/{sha}","languages_url":"https://api.github.com/repos/pypeit/PypeIt/languages","stargazers_url":"https://api.github.com/repos/pypeit/PypeIt/stargazers","contributors_url":"https://api.github.com/repos/pypeit/PypeIt/contributors","subscribers_url":"https://api.github.com/repos/pypeit/PypeIt/subscribers","subscription_url":"https://api.github.com/repos/pypeit/PypeIt/subscription","commits_url":"https://api.github.com/repos/pypeit/PypeIt/commits{/sha}","git_commits_url":"https://api.github.com/repos/pypeit/PypeIt/git/commits{/sha}","comments_url":"https://api.github.com/repos/pypeit/PypeIt/comments{/number}","issue_comment_url":"https://api.github.com/repos/pypeit/PypeIt/issues/comments{/number}","contents_url":"https://api.github.com/repos/pypeit/PypeIt/contents/{+path}","compare_url":"https://api.github.com/repos/pypeit/PypeIt/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pypeit/PypeIt/merges","archive_url":"https://api.github.com/repos/pypeit/PypeIt/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pypeit/PypeIt/downloads","issues_url":"https://api.github.com/repos/pypeit/PypeIt/issues{/number}","pulls_url":"https://api.github.com/repos/pypeit/PypeIt/pulls{/number}","milestones_url":"https://api.github.com/repos/pypeit/PypeIt/milestones{/number}","notifications_url":"https://api.github.com/repos/pypeit/PypeIt/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pypeit/PypeIt/labels{/name}","releases_url":"https://api.github.com/repos/pypeit/PypeIt/releases{/id}","deployments_url":"https://api.github.com/repos/pypeit/PypeIt/deployments","created_at":"2015-06-05T22:25:37Z","updated_at":"2018-09-15T08:14:23Z","pushed_at":"2018-09-15T08:14:25Z","git_url":"git://github.com/pypeit/PypeIt.git","ssh_url":"git@github.com:pypeit/PypeIt.git","clone_url":"https://github.com/pypeit/PypeIt.git","svn_url":"https://github.com/pypeit/PypeIt","homepage":null,"size":33543,"stargazers_count":16,"watchers_count":16,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"open_issues_count":91,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":12,"open_issues":91,"watchers":16,"default_branch":"master"}},"base":{"label":"pypeit:master","ref":"master","sha":"204bb64bd7b16255115a3025b761f12f6b56d62a","user":{"login":"pypeit","id":41153172,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQxMTUzMTcy","avatar_url":"https://avatars1.githubusercontent.com/u/41153172?v=4","gravatar_id":"","url":"https://api.github.com/users/pypeit","html_url":"https://github.com/pypeit","followers_url":"https://api.github.com/users/pypeit/followers","following_url":"https://api.github.com/users/pypeit/following{/other_user}","gists_url":"https://api.github.com/users/pypeit/gists{/gist_id}","starred_url":"https://api.github.com/users/pypeit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pypeit/subscriptions","organizations_url":"https://api.github.com/users/pypeit/orgs","repos_url":"https://api.github.com/users/pypeit/repos","events_url":"https://api.github.com/users/pypeit/events{/privacy}","received_events_url":"https://api.github.com/users/pypeit/received_events","type":"Organization","site_admin":false},"repo":{"id":36958428,"node_id":"MDEwOlJlcG9zaXRvcnkzNjk1ODQyOA==","name":"PypeIt","full_name":"pypeit/PypeIt","owner":{"login":"pypeit","id":41153172,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQxMTUzMTcy","avatar_url":"https://avatars1.githubusercontent.com/u/41153172?v=4","gravatar_id":"","url":"https://api.github.com/users/pypeit","html_url":"https://github.com/pypeit","followers_url":"https://api.github.com/users/pypeit/followers","following_url":"https://api.github.com/users/pypeit/following{/other_user}","gists_url":"https://api.github.com/users/pypeit/gists{/gist_id}","starred_url":"https://api.github.com/users/pypeit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pypeit/subscriptions","organizations_url":"https://api.github.com/users/pypeit/orgs","repos_url":"https://api.github.com/users/pypeit/repos","events_url":"https://api.github.com/users/pypeit/events{/privacy}","received_events_url":"https://api.github.com/users/pypeit/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/pypeit/PypeIt","description":"The Python Spectroscopic Data Reduction Pipeline","fork":false,"url":"https://api.github.com/repos/pypeit/PypeIt","forks_url":"https://api.github.com/repos/pypeit/PypeIt/forks","keys_url":"https://api.github.com/repos/pypeit/PypeIt/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pypeit/PypeIt/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pypeit/PypeIt/teams","hooks_url":"https://api.github.com/repos/pypeit/PypeIt/hooks","issue_events_url":"https://api.github.com/repos/pypeit/PypeIt/issues/events{/number}","events_url":"https://api.github.com/repos/pypeit/PypeIt/events","assignees_url":"https://api.github.com/repos/pypeit/PypeIt/assignees{/user}","branches_url":"https://api.github.com/repos/pypeit/PypeIt/branches{/branch}","tags_url":"https://api.github.com/repos/pypeit/PypeIt/tags","blobs_url":"https://api.github.com/repos/pypeit/PypeIt/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pypeit/PypeIt/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pypeit/PypeIt/git/refs{/sha}","trees_url":"https://api.github.com/repos/pypeit/PypeIt/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pypeit/PypeIt/statuses/{sha}","languages_url":"https://api.github.com/repos/pypeit/PypeIt/languages","stargazers_url":"https://api.github.com/repos/pypeit/PypeIt/stargazers","contributors_url":"https://api.github.com/repos/pypeit/PypeIt/contributors","subscribers_url":"https://api.github.com/repos/pypeit/PypeIt/subscribers","subscription_url":"https://api.github.com/repos/pypeit/PypeIt/subscription","commits_url":"https://api.github.com/repos/pypeit/PypeIt/commits{/sha}","git_commits_url":"https://api.github.com/repos/pypeit/PypeIt/git/commits{/sha}","comments_url":"https://api.github.com/repos/pypeit/PypeIt/comments{/number}","issue_comment_url":"https://api.github.com/repos/pypeit/PypeIt/issues/comments{/number}","contents_url":"https://api.github.com/repos/pypeit/PypeIt/contents/{+path}","compare_url":"https://api.github.com/repos/pypeit/PypeIt/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pypeit/PypeIt/merges","archive_url":"https://api.github.com/repos/pypeit/PypeIt/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pypeit/PypeIt/downloads","issues_url":"https://api.github.com/repos/pypeit/PypeIt/issues{/number}","pulls_url":"https://api.github.com/repos/pypeit/PypeIt/pulls{/number}","milestones_url":"https://api.github.com/repos/pypeit/PypeIt/milestones{/number}","notifications_url":"https://api.github.com/repos/pypeit/PypeIt/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pypeit/PypeIt/labels{/name}","releases_url":"https://api.github.com/repos/pypeit/PypeIt/releases{/id}","deployments_url":"https://api.github.com/repos/pypeit/PypeIt/deployments","created_at":"2015-06-05T22:25:37Z","updated_at":"2018-09-15T08:14:23Z","pushed_at":"2018-09-15T08:14:25Z","git_url":"git://github.com/pypeit/PypeIt.git","ssh_url":"git@github.com:pypeit/PypeIt.git","clone_url":"https://github.com/pypeit/PypeIt.git","svn_url":"https://github.com/pypeit/PypeIt","homepage":null,"size":33543,"stargazers_count":16,"watchers_count":16,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":12,"mirror_url":null,"archived":false,"open_issues_count":91,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":12,"open_issues":91,"watchers":16,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/pypeit/PypeIt/pulls/495"},"html":{"href":"https://github.com/pypeit/PypeIt/pull/495"},"issue":{"href":"https://api.github.com/repos/pypeit/PypeIt/issues/495"},"comments":{"href":"https://api.github.com/repos/pypeit/PypeIt/issues/495/comments"},"review_comments":{"href":"https://api.github.com/repos/pypeit/PypeIt/pulls/495/comments"},"review_comment":{"href":"https://api.github.com/repos/pypeit/PypeIt/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/pypeit/PypeIt/pulls/495/commits"},"statuses":{"href":"https://api.github.com/repos/pypeit/PypeIt/statuses/fb954a6d0943854bc9dbed069557a95302206615"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"jhennawi"}}