How can we make the output of some commands more readable?

One of the selling points of the IPython notebooks is the ability to take advantage of the browser/HTML for displaying output.

We can apply this to various parts of the CIAO-provided modules. In this case I'm going to restrict myself to a couple of Sherpa objects, but there are ChIPS and Crates object classes that could also use this.

I am taking a "simple" approach to this, which makes the conversion explicit rather than nice and implicit/seamless. This is in part because of restrictions in the way many Sherpa classes are constructed (the use of the NoNewAttributesAfterInit base class means that I can not "monkey patch" the classes to add in _to_html methods), but is also easier for experimentation.

In [1]:
from sherpa.astro.ui import *
from pycrates import *
from pychips import *

# hide the logging messages from Sherpa
import logging
logging.getLogger('sherpa').propagate = False

To avoid ChIPS windows being created - i.e. X11 windows - we turn off the display setting.

In [2]:
set_preference('window.display', 'false')

In this notebook directory I have placed a few files; for instance a source spectrum, along with the response files (ARF and RMF), but no background file.

In [3]:
ls src*
src.arf  src.pi   src.rmf

Create some objects to display

I am going to concentrate on model values here:

In [4]:
load_pha('src.pi')
set_source(xsapec.src * xsphabs.gal)
gal.nh = 1.0
group_counts(20)
notice(0.5,7)
read ARF file src.arf
read RMF file src.rmf

What could we display?

The obvious candidates include parameters and model expressions:

In [5]:
print("*** gal.nh ***")
print(gal.nh)
print("*** model expression ***")
print(get_source())
*** gal.nh ***
val         = 1.0
min         = 0.0
max         = 100000.0
units       = 10^22 atoms / cm^2
frozen      = False
link        = None
default_val = 1.0
default_min = 0.0
default_max = 100000.0
*** model expression ***
(xsapec.src * xsphabs.gal)
   Param        Type          Value          Min          Max      Units
   -----        ----          -----          ---          ---      -----
   src.kT       thawed            1        0.008           64        keV
   src.Abundanc frozen            1            0            5           
   src.redshift frozen            0            0           10           
   src.norm     thawed            1            0        1e+24           
   gal.nH       thawed            1            0       100000 10^22 atoms / cm^2

I now create several routines that can be used to view a source expression as an HTML table.

In [6]:
from IPython.core.display import HTML

# Note that this code needs to be extended to support properly displaying linked parameters.
def par_to_html_raw(par):
    """Convert a parameter file to a HTML table"""
    def th(n): return "<th>{}</th>".format(n)
    def td(n): return "<td>{}</td>".format(getattr(par, n))
    out = "<table><tr>" + "".join([th(n) for n in ["Param","Frozen","Value","Min","Max","Units"]]) + "</tr>"
    out += "<tr>"
    for n in ["fullname", "frozen", "val", "min", "max", "units"]:
        out += td(n)
    out += "</tr></table>"
    return out

def par_to_html(par):
    """Create a HTML view of the parameter, displayed by IPython notebook"""
    return HTML(par_to_html_raw(par))

The following may cause an "Untrusted text/html output ignored." message to be displayed if you load up this notebook; executing all the Python cells will re-create the output.

In [7]:
par_to_html(gal.nH)
Out[7]:
ParamFrozenValueMinMaxUnits
gal.nHFalse1.00.0100000.010^22 atoms / cm^2

If you do don't see the HTML output - e.g. instead seeing something about untrusted HTML - the following image shows you what you are missing.

In [8]:
from IPython.core.display import Image
Image('par.html.png')
Out[8]:
In [9]:
# Here I expand on the parameter view and use similar code to create a table
# for a source expression.

import collections

def parameter_header_raw():
    """Create a TR row for the header items."""
    def th(n): return "<th>{}</th>".format(n)
    return "<tr class='parvals'>" + "".join([th(n) for n in ["Component","Name","Frozen","Value","Min","Max","Units"]]) + "</tr>"

# Note that this code needs to be extended to support properly displaying linked parameters.
def par_to_tr_raw(par, bgcol=False, cptcount=1, newcpt=False):
    """Convert a parameter to a TR.
    
    If bgcol is set True then the model class will be added to the tr
    component (bad parameter name).
    
    The cptcount argument gives the number of parameters there are
    for this component and newcpt is True if it is the first
    parameter of the component.
    """
    def td(n, cname=None): 
        ostr = "<td"
        if cname != None:
            ostr += " class='{}'".format(cname)
        if newcpt and cptcount > 1 and n == "modelname":
            ostr += " rowspan='{}'".format(cptcount)
        return ostr + ">{}</td>".format(getattr(par, n))

    out = "<tr"
    if bgcol:
        out += " class='model'"
    out += ">"
        
    attrs = ["name", "frozen", "val", "min", "max", "units"]
    if newcpt:
        attrs.insert(0, "modelname")
        
    for n in attrs:
        if n == "val" and (par.val <= par.min or par.val >= par.max):
            cname = "atbounds"
        else:
            cname = None
        out += td(n, cname=cname)
    out += "</tr>"
    return out

def model_to_html_raw(mdl):
    """Create HTML display for a model (raw)"""
    def clean(s):
        if s.startswith("(") and s.endswith(")"):
            return s[1:-1]
        else:
            return s
    out = "<table class='model'><caption>" + clean(mdl.name) + "</caption>"
    out += parameter_header_raw()
    
    ctr = collections.Counter([p.modelname for p in mdl.pars])
    
    # have decided to highlight frozen parameters rather than different components
    curmodel = None
    #bgcol = True
    for par in mdl.pars:
        #if par.modelname != curmodel:
        #    bgcol = not bgcol
        #    curmodel = par.modelname
        bgcol = par.frozen    
        out += par_to_tr_raw(par, bgcol=bgcol, cptcount=ctr[par.modelname], newcpt=par.modelname != curmodel)        
        curmodel = par.modelname

    out += "</table>"
    return out

def display_model(mdl):
    "Create a HTML view of the model, displayed by IPython notebook"
    return HTML(model_to_html_raw(mdl))    

We also add CSS styling to bring some "bling" to the table:

In [10]:
%%html
<style>
table.model caption { font-size: 120%; }
tr.parvals { background: lightsteelblue; }
/* have decided to highlight the rows of frozen parameters */
tr.model { background: rgb(255,215,0); background: rgba(255,215,0,0.5)}
/* highlight those values at either the min or max boundary */
td.atbounds { background: goldenrod; }
</style>

This highlights rows of frozen parameters in a gold/yellow color, and then highlights parameter values which are at (or lie outside of) the maximum permitted range (in this case the redshift is at the minimum value).

In [11]:
mdl = get_source()
In [12]:
display_model(mdl)
Out[12]:
xsapec.src * xsphabs.gal
ComponentNameFrozenValueMinMaxUnits
srckTFalse1.00.00864.0keV
AbundancTrue1.00.05.0
redshiftTrue0.00.010.0
normFalse1.00.01e+24
galnHFalse1.00.0100000.010^22 atoms / cm^2

And the PNG of this, in case you can not see the above, is

In [13]:
Image('model.html.png')
Out[13]: