Working with Instruments¶
When working with multi wavelength observations (i.e. observables from a range of instruments with a range of filters, resolutions, depths etc.) we need a way to collect the diverse technical properties of instruments. This is where Instrument
and InstrumentCollections
come in. An Instrument
contains the the techincal properties of a single instrument, e.g. NIRCam filter curves, resolution, depths, SNR, or noise maps (we’ll detail the use cases for the properties below). An
InstrumentCollection
is a container enabling manipulation of multiple Instruments
in a single object.
For a Pipeline
(see pipeline docs) InstrumentCollection
objects are an important building block of automating pipelines to generate observables.
Creating an Instrument
¶
We’ll focus on different use cases in the section below. First we’ll cover the creation of simple Instrument
instances and combining them into InstrumentCollections
. For this we’ll just use simple Instruments
defining only the filters (which would be the case if you only want to generate photometry). We’ll begin by creating two filter collections, one for Webb’s NIRCam instrument and the other for generic UVJ top hats.
For more information on filters see the filter docs.
[1]:
from synthesizer.instruments import UVJ, FilterCollection
# Get the filters
webb_filters = FilterCollection(
filter_codes=[
f"JWST/NIRCam.{f}"
for f in ["F090W", "F150W", "F200W", "F277W", "F356W", "F444W"]
],
)
uvj_filters = UVJ()
Calculated wavelength array:
min = 7.74e+03 Angstrom
max = 5.11e+04 Angstrom
FilterCollection.lam.size = 4763
Calculated wavelength array:
min = 3.29e+03 Angstrom
max = 1.33e+04 Angstrom
FilterCollection.lam.size = 12811
Now that we have the filters in hand we can simply instantiate the Instrument
objects with the filters and a label.
[2]:
from synthesizer.instruments import Instrument
# Instatiate the instruments
webb_inst = Instrument("JWST", filters=webb_filters)
uvj_inst = Instrument("UVJ", filters=uvj_filters)
As with everything else in Synthesizer, if we want to see whats inside the instruments we just created we can print them to see a table.
[3]:
print(webb_inst)
+------------------------------------------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+-----------------------------------------------------------------------------+
| Attribute | Value |
+------------------------------------+-----------------------------------------------------------------------------+
| label | 'JWST' |
+------------------------------------+-----------------------------------------------------------------------------+
| filters | <synthesizer.instruments.filters.FilterCollection object at 0x7f16343233d0> |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_photometry | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
[4]:
print(uvj_inst)
+------------------------------------------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+-----------------------------------------------------------------------------+
| Attribute | Value |
+------------------------------------+-----------------------------------------------------------------------------+
| label | 'UVJ' |
+------------------------------------+-----------------------------------------------------------------------------+
| filters | <synthesizer.instruments.filters.FilterCollection object at 0x7f163436b010> |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_photometry | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
You can see from these print outs that we have the label and FilterCollections
we passed but also a series of flags defining what the instrument can be used for. Since we only passed filters
we can only generate photometry from spectra with the “default” wavelength array (the one used by the `Grid
<../grids/grids.rst>`__).
Combining Instruments
¶
If we want to combine these into a single InstrumentCollection
we can simply add them together (this can be done for arbitrarily many instruments).
[5]:
instruments = webb_inst + uvj_inst
print(instruments)
+--------------------------------------+
| INSTRUMENT COLLECTION |
+-------------------+------------------+
| Attribute | Value |
+-------------------+------------------+
| ninstruments | 2 |
+-------------------+------------------+
| instrument_labels | [JWST, UVJ, ] |
+-------------------+------------------+
| instruments | JWST: Instrument |
| | UVJ: Instrument |
+-------------------+------------------+
Here we can see the ImageCollection
contains both the instruments as expected.
Working with InstrumentCollections
¶
If we want to extract a specific instrument from an InstrumentCollection
we can treat it exactly as we would a dictionary by indexing with the label.
[6]:
print(instruments["JWST"].label)
JWST
If we want to loop through Instruments
in the collection we can treat it as an iterable.
[7]:
for inst in instruments:
print(inst.label)
JWST
UVJ
We can write an InstrumentCollection
to a HDF5 file to reload it later by calling the write_instruments
method. This simply requires a filepath for where to save the InstrumentCollection
.
[8]:
instruments.write_instruments("instruments.hdf5")
We can then use this file later rather than making and combining all the individual Instruments
but passing the filepath to an InstrumentCollection
at instantiation.
[9]:
from synthesizer.instruments import InstrumentCollection
instruments = InstrumentCollection(filepath="instruments.hdf5")
Instrument Use Cases¶
We’ve already seen the pure photometry use case where you only need to include a FilterCollection
on your Instrument
. However, that is the tip of the iceberg. Below each section details what is needed for different observables.
Spectroscopy¶
It’s entirely possible to generate spectra for a galaxy without an Instrument
, instead using the Grid
wavelengths (see the spectra docs), but if you want to match observations by a particular instrument (e.g. LSST, DESI) you’ll need the specific wavelength array for that instrument.
[10]:
import numpy as np
from unyt import angstrom
lsst_inst = Instrument("LSST", lam=np.linspace(10**3, 10**4, 100) * angstrom)
print(lsst_inst)
+----------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+---------------------------------------------+
| Attribute | Value |
+------------------------------------+---------------------------------------------+
| label | 'LSST' |
+------------------------------------+---------------------------------------------+
| can_do_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_photometry | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| lam (100,) | 1.00e+03 Å -> 1.00e+04 Å (Mean: 5.50e+03 Å) |
+------------------------------------+---------------------------------------------+
You can also include a SNR and depth in apparent magnitude to parametrise any noise contribution to the resultant spectra.
[11]:
desi_inst = Instrument(
"LSST", lam=np.linspace(10**3, 10**4, 100) * angstrom, snrs=5, depth=28
)
print(desi_inst)
+----------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+---------------------------------------------+
| Attribute | Value |
+------------------------------------+---------------------------------------------+
| label | 'LSST' |
+------------------------------------+---------------------------------------------+
| depth | 28 |
+------------------------------------+---------------------------------------------+
| snrs | 5 |
+------------------------------------+---------------------------------------------+
| can_do_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_photometry | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| lam (100,) | 1.00e+03 Å -> 1.00e+04 Å (Mean: 5.50e+03 Å) |
+------------------------------------+---------------------------------------------+
Imaging¶
For imaging we need to be able to first make the photometry so we need a FilterCollection
, but then for simple images without observational effects all we need is the resolution with units.
[12]:
from unyt import kpc
webb_inst = Instrument("JWST", filters=webb_filters, resolution=0.1 * kpc)
print(webb_inst)
+------------------------------------------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+-----------------------------------------------------------------------------+
| Attribute | Value |
+------------------------------------+-----------------------------------------------------------------------------+
| label | 'JWST' |
+------------------------------------+-----------------------------------------------------------------------------+
| filters | <synthesizer.instruments.filters.FilterCollection object at 0x7f16343233d0> |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_photometry | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+-----------------------------------------------------------------------------+
If we want to include the effects of an instruments point spread function (PSF) we can pass a dictionary of PSF arrays with one for each filter.
[13]:
# Generate a dictionary of FAKE PSFs, importantly with a PSF for each filter
psfs = {f: np.ones((100, 100)) for f in webb_filters.filters}
webb_inst = Instrument(
"JWST", filters=webb_filters, resolution=0.1 * kpc, psfs=psfs
)
print(webb_inst)
+------------------------------------------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+-----------------------------------------------------------------------------+
| Attribute | Value |
+------------------------------------+-----------------------------------------------------------------------------+
| label | 'JWST' |
+------------------------------------+-----------------------------------------------------------------------------+
| filters | <synthesizer.instruments.filters.FilterCollection object at 0x7f16343233d0> |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_photometry | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+-----------------------------------------------------------------------------+
| psfs | JWST/NIRCam.F090W: ndarray |
| | JWST/NIRCam.F150W: ndarray |
| | JWST/NIRCam.F200W: ndarray |
| | JWST/NIRCam.F277W: ndarray |
| | JWST/NIRCam.F356W: ndarray |
| | JWST/NIRCam.F444W: ndarray |
+------------------------------------+-----------------------------------------------------------------------------+
If we want to include noise there’s a couple of different approaches.
We can either pass the depth and Signal-to-Noise Ratio (SNR) for each filter.
[14]:
# Generate depths and snrs, again there must be one for each filter
depths = {f: 28.0 for f in webb_filters.filters}
snrs = {f: 5.0 for f in webb_filters.filters}
webb_inst = Instrument(
"JWST",
filters=webb_filters,
resolution=0.1 * kpc,
psfs=psfs,
depth=depths,
snrs=snrs,
)
print(webb_inst)
+------------------------------------------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+-----------------------------------------------------------------------------+
| Attribute | Value |
+------------------------------------+-----------------------------------------------------------------------------+
| label | 'JWST' |
+------------------------------------+-----------------------------------------------------------------------------+
| filters | <synthesizer.instruments.filters.FilterCollection object at 0x7f16343233d0> |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_photometry | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+-----------------------------------------------------------------------------+
| depth | JWST/NIRCam.F090W: float |
| | JWST/NIRCam.F150W: float |
| | JWST/NIRCam.F200W: float |
| | JWST/NIRCam.F277W: float |
| | JWST/NIRCam.F356W: float |
| | JWST/NIRCam.F444W: float |
+------------------------------------+-----------------------------------------------------------------------------+
| snrs | JWST/NIRCam.F090W: float |
| | JWST/NIRCam.F150W: float |
| | JWST/NIRCam.F200W: float |
| | JWST/NIRCam.F277W: float |
| | JWST/NIRCam.F356W: float |
| | JWST/NIRCam.F444W: float |
+------------------------------------+-----------------------------------------------------------------------------+
| psfs | JWST/NIRCam.F090W: ndarray |
| | JWST/NIRCam.F150W: ndarray |
| | JWST/NIRCam.F200W: ndarray |
| | JWST/NIRCam.F277W: ndarray |
| | JWST/NIRCam.F356W: ndarray |
| | JWST/NIRCam.F444W: ndarray |
+------------------------------------+-----------------------------------------------------------------------------+
If we already have noise maps for each filter we can instead pass a noise map per filter.
[15]:
# Generate FAKE noise maps, again there must be one for each filter
noise_maps = {f: np.random.rand(100, 100) for f in webb_filters.filters}
webb_inst = Instrument(
"JWST", filters=webb_filters, resolution=0.1 * kpc, noise_maps=noise_maps
)
print(webb_inst)
+------------------------------------------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+-----------------------------------------------------------------------------+
| Attribute | Value |
+------------------------------------+-----------------------------------------------------------------------------+
| label | 'JWST' |
+------------------------------------+-----------------------------------------------------------------------------+
| filters | <synthesizer.instruments.filters.FilterCollection object at 0x7f16343233d0> |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_imaging | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_photometry | True |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_resolved_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| can_do_spectroscopy | False |
+------------------------------------+-----------------------------------------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+-----------------------------------------------------------------------------+
| noise_maps | JWST/NIRCam.F090W: ndarray |
| | JWST/NIRCam.F150W: ndarray |
| | JWST/NIRCam.F200W: ndarray |
| | JWST/NIRCam.F277W: ndarray |
| | JWST/NIRCam.F356W: ndarray |
| | JWST/NIRCam.F444W: ndarray |
+------------------------------------+-----------------------------------------------------------------------------+
Resolved Spectroscopy¶
As well as producing integrated spectroscopy we can also use an Instrument
for resolved spectroscopy. For this we need the wavelength array for the Instrument
and the resolution of the spaxels.
[16]:
nirspec = Instrument(
"NIRSpec",
lam=np.linspace(10**3, 10**4, 100) * angstrom,
resolution=0.1 * kpc,
)
print(nirspec)
+----------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+---------------------------------------------+
| Attribute | Value |
+------------------------------------+---------------------------------------------+
| label | 'NIRSpec' |
+------------------------------------+---------------------------------------------+
| can_do_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_photometry | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_resolved_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+---------------------------------------------+
| lam (100,) | 1.00e+03 Å -> 1.00e+04 Å (Mean: 5.50e+03 Å) |
+------------------------------------+---------------------------------------------+
Like with imaging we can also pass a PSF to include its effects but here we only need one since we don’t have individual filters.
[17]:
nirspec = Instrument(
"NIRSpec",
lam=np.linspace(10**3, 10**4, 100) * angstrom,
resolution=0.1 * kpc,
psfs=np.ones((100, 100)),
)
print(nirspec)
+----------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+---------------------------------------------+
| Attribute | Value |
+------------------------------------+---------------------------------------------+
| label | 'NIRSpec' |
+------------------------------------+---------------------------------------------+
| can_do_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_resolved_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_spectroscopy | False |
+------------------------------------+---------------------------------------------+
| can_do_photometry | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_resolved_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+---------------------------------------------+
| lam (100,) | 1.00e+03 Å -> 1.00e+04 Å (Mean: 5.50e+03 Å) |
+------------------------------------+---------------------------------------------+
| psfs (100, 100) | 1.00e+00 -> 1.00e+00 (Mean: 1.00e+00) |
+------------------------------------+---------------------------------------------+
We can also apply noise to the spectrum by passing the noise as a function of wavelength to the noise_maps
argument.
[18]:
nirspec = Instrument(
"NIRSpec",
lam=np.linspace(10**3, 10**4, 100) * angstrom,
resolution=0.1 * kpc,
psfs=np.ones((100, 100)),
noise_maps=np.random.rand(100, 100),
)
print(nirspec)
+----------------------------------------------------------------------------------+
| INSTRUMENT |
+------------------------------------+---------------------------------------------+
| Attribute | Value |
+------------------------------------+---------------------------------------------+
| label | 'NIRSpec' |
+------------------------------------+---------------------------------------------+
| can_do_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_noisy_resolved_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_noisy_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_photometry | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_imaging | False |
+------------------------------------+---------------------------------------------+
| can_do_psf_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_resolved_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| can_do_spectroscopy | True |
+------------------------------------+---------------------------------------------+
| resolution | 0.1 kpc |
+------------------------------------+---------------------------------------------+
| lam (100,) | 1.00e+03 Å -> 1.00e+04 Å (Mean: 5.50e+03 Å) |
+------------------------------------+---------------------------------------------+
| psfs (100, 100) | 1.00e+00 -> 1.00e+00 (Mean: 1.00e+00) |
+------------------------------------+---------------------------------------------+
| noise_maps (100, 100) | 1.46e-04 -> 1.00e+00 (Mean: 5.00e-01) |
+------------------------------------+---------------------------------------------+