Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a135fff
Refactor PALACE parameter handling, add spectrum validation, and impr…
baileyji May 29, 2026
8532590
Add `BUNIT` header entry for spectral trace images clears up warnings
baileyji May 29, 2026
ed85b03
Add effect order diagnostics
baileyji May 29, 2026
00d9769
Avoid writing generated echelle traces to package
baileyji May 29, 2026
caf99db
Add image-plane background and detector response effects
baileyji May 29, 2026
718ff35
Add post-disperser diffuse background effect
baileyji May 29, 2026
af0850b
Handle detector WCS for diffuse background pixels
baileyji May 30, 2026
51771ad
Cache repeated ADC and moon calculations
baileyji May 30, 2026
3e5e3fb
Cache post-disperser diffuse spectral rates
baileyji May 30, 2026
d50b7e8
Cache FOV spectrum wavesets
baileyji May 30, 2026
50681ca
Improve analytical PSF kernel flux handling
baileyji May 31, 2026
b928d4e
Add tapered detector quantum efficiency
baileyji Jun 1, 2026
83984b8
Use flattop tapered quantum efficiency
baileyji Jun 2, 2026
3bc9826
Rename tapered QE passband width
baileyji Jun 2, 2026
416831a
Support downstream throughput in diffuse backgrounds
baileyji Jun 3, 2026
7b3babd
Allow declared missing selector values
baileyji Jun 3, 2026
5904156
Avoid duplicate selector wheel missing logs
baileyji Jun 3, 2026
46ba078
fixed unit transfer during SpectralSurface creation
Yashvi-Sharma Jun 30, 2026
97ae50a
Add support for detector angle in echelle trace calculations
baileyji Jul 1, 2026
70adcc6
Use detector-local scales for spectral image planes
baileyji Jul 1, 2026
c0ad0b5
Add optional trace flux Jacobian scaling
baileyji Jul 1, 2026
c98be9a
Keep decoupled detector headers out of FOV sky geometry
baileyji Jul 1, 2026
d4840a7
Clip analytical echelle traces in detector frame
baileyji Jul 1, 2026
a0f95d5
Stabilize spectral trace transform fits
baileyji Jul 2, 2026
1616827
Keep analytical echelle padding out of trace geometry
baileyji Jul 2, 2026
6b7c3eb
Refine spectral trace logic by removing unused edge wave calculations…
baileyji Jul 2, 2026
7f3c4a5
Update with full instrument design parameters, fixing focal length is…
baileyji Jul 3, 2026
2657d61
Clip tiny negative trace pixels.
baileyji Jul 3, 2026
d6aa85d
Merge coo main and Yashvi develop
baileyji Jul 5, 2026
2eb4f23
Keep analytical echelle geometry consistent
baileyji Jul 23, 2026
12a5ea7
Keep echelle trace tables in physical coordinates
baileyji Jul 23, 2026
4755a86
formatting
baileyji Jul 24, 2026
bb83f05
Add live spectral-trace detector coordinate query
baileyji Jul 24, 2026
27e246e
Allow trace queries at rounded FOV boundaries
baileyji Jul 24, 2026
03dcbca
Refactor selector wheel effect retrieval logic in optical train
baileyji Jul 24, 2026
3dcf441
Adjust echelle trace order calculation to ensure inclusive range.
baileyji Jul 25, 2026
9e76ccd
put skycalc timeout as a effect kwarg passed by alias property, refac…
Yashvi-Sharma Aug 6, 2026
713cfd1
Code cleanup: simplify and reformat inline loops, conditionals, and a…
baileyji Aug 7, 2026
d69d575
Refine sky wavelength bounds check to avoid redundant warnings.
baileyji Sep 2, 2026
ed222c1
Fix a shadowing bug with alpha in Moffat/AOEnhanceablePSF that produc…
baileyji Sep 2, 2026
a3a0776
Improve handling of unit conversion and caching for to standardize an…
baileyji Sep 2, 2026
081f756
fixed bright, grey, dark time params for moon background
Yashvi-Sharma Sep 4, 2026
2c1608a
Update Python version requirement to 3.12 to fix workflow errors
Yashvi-Sharma Sep 4, 2026
70f6ae0
Reverted Python version change
Yashvi-Sharma Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions .github/workflows/bump_on_dependency.yml
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
name: Bump dev version on dependency PR
on:
pull_request:
types: [closed]

jobs:
Bump:
name: Bump prerelease version
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
contains(
github.event.pull_request.labels.*.name,
'dependencies'
)
uses: AstarVienna/DevOps/.github/workflows/bump.yml@main
secrets: inherit
with:
rule: prerelease
branch: main

name: Bump dev version on dependency PR
on:
pull_request:
types: [closed]

jobs:
Bump:
name: Bump prerelease version
if: >
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
contains(
github.event.pull_request.labels.*.name,
'dependencies'
)
uses: AstarVienna/DevOps/.github/workflows/bump.yml@main
secrets: inherit
with:
rule: prerelease
branch: main
1 change: 1 addition & 0 deletions scopesim/defaults.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,7 @@ properties :

spectral_bin_width : !!float 1E-4
spectral_resolution: 5000
trace_flux_jacobian : False
minimum_throughput : !!float 1E-6
minimum_pixel_flux : 1

Expand Down
1 change: 1 addition & 0 deletions scopesim/effects/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@
from .surface_list import *
from .ter_curves import *
from . import ter_curves_utils
from .illumination import *

from .detector_list import *
from .electronic import *
Expand Down
71 changes: 51 additions & 20 deletions scopesim/effects/atmo_dispersion.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,7 +218,7 @@ class ADShift(ShiftFoV3D):
def __init__(self, **kwargs):
super().__init__(**kwargs)

self.target, self.location, self.time = get_observation_info_from_cmds(self.cmds)
self.target, self.location, self.time, _ = get_observation_info_from_cmds(self.cmds)

self.zenith_angle = get_zenith_angle(self.target, self.location, self.time) * u.deg

Expand DownExpand Up@@ -298,9 +298,12 @@ def __init__(self, **kwargs):
if 'filename' not in kwargs and 'zenith_angle_error' not in kwargs:
raise ValueError("Residuals must be supplied through either filename or zenith_angle_error.")

self.target, self.location, self.time = get_observation_info_from_cmds(self.cmds)
self.target, self.location, self.time, _ = get_observation_info_from_cmds(self.cmds)

self.zenith_angle = get_zenith_angle(self.target, self.location, self.time) * u.deg
self._ad_shift_cache_key = None
self._ad_shift_cache = None
self._residual_interpolator_cache = None

def get_shifts(self, obj: FieldOfView3D):
"""
Expand All@@ -313,35 +316,24 @@ def get_shifts(self, obj: FieldOfView3D):

if self.data is not None and isinstance(self.data, Table):
logger.info(f'Residuals supplied by {self.meta["filename"]}')
Z = self.data.colnames
Z.remove("wavelength")
lam_um = quantity_from_table("wavelength", self.data, "um").value
R = np.array([np.array(self.data[z]).astype(float) for z in Z])
R_unit = u.Unit(self.data.meta.get("shifts_unit", "arcsec"))
Z = np.array(Z).astype(float)
adc_opt_resid = RegularGridInterpolator((Z, lam_um), R, method="linear", bounds_error=False,
fill_value=None)

adc_nir_resid = lambda xy: adc_opt_resid((xy[0], (xy[1] - 1) / 1.5 * (1.1 - .31) + .31)) # scale to nIR
# 300-500 & 500-1000
adc_ub_resid = lambda xy: adc_opt_resid((xy[0], (xy[1] - .3) / .2 * (1.1 - .31) + .31)) / 2 # scale
adc_gri_resid = lambda xy: adc_opt_resid((xy[0], (xy[1] - .5) / .5 * (1.1 - .31) + .31)) / 2 # scale to nIR
R_unit, adc_opt_resid = self._get_residual_interpolator()

if self.meta.get("use_broadband", False):
use = wave < 1.0 * u.um
shifts[use] += (adc_opt_resid(
(self.zenith_angle.to_value(u.deg), wave[use].to_value(u.um))) * R_unit).to(u.arcsec)
else:
use = wave < 0.5 * u.um
res1 = (adc_ub_resid(
res1 = (self._adc_ub_resid(adc_opt_resid,
(self.zenith_angle.to_value(u.deg), wave[use].to_value(u.um))) * R_unit).to(u.arcsec)
shifts[use] += res1
use = ~use & (wave < 1.0 * u.um)
res2 = (adc_gri_resid(
res2 = (self._adc_gri_resid(adc_opt_resid,
(self.zenith_angle.to_value(u.deg), wave[use].to_value(u.um))) * R_unit).to(u.arcsec)
shifts[use] += res2
use = wave >= 1.0 * u.um
res3 = (adc_nir_resid((self.zenith_angle.to_value(u.deg), wave[use].to_value(u.um))) * R_unit).to(u.arcsec)
res3 = (self._adc_nir_resid(adc_opt_resid,
(self.zenith_angle.to_value(u.deg), wave[use].to_value(u.um))) * R_unit).to(u.arcsec)
shifts[use] += res3

if self.meta.get('zenith_angle_error', 0.0) != 0.0:
Expand All@@ -354,11 +346,13 @@ def get_shifts(self, obj: FieldOfView3D):
ad_kwargs[k] = v
else:
ad_kwargs[k] = self.meta[k]
ad = ADShift(**ad_kwargs, cmds=self.cmds)
ad = self._get_ad_shift(ad_kwargs)
ad.zenith_angle = self.zenith_angle
ad_shift = ad._get_shifts_arcsec(obj) # get shift at zenith angle
ad.zenith_angle = ad.zenith_angle + self.meta.get('zenith_angle_error', 0.0) * u.deg # update zenith angle
ad_shift -= ad._get_shifts_arcsec(obj) # subtract shift at (zenith angle + error) to get residual
shifts += ad_shift
ad.zenith_angle = self.zenith_angle

pos_angle_y = field_rotation_pa_y(obj.hdu.header)
par_angle = get_parallactic_angle(self.target, self.location, self.time)
Expand All@@ -368,6 +362,43 @@ def get_shifts(self, obj: FieldOfView3D):
dx = -1 * shifts * np.sin(theta)
return dx, dy

def _get_residual_interpolator(self):
if self._residual_interpolator_cache is None:
Z = list(self.data.colnames)
Z.remove("wavelength")
lam_um = quantity_from_table("wavelength", self.data, "um").value
R = np.array([np.array(self.data[z]).astype(float) for z in Z])
R_unit = u.Unit(self.data.meta.get("shifts_unit", "arcsec"))
Z = np.array(Z).astype(float)
adc_opt_resid = RegularGridInterpolator(
(Z, lam_um), R, method="linear", bounds_error=False,
fill_value=None,
)
self._residual_interpolator_cache = (R_unit, adc_opt_resid)
return self._residual_interpolator_cache

@staticmethod
def _adc_nir_resid(adc_opt_resid, xy):
return adc_opt_resid((xy[0], (xy[1] - 1) / 1.5 * (1.1 - .31) + .31))

@staticmethod
def _adc_ub_resid(adc_opt_resid, xy):
return adc_opt_resid((xy[0], (xy[1] - .3) / .2 * (1.1 - .31) + .31)) / 2

@staticmethod
def _adc_gri_resid(adc_opt_resid, xy):
return adc_opt_resid((xy[0], (xy[1] - .5) / .5 * (1.1 - .31) + .31)) / 2

def _get_ad_shift(self, ad_kwargs):
cache_key = tuple(
(key, str(from_currsys(value, self.cmds)))
for key, value in sorted(ad_kwargs.items())
)
if self._ad_shift_cache is None or cache_key != self._ad_shift_cache_key:
self._ad_shift_cache = ADShift(**ad_kwargs, cmds=self.cmds)
self._ad_shift_cache_key = cache_key
return self._ad_shift_cache


########################### AD utils ###############################
def field_rotation_pa_y(header) -> float:
Expand DownExpand Up@@ -492,4 +523,4 @@ def refractive_index(wavelength: u.Quantity, temp: u.Quantity, pressure: u.Quant

nprop = 1 + (ρa / ρaxs) * (naxs - 1) + (ρw / ρws) * (nws - 1)

return nprop
return nprop
1 change: 1 addition & 0 deletions scopesim/effects/detector_list.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,6 +139,7 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)
params = {
"pixel_scale": "!INST.pixel_scale", # arcsec
"plate_scale": "!INST.plate_scale", # arcsec / mm
"active_detectors": "all",
}
self.meta.update(params)
Expand Down
2 changes: 1 addition & 1 deletion scopesim/effects/electronic/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,7 @@

from .electrons import LinearityCurve, ADConversion, InterPixelCapacitance
from .noise import (Bias, PoorMansHxRGReadoutNoise, BasicReadoutNoise,
ShotNoise, DarkCurrent, PixelResponseNonUniformity)
PixelResponseNonUniformity, ShotNoise, DarkCurrent)
from .exposure import AutoExposure, ExposureIntegration, ExposureOutput
from .pixels import ReferencePixelBorder, BinnedImage, UnequalBinnedImage
from .dmps import DetectorModePropertiesSetter
Expand Down
2 changes: 1 addition & 1 deletion scopesim/effects/electronic/noise.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -215,7 +215,7 @@ def apply_to(self, obj, **kwargs):
return obj

def plot(self, det_id=None):
"""Plot effect."""
"""Plot the cached gain map."""
if not self._gain_maps:
raise RuntimeError("No gain map yet - run a simulation first.")
key = det_id if det_id in self._gain_maps else next(iter(self._gain_maps))
Expand Down
Loading
Loading