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

Refactor light-leak function and remove light-leak files from repository #180

Merged
merged 7 commits into from
Sep 22, 2023
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
9,057 changes: 0 additions & 9,057 deletions xrtpy/util/data/leak_fits/term_p1cp_20140527_204601.fits

This file was deleted.

10,477 changes: 0 additions & 10,477 deletions xrtpy/util/data/leak_fits/term_p1tp_20140515_182503.fits

This file was deleted.

9,796 changes: 0 additions & 9,796 deletions xrtpy/util/data/leak_fits/term_p2am_20150718_160913.fits

This file was deleted.

12,016 changes: 0 additions & 12,016 deletions xrtpy/util/data/leak_fits/term_p2ap_20150620_172818.fits

This file was deleted.

18,205 changes: 0 additions & 18,205 deletions xrtpy/util/data/leak_fits/term_p2cp_20150620_190645.fits

This file was deleted.

9,194 changes: 0 additions & 9,194 deletions xrtpy/util/data/leak_fits/term_p2tp_20150718_160921.fits

This file was deleted.

14,218 changes: 0 additions & 14,218 deletions xrtpy/util/data/leak_fits/term_p3am_20170808_180126.fits

This file was deleted.

18,413 changes: 0 additions & 18,413 deletions xrtpy/util/data/leak_fits/term_p3ap_20170809_183821.fits

This file was deleted.

10,416 changes: 0 additions & 10,416 deletions xrtpy/util/data/leak_fits/term_p4am_20180712_171919.fits

This file was deleted.

12,037 changes: 0 additions & 12,037 deletions xrtpy/util/data/leak_fits/term_p4ap_20180712_171928.fits

This file was deleted.

9,911 changes: 0 additions & 9,911 deletions xrtpy/util/data/leak_fits/term_p5am_20220709_180901.fits

This file was deleted.

6,797 changes: 0 additions & 6,797 deletions xrtpy/util/data/leak_fits/term_p5ap_20220709_180910.fits

This file was deleted.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion xrtpy/util/tests/test_xrt_remove_lightleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ def test_lightleak(idlfile, compfile, allclose):

ll_removed_map_xrtpy = xrt_remove_lightleak(input_map)

assert allclose(ll_removed_map_xrtpy.data, IDL_map.data, atol=1e-5)
if input_map.data.shape == (2048, 2048):
# Because of rebinning for full resolution images, the match is worse
# between IDL created images and XRTpy ones. IDL's method of rebinning
# is different from that used by sunpy.
assert allclose(ll_removed_map_xrtpy.data, IDL_map.data, atol=0.75)
else:
assert allclose(ll_removed_map_xrtpy.data, IDL_map.data, atol=1e-5)
345 changes: 163 additions & 182 deletions xrtpy/util/xrt_remove_lightleak.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,155 @@
"""
Functionality for removing the visible light leak from XRT composite image data.
"""
__all__ = ["xrt_remove_lightleak"]

import numpy as np
import astropy.time
import astropy.units as u
import warnings

from datetime import datetime
from pathlib import Path
from sunpy.data import manager
from sunpy.map import Map
from sunpy.time import parse_time
from urllib.parse import urljoin

from xrtpy.util import _SSW_MIRRORS

__all__ = ["xrt_remove_lightleak"]

LL_FILE_HASHES = {
"term_p1cp_20140527_204601.fits": "bdb924a6ae62292980b266cec0fb96c3626d921efe8148a13b26f964513ea533",
"term_p1tp_20140515_182503.fits": "0f5211633069cfb6a2d9d95f42d42bf52c4e16e1c47b89d32c567b430cd794b0",
"term_p2am_20150718_160913.fits": "b2ba35e7b59d6785b5290900405b82814ebe3ca25c321d55d3853b452844df1f",
"term_p2ap_20150620_172818.fits": "f08c96b86a5c2ed6e980d4a565a536476fe941b48877d2b22709b9694c2ce1c7",
"term_p2cp_20150620_190645.fits": "0e7f72da2d408894980b8a14ead08e0fcd8706cb94c4aa4e256c60576c0c7678",
"term_p2tp_20150718_160921.fits": "9fae8a831c52ecbe3d53d7e4d25caf78702a1aa5a117693d1bf75f0f200b30e6",
"term_p3am_20170808_180126.fits": "ab8a92f728bfa7f68837c349923247a744ff5f86f2d996efc2c54b833455e3f3",
"term_p3ap_20170809_183821.fits": "069d54e292d070322da0095efb903e79a1605371996a791e16e595420f557734",
"term_p4am_20180712_171919.fits": "fd73856d8ea6eee83285ef91f0feb25f8568e6d0b88f77a2c35f4381c888abb3",
"term_p4ap_20180712_171928.fits": "d57a92975e708999fabe22dbd4d491a9a10d9491ee4068a62c73ed4555f7e5dd",
"term_p5am_20220709_180901.fits": "a052bc352cddfe2e7da69f90de83dd8549a457b8ef52acc18932ad41e255f410",
"term_p5ap_20220709_180910.fits": "ab078b4d9573c4b737e7fb41311f824eb540c44c1fd8e3a588cd4e6e0ac2cb39",
}


def _get_stray_light_phase(date_obs):
"""
Get the stray light phase number.

The stray light phase is computed according to the date as follows:

phase 0 : 23-Oct-2006 10:00
phase 1 : 9-May-2012 12:00
phase 2 : 14-Jun-2015 12:30
phase 3 : 27-May-2017 11:00
phase 4 : 29-May-2018 00:00
phase 5 : 8-Jun-2022 12:40
phase 6 : 5-May-2023 4:39

Parameters:
-----------
obs_date : Any parse-time compatible format
Observation date. This can be specified in any format understood by
`sunpy.time.parse_time`.

Returns:
--------
phase : `int`
The phase of the light leak
"""
phase_periods = astropy.time.Time(
[
"2012-05-09 12:00",
"2015-06-14 12:30",
"2017-05-27 11:00",
"2018-05-29 00:00",
"2022-06-08 12:40",
"2023-05-05 04:39",
],
format="iso",
scale="utc",
)

date_obs = parse_time(date_obs)
if date_obs >= phase_periods[5]:
phase = 6
elif date_obs >= phase_periods[4]:
phase = 5
elif date_obs >= phase_periods[3]:
phase = 4
elif date_obs >= phase_periods[2]:
phase = 3
elif date_obs >= phase_periods[1]:
phase = 2
elif date_obs >= phase_periods[0]:
phase = 1
else:
phase = 0

if phase == 6:
warnings.warn(
"light leak images for this period are not yet"
" available. Defaulting to previous phase."
)
phase = 5

return phase


def _select_lightleak_file(filter_wheel_1, filter_wheel_2, date):
phase = _get_stray_light_phase(date)
file_dict = {
("open", "al mesh"): {
2: "term_p2am_20150718_160913.fits",
3: "term_p3am_20170808_180126.fits",
4: "term_p4am_20180712_171919.fits",
5: "term_p5am_20220709_180901.fits",
},
("al poly", "open"): {
2: "term_p2ap_20150620_172818.fits",
3: "term_p3ap_20170809_183821.fits",
4: "term_p4ap_20180712_171928.fits",
5: "term_p5ap_20220709_180910.fits",
},
("c poly", "open"): {
2: "term_p2cp_20150620_190645.fits",
},
("open", "ti poly"): {
1: "term_p1tp_20140515_182503.fits",
2: "term_p2tp_20150718_160921.fits",
},
}
fw_tuple = (filter_wheel_1.lower(), filter_wheel_2.lower())
if fw_tuple not in file_dict:
raise ValueError(
f"No leak image available for {filter_wheel_1}/{filter_wheel_2}."
)
if phase not in file_dict[fw_tuple]:
raise ValueError(
f"No leak image available for {date} and {filter_wheel_1}/{filter_wheel_2}"
)

return file_dict[fw_tuple][phase]


def xrt_remove_lightleak(in_map, kfact=1.0, leak_image=None, verbose=False):
def xrt_remove_lightleak(in_map, scale=1.0, leak_map=None):
r"""
Subtract light leak (visible stray light) image from XRT synoptic
composite images.
Subtract visible stray light image from XRT synoptic composite images.

Parameters:
-----------
in_map : ~sunpy.map.sources.hinode.XRTMap
|Map| for the synoptic composite image which the visible stray light
will be subtracted from

kfact : float, default: 1.0
k-factor to apply when subtracting the light leak image:
``out_data = in_data - k * [leak_img]``


leak_image : float array, dimensions 1024x1024, optional
scale : float, default: 1.0
Scaling factor to apply when subtracting the light leak image:
``out_data = in_data - scale * leak_img``
leak_map : `~sunpy.map.Map`, optional
A leak image to subtract, if a non-default image is desired.
It's assumed that the image is 1024x1024, prepped and exposure
normalized.

verbose : `bool`, default: `False`
If `True`, print out extra messages.

Returns:
--------
out_map : ~sunpy.map.sources.hinode.XRTMap
out_map : `~sunpy.map.sources.hinode.XRTMap`
|Map| of input image with the light leak removed. The metadata HISTORY
is also updated to reflect the fact that the light leak was removed.

Expand Down Expand Up @@ -88,176 +200,45 @@ def xrt_remove_lightleak(in_map, kfact=1.0, leak_image=None, verbose=False):
k-factor is obtained with the function :file:`GET_SLCORFACT_RAW.PRO`, and the leak
image subtraction has been already performed only for the Ti_poly SCIA
images at the phase 1 (as of Feb-2022).

"""
if "Light leak subtraction: DONE" in in_map.meta["HISTORY"]:
warnings.warn("HISTORY indicates light leak subtraction already done on image.")

# Check to see if the lightleak has already been subtracted
history = in_map.meta["HISTORY"]
if "Light leak subtraction: DONE" in history:
raise ValueError(
"HISTORY indicates light leak subtraction already done on image."
if leak_map is None:
leak_filename = _select_lightleak_file(
*in_map.measurement.split("-"), in_map.date
)
# ********* select leak image from the archive *********
if leak_image is None:
dir_leak = Path(__file__).parent.absolute() / "data" / "leak_fits"
filt1 = in_map.meta["EC_FW1"]
filt2 = in_map.meta["EC_FW2"]
fpair = str(filt1) + str(filt2)
sfilt1 = in_map.meta["EC_FW1_"]
sfilt2 = in_map.meta["EC_FW2_"]
sfpair = f"{sfilt1}/{sfilt2}"
sl_phase = check_sl_phase(in_map.meta["date_obs"])
if sl_phase == 6:
warnings.warn(
"light leak images for this period are not yet"
" available. Defaulting to previous phase."
)
sl_phase = 5

if fpair == "01": # Open/Al_mesh
sl_phase_dict = {
2: "term_p2am_20150718_160913.fits",
3: "term_p3am_20170808_180126.fits",
4: "term_p4am_20180712_171919.fits",
5: "term_p5am_20220709_180901.fits",
}
elif fpair == "10": # Al_poly/Open
sl_phase_dict = {
2: "term_p2ap_20150620_172818.fits",
3: "term_p3ap_20170809_183821.fits",
4: "term_p4ap_20180712_171928.fits",
5: "term_p5ap_20220709_180910.fits",
}
elif fpair == "20": # C_poly/Open
sl_phase_dict = {2: "term_p2cp_20150620_190645.fits"}
elif fpair == "02": # Open/Ti_poly
sl_phase_dict = {
1: "term_p1tp_20140515_182503.fits",
2: "term_p2tp_20150718_160921.fits",
}
else:
sl_phase_dict = {}
print(f"No leak image available for this filter pair {sfpair}.")
if verbose:
info = (
f"{in_map.meta['date_obs']} {in_map.measurement} "
+ f"{in_map.meta['naxis1']}x{in_map.meta['naxis2']}"
)
print(info)
print("Output data are identical with the input")
return in_map

try:
leak_fn = sl_phase_dict[sl_phase]
except KeyError:
print(f"No leak image available for phase {sl_phase}")
if verbose:
print(f"Filter pair used: {sfpair}")
print("Output data are identical with the input")
return in_map

leak_map = Map(dir_leak / leak_fn)

leak_image = leak_map.data
if in_map.meta["naxis1"] == 2048:
# assumes leak_image is 1024 x 1024 (I think)
leak_image2 = rebin(leak_image, (2048, 2048)) * 0.25 * kfact
else:
leak_image2 = leak_image * kfact

out_image = in_map.data - leak_image2

hist_entry = f"Light leak subtraction: DONE with {leak_fn} and kfactor:{kfact}"
out_meta = in_map.meta
out_meta["history"] += f"\n{hist_entry}"

if verbose:
print(f"appended to the history : {hist_entry}")

return Map((out_image, out_meta))


def check_sl_phase(date_obs):
"""
Get the stray light phase number

Parameters:
-----------
obs_date : `str`
Observation date string formatted in ISO format: YYYY-MM-DDTHH:mm
where YYYY is the year, MM is the month number (zero-padded), DD is
the day of the month (zero-padded), HH is the hour and mm is the
minute (note T separator between date and time; may also include
seconds). This is the standard format for the value of date_obs in the
FITS headers of XRT images.

Returns:
--------
phase no. : `int`
The phase of the light leak as follows:

phase 0 : 23-Oct-2006 10:00
phase 1 : 9-May-2012 12:00
phase 2 : 14-Jun-2015 12:30
phase 3 : 27-May-2017 11:00
phase 4 : 29-May-2018 00:00
phase 5 : 8-Jun-2022 12:40
phase 6 : 5-May-2023 4:39
"""

time_p1 = datetime.strptime("09-May-2012 12:00", "%d-%b-%Y %H:%M")
time_p2 = datetime.strptime("14-Jun-2015 12:30", "%d-%b-%Y %H:%M")
time_p3 = datetime.strptime("27-May-2017 11:00", "%d-%b-%Y %H:%M")
time_p4 = datetime.strptime("29-May-2018 00:00", "%d-%b-%Y %H:%M")
time_p5 = datetime.strptime("08-Jun-2022 12:40", "%d-%b-%Y %H:%M")
time_p6 = datetime.strptime("05-May-2023 04:39", "%d-%b-%Y %H:%M")
# NOTE: This function is being defined inline because the filename is only known once
# the date and filter wheel combination are known.
@manager.require(
"ll_file",
[
urljoin(mirror, f"hinode/xrt/idl/util/leak_fits/{leak_filename}")
for mirror in _SSW_MIRRORS
],
LL_FILE_HASHES[leak_filename],
)
def get_ll_file():
return manager.get("ll_file")

intime = datetime.fromisoformat(date_obs)
if intime >= time_p6:
phase = 6
elif intime >= time_p5:
phase = 5
elif intime >= time_p4:
phase = 4
elif intime >= time_p3:
phase = 3
elif intime >= time_p2:
phase = 2
elif intime >= time_p1:
phase = 1
leak_map = Map(get_ll_file())
else:
phase = 0

return phase


def rebin(image, newshape):
"""
Rebin an array to a new shape. Copied from:
https://scipy-cookbook.readthedocs.io/items/Rebinning.html
Should behave similarly to IDL rebin

Parameters:
-----------
image : 2D numpy array
image to be rebinned
# NOTE: This is to fill in the HISTORY in the resulting file
leak_filename = "unknown"

newshape : tuple
dimensions of rebinned image. Each dimension should be an integer
multiple of the corresponding dimension in the input image, though
they need not be the same multiple
# case of 2048 x 2048 input image - for full resolution image, the light leak image flux
# in each pixel must be split into four pixels each, thus the factor of 0.25 below
if in_map.dimensions[0] == 2 * leak_map.dimensions[0]:
leak_map = leak_map.resample(u.Quantity(in_map.dimensions))
leak_map *= 0.25
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add a comment here about why this is multiplied by 0.25?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, will do. Also need to restrict the condition to cases in which the in_map.dimensions are twice the leak_map.dimensions.
I was also thinking that we should probably issue a warning if the image has the wrong dimensions generally. The use of the routine as written is supposed to be for composite images that are either 1024 x 1024 or 2048 x 2048. It won't work for images with different sizes.

leak_map *= scale

Returns:
--------
newimage : 2D numpy array
rebinned image
"""
out_map = in_map - leak_map.quantity

assert len(image.shape) == len(newshape)
hist_entry = (
f"Light leak subtraction: DONE with {leak_filename} and kfactor:{scale}"
)
out_map.meta["history"] += f"\n{hist_entry}"

slices = [
slice(0, old, float(old) / new) for old, new in zip(image.shape, newshape)
]
coordinates = np.mgrid[slices]
indices = coordinates.astype("i")
return image[tuple(indices)]
return out_map
Loading