ScopeSim Sources

ScopeSim Source and SourceField Overview

What is a Source?

Source is the top-level target container passed into OpticalTrain.observe(). It combines:

  • Spatial information: where light is on sky (source.fields)

  • Spectral information: what spectrum each component has (source.spectra)

  • Metadata: extra context (source.meta)

A single Source can hold multiple components (e.g., stars + diffuse background + cube).


What is a SourceField?

Source.fields is a list that stores SourceField objects representing one spatial/spectral component.

Available field types (subclasses of SourceField):

  1. ``TableSourceField``

    • Stores an astropy.table.Table with point-source rows.

    • Typical columns: x, y, ref, weight.

    • ref maps each row to an entry in source.spectra.

  2. ``ImageSourceField``

    • Stores a 2D fits.ImageHDU (surface-brightness map).

    • Uses WCS in header for sky-to-pixel mapping.

    • Usually linked to one spectrum via header metadata (e.g., SPEC_REF).

  3. ``CubeSourceField``

    • Stores a 3D cube (fits.ImageHDU) with spatial + wavelength axes.

    • Contains its own spectral axis (not just one average spectrum).


[1]:
import matplotlib.pyplot as plt
import numpy as np
import astropy.units as u
from astropy.table import Table, vstack
from astropy.io import fits

import scopesim_templates as sim_tp
from scopesim.source.source import Source
from scopesim.source.source_fields import TableSourceField, ImageSourceField, CubeSourceField
import scopesim.source.source_templates  as source_templates
import scopesim.source.source_utils as source_utils

def plot_source(source):
    wave = np.linspace(0.3, 2.5, 1001) * u.um
    num_fields = len(source.fields)
    fig, axs = plt.subplots(figsize=(6,2*num_fields), nrows=num_fields, ncols=2, width_ratios=[1,2], constrained_layout=True)
    if num_fields == 1:
        axs = np.array([axs])
    for i, field in enumerate(source.fields):
        ax1, ax2 = axs[i]
        ax1.imshow(field.data, origin='lower', cmap='viridis')
        ax2.plot(wave, field.spectrum(wave))
        ax1.set_title('Spatial Profile')
        ax1.set_xlabel('X (arcsec)')
        ax1.set_ylabel('Y (arcsec)')
        ax2.set_title('Spectrum')
        ax2.set_xlabel('Wavelength (um)')
        ax2.set_ylabel(r'Flux (ph s$^{-1}$ cm$^{-2}$ $\AA^{-1}$)')
/Users/yashvi/Desktop/ZShooter/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

How to create Source objects

A) From a point-source table + spectra

[2]:
tbl = Table(
    data=[[0.0, 1.2], [0.0, -0.5], [0, 0], [1.0, 0.7]],
    names=["x", "y", "ref", "weight"],
)
tbl["x"].unit = u.arcsec
tbl["y"].unit = u.arcsec

ps = Source(table=tbl, spectra=[source_templates.vega_spectrum(10)])
ps.fields
[2]:
[TableSourceField(field=<Table length=2>
    x       y     ref   weight
  arcsec  arcsec
 float64 float64 int64 float64
 ------- ------- ----- -------
     0.0     0.0     0     1.0
     1.2    -0.5     0     0.7, spectra={0: <synphot.spectrum.SourceSpectrum object at 0x11205ee10>})]

B) From an image HDU + spectrum

[3]:
img = np.ones((64, 64), dtype=float)
hdr = fits.Header(cards={'CUNIT1':'arcsec', 'CUNIT2':'arcsec', 'CDELT1':1, 'CDELT2':1}) # need to include this minimal header
hdu = fits.ImageHDU(data=img, header=hdr)

im = Source(image_hdu=hdu, spectra=[source_templates.ab_spectrum(mag=20)]) # ab_spectrum creates a spectrum with constant flux in magnitude space of given mag (which does not mean constant in erg/s/cm2/Angstrom)
print(im.fields)
plot_source(im)

[ImageSourceField(field=<astropy.io.fits.hdu.image.ImageHDU object at 0x1139d55b0>, wcs=WCS Keywords

Number of WCS axes: 2
CTYPE : '' ''
CUNIT : 'deg' 'deg'
CRVAL : 0.0 0.0
CRPIX : 0.0 0.0
PC1_1 PC1_2  : 1.0 0.0
PC2_1 PC2_2  : 0.0 1.0
CDELT : 0.000277777777777777 0.000277777777777777
NAXIS : 64  64, spectra={0: <synphot.spectrum.SourceSpectrum object at 0x10de474a0>})]
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_5_1.png

NOTE: Can also use src.source_templates.uniform_source() to create the above source in one step as shown below. The default spatial sampling is 1 arcsec/pixel.

[4]:
im2 = source_templates.uniform_source(source_templates.ab_spectrum(mag=20), extent=64)
print(im2.fields)
plot_source(im2)
[ImageSourceField(field=<astropy.io.fits.hdu.image.ImageHDU object at 0x113cbc830>, wcs=WCS Keywords

Number of WCS axes: 2
CTYPE : 'RA---TAN' 'DEC--TAN'
CUNIT : 'deg' 'deg'
CRVAL : 0.0 0.0
CRPIX : 32.5 32.5
PC1_1 PC1_2  : 1.0 0.0
PC2_1 PC2_2  : 0.0 1.0
CDELT : -0.00027777777777778 0.00027777777777778
NAXIS : 64  64, spectra={0: <synphot.spectrum.SourceSpectrum object at 0x113acede0>})]
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_7_1.png

To create a uniform source in PHOTLAM units (photons/s/cm^2/Angstrom), use ConstFlux1D with the appropriate amplitude (e.g., 0.001 for 1 photon/s/cm^2/Angstrom):

[5]:
sp = source_templates.ConstFlux1D(amplitude=0.001) # synphot model, default unit is photon/s/cm^2/Angstrom
waves = np.geomspace(100, 300000, 50000)
lookup = sp(waves)
im3 = source_templates.uniform_source(
                                    source_templates.SourceSpectrum(source_templates.Empirical1D, points=waves, lookup_table=lookup),  # synphot spectrum
                                    extent=60)
plot_source(im3)
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_9_0.png

C) From helper templates (quick start) available in scopesim_templates package

[6]:
# star
star = sim_tp.star(filter_name='V', amplitude=10*u.ABmag) # spectra taken from 'spextra' package
star.plot()
[6]:
<Axes: xlabel='x [arcsec]', ylabel='y [arcsec]'>
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_11_1.png
[7]:
# Galaxy
gal = sim_tp.extragalactic.galaxies.spiral_two_component(extent=200*u.arcsec, fluxes=(15, 15)*u.mag)  # loaded from saved fits template of NGC1232L
gal.shift(dy=-30*0.004)
plot_source(gal)
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_12_0.png

Other available galaxy models include galaxy, galaxy3d, and elliptical. See scopesim_templates.extragalactic.galaxies for details.

Useful sources

A uniform source to simulate dome flats

[8]:
# Let's say we want a high-power halogen bulb emitting a 3000 K blackbody spectrum, uniformly illuminating a 1 arcmin x 1 arcmin area on the sky. We can create this source as follows:
domeflat = sim_tp.calibration.flat_field(temperature=3000*u.K, amplitude=4*u.ABmag, filter_curve='V', extend=60)
plot_source(domeflat)
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_16_0.png

Arc lamps for wavelength calibration

[9]:
# load line list
uvb = Table.read('line_lists/ThAr_XSHOOTER_UVB_lines.dat', delimiter='|', format='ascii', header_start=0)
neir = Table.read('line_lists/Ne_IR_MOSFIRE_lines.dat', delimiter='|', format='ascii', header_start=0)
arir = Table.read('line_lists/Ar_IR_MOSFIRE_lines.dat', delimiter='|', format='ascii', header_start=0)
lines = vstack([uvb, neir, arir])
lines
[9]:
Table length=1292
col0waveionNISTInstramplitudeSource_1
int64float64str4int64int64float64str20int64
--3094.261313368305ThAr132218.70677052723238wavemodel.py--
--3140.0171522371547ThAr132148.12281124090538wavemodel.py--
--3170.5540268969266ThAr132158.8138285062605wavemodel.py--
--3181.1243070684113ThAr132227.88057703827917wavemodel.py--
--3189.181869102448ThAr132222.56046465150408wavemodel.py--
--3233.222527087156ThAr132199.46752294459446wavemodel.py--
--3239.0583334075486ThAr132145.47867955933862wavemodel.py--
--3244.661251438086ThAr132208.4018341765855wavemodel.py--
--3245.1958097699485ThAr132253.38302082586046wavemodel.py--
........................
--22118.66ArI132360.0keck_mosfire_webpage--
--22539.74ArI13240.0keck_mosfire_webpage--
--22978.37ArI132150.0keck_mosfire_webpage--
--23139.52ArI13211910.0keck_mosfire_webpage--
--23851.54ArI13210480.0keck_mosfire_webpage--
--23973.06ArI13213070.0keck_mosfire_webpage--
--24783.37ArI132150.0keck_mosfire_webpage--
--25132.13ArI1325760.0keck_mosfire_webpage--
--25512.18ArI1323750.0keck_mosfire_webpage--
--25668.02ArI132900.0keck_mosfire_webpage--
[10]:
R = 60000 # desired spectral resolution

centers = np.array(lines['wave']).astype(float)  # in Angstrom
fwhms = 2.6*(centers/R) # nyquist sampled
stddevs = fwhms / (2*np.sqrt(2*np.log(2)))
amps = np.array(lines['amplitude']).astype(float)

waves = np.arange(3000, 25000, 0.5/R)
# Fast sparse Gaussian accumulation — only touches pixels within ±10σ per line
sp = np.zeros(len(waves))
dw = 0.5/R

for cen, std, amp in zip(centers, stddevs, amps):
    hw = int(np.ceil(10 * std / dw))          # half-window in pixels
    idx = int(round((cen - waves[0]) / dw))  # center pixel
    lo = max(0, idx - hw)
    hi = min(len(waves), idx + hw + 1)
    w_local = waves[lo:hi]
    sp[lo:hi] += amp * np.exp(-(w_local - cen)**2 / (2 * std**2))
[11]:
lamp1 = source_templates.uniform_source(
                                    source_templates.SourceSpectrum(source_templates.Empirical1D, points=waves, lookup_table=sp),  # synphot spectrum
                                    extent=60)
plot_source(lamp1)
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_20_0.png

NOTE Plotting results for higher values of R takes forever because the array size gets pretty large

Extragalactic transient: Supernova

Template spectra can be pulled from A-star Vienna’s spextra package library: https://scopesim.univie.ac.at/spextra/database/libraries/

from spextra import Spextrum
sed = Spextrum('brown/NGC3870')
[23]:
# base galaxy
gal = sim_tp.galaxy(sed='brown/NGC3870', z=0.02, amplitude=15, filter_curve='g', pixel_scale=1, r_eff=2.5, n=1., ellip=0.3, theta=0, extend=5)
plot_source(gal)

# download spectrum from spextra library
from spextra import Spextrum
sed = Spextrum('sne/sn1a').redshift(0.02) # redshift it to match the host galaxy
spec = sed.scale_to_magnitude(14*u.ABmag, 'g')
spec.plot()
# add sn to gal at 5",5" offset from center
gal.append(Source(spectra=spec, x=[5.0], y=[5.0], ref=[0.], weight=[1.0]))
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_24_0.png
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_24_1.png
[22]:
im = gal.image(wave_min=0.3, wave_max=2.5, pixel_scale=1*u.arcsec)
im.plot()
astar.scopesim.optics.image_plane - No BUNIT found in added HDU.
py.warnings - WARNING: /Users/yashvi/Desktop/ZShooter/ScopeSim/scopesim/source/source.py:501: DeprecationWarning: Adding a table directly to the ImagePlane is deprecated since v0.10.0. Passing a table to ImagePlane.add() will raise an error from version 0.12 onwards. Use FOV to add tables and image HDUs together before.
  im_plane.add(hdu_or_table, sub_pixel=sub_pixel,

[22]:
(<Figure size 640x480 with 2 Axes>, <Axes: >)
../../../_images/_staged_sim_notebooks_ii_ScopeSim_Sources_25_2.png