Designing New ๐ Featuresยถ
The Rainbow
class is the heart of chromatic
. We aim for it to be as intuitive and easy-to-use as possible, to enable transparent and repeatable analysis of spectrosopic time-series datasets. This page collects a few explanations that might be useful if you're trying to develop new Rainbow
features.
from chromatic import SimulatedRainbow
How are Rainbow
objects organized?ยถ
The user mainly interacts with quantities like time, wavelength, and flux via the .time
, .wavelength
, .flux
attributes. However, if you more closely at what's happening inside a Rainbow
object, you'll see that these are properties that pull data from some core dictionaries. The general user probably doesn't need to interact with the core dictionaries, but if you're developing new features for Rainbow
objects you will want to understand what's happening a little more clearly.
The core dictionaries are designed to store quantities that have dimensions like either wavelength
, time
, or flux
:
.wavelike[...]
contains everything that has the same dimensions aswavelength
. This dictionary will contain at least a'wavelength'
key, with the actual wavelengths themselves. It might also contain information like the average spectrum of the star, the average S/N at each wavelength, the number of original detector pixels that wound up in this particular wavelength bin, and/or a mask of what wavelengths should be considered good or bad (no matter the time point)..timelike[...]
contains everything that has the same dimensons astime
. This dictionary will contain at least a'time'
key, with the actual times themselves. It might also contain information like a broadband light curve of the transit, the x or y position of the spectrum on the detector, the temperature of the detector, and/or a mask of what times should be considered good or bad (no matter the wavelength)..fluxlike[...]
contains everything that has the same dimensons asflux
. This dictionary will contain at least a'flux'
key, with the actual fluxes themselves. It should also contain an'uncertainty'
keyword with the uncertainties associated with those fluxes (or maybeNone
). It might also contain information about other quantities that depend on both time and wavelength, such as the centroid of the spectral trace, the maximum fraction of saturation, and/or a mask of what individual points should be considered good or bad.
There is one more core dictionary:
.metadata
contains general information that might be useful to hang onto, to pass along to another derived object, or to save out to a file.
r = SimulatedRainbow().inject_noise()
r.wavelike.keys()
dict_keys(['wavelength', 'original_wave_index'])
r.timelike.keys()
dict_keys(['time', 'original_time_index'])
r.fluxlike.keys()
dict_keys(['flux', 'model', 'uncertainty'])
r.metadata.keys()
dict_keys(['name', 'history', 'R', 'wscale', 'tscale', 'signal_to_noise'])
When you retrieve variables with something like .wavelength
, .time
, .flux
, data is being pulled directly from these dictionaries.
What can Rainbow
objects do?ยถ
We defined a small lexicon of things that Rainbow
objects can do. If you want to write a new feature, hopefully it fits into one of these categories. If not, we can certainly discuss adding new ones!
- actions return a new
Rainbow
object as the result. As such, they can be chained together into commands liker.bin(R=5).normalize().plot()
. - wavelike return a wavelike-shaped array from a
Rainbow
, with one quantity for each wavelength. - timelike return a timelike-shaped array from a
Rainbow
, with one quantity for each time. - visualizations create some graphical representation of the data in a
Rainbow
.
Each of these categories has its own directory inside chromatic/rainbows/
where the corresponding code should be stored. The .help()
method attached to any Rainbow
will list everything you can do with it.
r.help()
Hooray for you! You asked for help on what you can do with this ๐ object. Here's a quick reference of a few available options for things to try. ----------- | actions | ----------- ๐๐งฎ๐ | +-*/ Do basic math operations with two Rainbows. ๐๐๐ช | .[:,:]() Index, slice, or mask a Rainbow to get a subset. ๐๐ง๐ | .align_wavelengths() Align spectra with different wavelength grids onto one shared axis. ๐๐งบ๐งฑ | .bin() Bin to a new wavelength or time grid. ๐๐งโ๐คโ๐ง๐ | .compare() Connect to other ๐ objects for easy comparison. ๐๐โฐ | .concatenate_in_time() Stitch together two Rainbows with identical wavelengths. ๐๐๐ | .concatenate_in_wavelength() Stitch together two Rainbows with identical times. ๐๐ฉ๐ | .flag_outliers() Flag outlier data points. ๐โฒ๐ | .fold() Fold times relative to a particular period and epoch. ๐๐งบโฐ | .get_average_lightcurve_as_rainbow() Bin down to a single integrated light curve. ๐๐งบ๐ | .get_average_spectrum_as_rainbow() Bin down to a single integrated spectrum. ๐๐ง๐ฒ | .inject_noise() Inject (uncorrelated, simple) random noise. ๐๐ง๐น | .inject_systematics() Inject (correlated, wobbly) systematic noise. ๐โญ๏ธ๐ป | .inject_spectrum() Inject a static stellar spectrum. ๐๐ช๐ | .inject_transit() Inject a transit signal. ๐๐ซ๐ | .normalize() Normalize by dividing through by a typical spectrum (and/or light curve). ๐๐ดโโ ๏ธ๐ | .remove_trends() Remove smooth trends in time and/or wavelength. ๐๐๐ | .shift() Doppler shift wavelengths. ๐๐ฑ๐ | .trim() Trim away wavelengths or times. ---------------- | get/timelike | ---------------- โฐ๐๐ | .get_average_lightcurve() Get the weighted average light curve. โฐ๐๐ | .get_for_time() Get a quantity associated with a time index. โฐ๐๐ | .get_median_lightcurve() Get the median light curve. โฐ๐๐ | .get_ok_data_for_time() Get a quantity associated with a time index. โฐ๐ฐ๐ | .get_times_as_astropy() Get the times as an astropy Time object. โฐ๐๐ | .set_times_from_astropy() Set the times from an astropy Time object (modifies in-place). ---------------- | get/wavelike | ---------------- ๐๐๐ | .get_average_spectrum() Get the weighted average spectrum. ๐๐๐ | .get_for_wavelength() Get a quantity associated with a wavelength index. ๐๐ฏ๐ | .get_measured_scatter() Get the measured scatter on the time series for each wavelength. ๐๐๐ | .get_median_spectrum() Get the median spectrum. ๐๐๐ | .get_ok_data_for_wavelength() Get a quantity associated with a wavelength index. ๐๐๐ | .get_spectral_resolution() Get the spectral resolution (R=w/dw). ----------- | helpers | ----------- ๐๐๐ | .help() Get one-line help summaries of available methods. ๐๐๐ชต | .history() Get the history that went into this Rainbow. ๐พ๐๐ผ | .save() Save this Rainbow out to a permanent file. ------------------ | visualizations | ------------------ ๐จ๐ฝโฐ | .animate_lightcurves() Animate a sequence of light curves across different wavelengths. ๐จ๐ฝ๐ | .animate_spectra() Animate a sequence of spectra across different times. ๐จ๐ผ๐บ | .imshow() Paint a map of flux across wavelength and time. ๐จ๐น๐บ | .imshow_interact() Show flux map and lightcurves with interactive wavelength selection. ๐จ๐๐บ | .pcolormesh() Paint a map of flux across wavelength and time (with non-uniform grids). ๐จ๐๐งถ | .plot() Plot a sequence of light curves with vertical offsets. ------------------------------ | visualizations/diagnostics | ------------------------------ ๐จ๐๐บ | .imshow_quantities() Show multiple 2D (wavelength and time) quantities as imshow maps. ๐จ๐๐งถ | .plot_quantities() Show multiple 1D (wavelength or time) quantities as scatter plots. ------------------------- | visualizations/models | ------------------------- ๐จ๐บ๐ข | .imshow_with_models() Paint a flux map with model components. ๐จ๐๐ข | .plot_one_wavelength_with_models() Plot one wavelength's light curve with model components. ๐จ๐ฝ๐ข | .animate_with_models() Animate all wavelengths' light curves with model components. ๐จ๐งถ๐ข | .plot_with_model() Plot a sequence of light curves with their models. --------------------------- | visualizations/timelike | --------------------------- ๐จโฐ๐ | .plot_average_lightcurve() Plot the weighted average flux per time. ๐จโฐ๐ | .plot_median_lightcurve() Plot the median flux per time. --------------------------- | visualizations/wavelike | --------------------------- ๐จ๐๐ญ | .plot_average_spectrum() Plot the weighted average flux per wavelength. ๐จ๐๐ | .plot_spectral_resolution() Plot the spectral resolution per wavelength. ๐จ๐๐ง | .plot_noise_comparison() Plot the measured and expected scatter per wavelength. ๐จ๐๐ฅฆ | .plot_noise_comparison_in_bins() Plot measured and expected scatter in different size bins.
How do we add new abilities to Rainbow
objects?ยถ
If you want to add a new method to a Rainbow
object, there are a few general steps you'll probably want to follow.
- Install in development mode (see Installation), so you can modify the code package directly and test the code in place.
- Decide a category of "things a
Rainbow
can do" in which it belongs. - Find the directory for that category in the
chromatic
code package. For example, "actions" that return newRainbow
objects will be inchromatic/rainbows/actions/
. - Look at another example in that directory to get a sense for the general layout. For example, you'll notice that "actions" generally create a copy of
self
, make some changes to that copy, and then return it as the new object. - Add your new function, either to an existing
.py
file where it would make sense or to its own new.py
file. The first argument to your function should beself
, which is theRainbow
object itself, and then any additional arguments should follow afterward. A good way to test out your new function in a notebook or a small isolated script might look something like the following:
from chromatic import *
r = SimulatedRainbow().inject_noise()
def snazzy_new_action(self, x=2):
# create a copy of the original object
new = self._create_copy()
# do something
new.fluxlike['flux'] = new.flux**x
# return the modified copy
return new
output = snazzy_new_action(r)
This allows you to develop and test your new function without having to worry about it being imported properly into the core Rainbow
definition.
- Connect your new function into the
Rainbow
class definition. Normally, we might define a new method directly in the same file as the class definition itself, but we wanted to split the method definitions into multiple files and directories, to make things easier to find. Importing your new function to become aRainbow
method takes a few steps. For example, let's imagine you're making a new "action" calledsnazzy_new_action
and it's located inchromatic/rainbows/actions/snazzy.py
:- In
rainbows/actions/snazzy.py
, include line of code like__all__ = ['snazzy_new_action']
at the top. The__all__
list defines what would get imported via a line likefrom chromatic.rainbows.actions.snazzy import *
(things not in__all__
will need to be explicitly imported). - In
rainbows/actions/__init__.py
, include a line of code likefrom .snazzy import *
, so that imports fromchromatic.rainbows.actions
will know how to find things insnazzy.py
. - In
rainbows/rainbow.py
, down at the very bottom of the class definition forRainbow
, addsnazzy_new_action
to the list offrom .actions import (...)
. This, finally, will mean that allRainbow
objects will have access to your new method, and we can do things liker.snazzy_new_action()
from anyRainbow
. - In
rainbows/actions/descriptions.txt
, add a row describing your snazzy new action. This table defines what appears in the.help()
method.
- In
- Write a test for your new feature. This is a function that somehow tests whether your new feature works; the simplest form would be "does this function run", a slightly fancier version would be "are its outputs accurate." To write a test:
- Look in
chromatic/tests/
to find a bunch oftest_*.py
files with examples of automated tests in them. - Create a function that has
*test*
somewhere in its name and store it somewhere in thetests
directory. At a minimum, this function should test that the bit of code you wrote runs without breaking. - Run
pytest
from the command line within the main repository directory. This will run your test function (along with all the other tests) and tell you whether it passed or failed. Make sure it passes!
- Look in
How do we add new readers/writers for Rainbow
objects?ยถ
You might want to create a new reader or writer, to allow chromatic
to interact with your own datasets or tools. To facilitate this, templates are available with human-friendly instructions for how to add a new reader or writer. If you want to try to incorporate a new reader or writer, please:
- Install in development mode (see Installation), so you can modify the code package directly and test the code in place.
- Navigate to
chromatic/rainbows/[readers|writers]/template.py
. - Follow the instructions in the comments in that file.
Good luck!