Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add routine for modern dark scaling #172

Merged
merged 8 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 55 additions & 4 deletions chandra_aca/dark_model.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Routines related to the canonical Chandra ACA dark current model.
Routines related to the Chandra ACA dark current models.

The model is based on smoothed twice-broken power-law fits of
dark current histograms from Jan-2007 though Aug-2017. This analysis
was done entirely with dark current maps scaled to -14 C.
The canonical model for ACA dark current is a based on smoothed
twice-broken power-law fits of dark current histograms from Jan-2007
though Aug-2017. This analysis was done entirely with dark current maps scaled to -14 C.

See: /proj/sot/ska/analysis/dark_current_model/dark_model.ipynb
and other files in that directory.

Alternatively:
http://nbviewer.ipython.org/url/asc.harvard.edu/mta/ASPECT/analysis/dark_current_model/dark_model.ipynb

The dark_temp_scale_img method in this file uses a more recent 2023 approach with per-pixel scaling.

https://nbviewer.org/url/asc.harvard.edu/mta/ASPECT/analysis/dark_current_model/dark_model-2023.ipynb

"""

import warnings

import numpy as np
import numpy.typing as npt
from Chandra.Time import DateTime

# Define a common fixed binning of dark current distribution
Expand Down Expand Up @@ -340,3 +346,48 @@ def synthetic_dark_image(date, t_ccd_ref=None):
dark *= dark_temp_scale(-14, t_ccd_ref)

return dark


def dark_temp_scale_img(img: float | npt.ArrayLike, t_ccd: float, t_ccd_ref: float):
"""
Get dark current taken at ``t_ccd`` scaled to reference temperature ``t_ccd_ref``

This scales the dark current based on an exponential scaling factor that depends on
the dark current value of each pixel. This is a more accurate way to scale dark
current images than using a global scaling factor as in dark_temp_scale().

See the reference notebook at:
https://nbviewer.org/url/asc.harvard.edu/mta/ASPECT/analysis/dark_current_model/dark_model-2023.ipynb

Parameters
----------
img : float, ArrayLike
Dark current image or value in e-/sec
t_ccd : float
CCD temperature (degC) of the input image
t_ccd_ref : float
CCD temperature (degC) of the scaled output image

Returns
-------
float, np.ndarray
Dark current image scaled to ``t_ccd_ref``
"""

# Confirm t_ccd and t_ref are just floats
t_ccd = float(t_ccd)
t_ccd_ref = float(t_ccd_ref)

# this comes from the simple fit to DC averages, with fixed T_CCD=265.15 (-8.0 C)
a, b, c, d, e = [
-4.88802057e00,
-1.66791619e-04,
-2.22596103e-01,
-2.45720364e-03,
1.90718453e-01,
]
t = 265.15
y = np.log(np.clip(img, 20, 1e4)) - e * t
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You removed the np.isnan check. Right now I do not know where that might affect the result, but I can guarantee that if I put it there it is because at some point I cared (maybe a warning at some point).

Maybe something to just keep in mind in case weird things happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding nans, it looks like the routine isn't breaking on them:

In [15]: img = [np.nan, -5, 0, 100, 1000, 1000]

In [16]: dark_temp_scale_img(img, -10, 5)
Out[16]: 
array([          nan,  -21.16578998,    0.        ,  504.20852555,
       4645.94826402, 4645.94826402])

I think the output for negative numbers is probably unhelpful but it looks like the nans stay nans.

scale = a + b * t + c * y + d * y**2

return img * np.exp(scale * (t_ccd_ref - t_ccd))
Binary file added chandra_aca/tests/data/dark_scaled_img.pkl
Binary file not shown.
39 changes: 39 additions & 0 deletions chandra_aca/tests/test_dark_model.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import os
import pickle
from pathlib import Path

import numpy as np
import pytest
from mica.archive.aca_dark import get_dark_cal_props
from mica.common import MICA_ARCHIVE

from chandra_aca.dark_model import dark_temp_scale_img

from ..dark_model import dark_temp_scale, get_warm_fracs, synthetic_dark_image

HAS_MICA = os.path.exists(MICA_ARCHIVE)
Expand Down Expand Up @@ -72,3 +77,37 @@ def test_get_warm_fracs_2017185():
exp = np.array([207845, 79207, 635, 86, 26])

assert np.allclose(wps, exp, rtol=0.001, atol=2)


@pytest.mark.parametrize(
"img, t_ccd, t_ref, expected",
[
(np.array([100, 1000, 500]), -10.0, -5.0, np.array([171.47, 1668.62, 852.79])),
(np.array([100, 1000, 500]), -10.0, -15.0, np.array([58.31, 599.29, 293.15])),
([[100, 1000], [500, 600]], -15.0, -15.0, [[100, 1000], [500, 600]]),
([200, 2000, 600], -6.0, 2.0, np.array([478.16, 4299.02, 1399.40])),
(2000, -6, 2, 4299.02),
([2000, np.nan], -6, 2, [4299.02, np.nan]),
(np.nan, -6, 2, np.nan),
],
)
def test_dark_temp_scale_img(img, t_ccd, t_ref, expected):
scaled_img = dark_temp_scale_img(img, t_ccd, t_ref)
assert np.allclose(scaled_img, expected, atol=0.1, rtol=0, equal_nan=True)
if np.shape(img):
assert isinstance(scaled_img, np.ndarray)
else:
assert isinstance(scaled_img, float)


@pytest.mark.skipif("not HAS_MICA")
def test_dark_temp_scale_img_real_dc():
test_data = {}
with open((Path(__file__).parent / "data" / "dark_scaled_img.pkl"), "rb") as f:
test_data.update(pickle.load(f))

dc = get_dark_cal_props("2024:001", include_image=True)
# just use 100 square pixels instead of 1024x1024
img = dc["image"][100:200, 100:200]
scaled_img = dark_temp_scale_img(img, dc["t_ccd"], -3.0)
assert np.allclose(scaled_img, test_data["scaled_img"], atol=0.1, rtol=0)
Loading