Skip to content

Commit

Permalink
Merge branch 'enh/createNewAperture'
Browse files Browse the repository at this point in the history
  • Loading branch information
DBerke committed Nov 4, 2022
2 parents 27d9c8e + aa20e64 commit 9cbb9f2
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 0 deletions.
22 changes: 22 additions & 0 deletions geminidr/core/parameters_spect.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ def setDefaults(self):
del self.grow


class createNewApertureConfig(config.Config):
aperture = config.Field("Base aperture to offset from", int, None, optional=False)
shift = config.Field("Shift (in pixels) to new aperture", float, None, optional=False)
aper_upper = config.RangeField("Offset to new upper edge", float, None,
optional=True, min=0., inclusiveMin=False)
aper_lower = config.RangeField("Offset to new lower edge", float, None,
optional=True, max=0., inclusiveMax=False)
suffix = config.Field("Filename suffix", str, "_newApertureCreated", optional=True)

def validate(self):
config.Config.validate(self)
if (self.aper_lower and self.aper_upper) or\
(not self.aper_lower and not self.aper_upper):
pass
else:
raise ValueError("Both aper_lower and aper_upper must either be "
"specified, or left as None.")


class determineDistortionConfig(config.Config):
suffix = config.Field("Filename suffix", str, "_distortionDetermined", optional=True)
spatial_order = config.RangeField("Fitting order in spatial direction", int, 3, min=1)
Expand All @@ -90,6 +109,7 @@ class determineDistortionConfig(config.Config):
max_missed = config.RangeField("Maximum number of steps to miss before a line is lost", int, 5, min=0)
debug = config.Field("Display line traces on image display?", bool, False)


class determineSlitEdgesConfig(config.Config):
suffix = config.Field("Filename suffix", str, "_slitEdgesDetermined", optional=True)
edges1 = config.ListField("List of left edges of illuminated region(s)",
Expand All @@ -101,6 +121,7 @@ class determineSlitEdgesConfig(config.Config):
debug = config.Field("Plot fits of edges and print extra information?",
bool, False)


class determineWavelengthSolutionConfig(config.core_1Dfitting_config):
suffix = config.Field("Filename suffix", str, "_wavelengthSolutionDetermined", optional=True)
center = config.RangeField("Central row/column to extract", int, None, min=1, optional=True)
Expand Down Expand Up @@ -132,6 +153,7 @@ def setDefaults(self):
del self.grow
self.niter = 3


class distortionCorrectConfig(parameters_generic.calRequirementConfig):
suffix = config.Field("Filename suffix", str, "_distortionCorrected", optional=True)
order = config.RangeField("Interpolation order", int, 3, min=0, max=5, inclusiveMax=True)
Expand Down
104 changes: 104 additions & 0 deletions geminidr/core/primitives_spect.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,110 @@ def _get_fit1d_input_data(ext, exptime, spec_table):

return adinputs

def createNewAperture(self, adinputs=None, **params):
"""
Create a new aperture, as an offset from another (given) aperture.
Parameters
----------
adinputs : list of :class:`~astrodata.AstroData`
A list of spectra with an APERTURE table.
aperture : int
Aperture number upon which to base new aperture.
shift : float
Shift (in pixels) to new aperture.
aper_lower : float
Distance in pixels from center to lower edge of new aperture.
aper_upper : float
Distance in pixels from center to upper edge of new aperture.
suffix : str
Suffix to be added to output files.
Returns
-------
list of :class:`~astrodata.AstroData`
The same input list is used as output but each object now has a new
aperture in its APERTURE table, created as an offset from an
existing aperture.
"""
log = self.log
log.debug(gt.log_message("primitive", self.myself(), "starting"))
timestamp_key = self.timestamp_keys[self.myself()]

aperture = params["aperture"]
shift = params["shift"]
aper_lower = params["aper_lower"]
aper_upper = params["aper_upper"]
sfx = params['suffix']

# First check that the given reference aperture is available in each
# extension of all AstroData objects, no-op if not. Report all cases
# where the reference aperture is missing.
ok = True
for ad in adinputs:
for ext in ad:
if aperture not in list(ext.APERTURE['number']):
log.warning(f"Aperture number {aperture} not found in "
f"extension {ext.id}.")
ok = False
if not ok:
log.warning(f"No new apertures will be created by {self.myself()}")
return adinputs

for ad in adinputs:
for ext in ad:
spataxis = ext.dispersion_axis() - 1 # Python sense
too_low, too_high = (("left", "right") if spataxis == 1
else ("bottom", "top"))

# We know this exists from the check above.
existing_apnums = list(ext.APERTURE['number'])
apnum = existing_apnums.index(aperture)

# Copy the appropriate row.
new_row = deepcopy(ext.APERTURE[apnum])
new_row['c0'] += shift

apmodel = am.table_to_model(new_row)
# Expect domain to be equal to the number of spectral pixels
try:
center_pixels = apmodel(np.arange(*apmodel.domain))
except TypeError: # something wrong (e.g., domain is "None")
center_pixels = apmodel(np.arange(ext.shape[1-spataxis]))
_min, _max = min(center_pixels), max(center_pixels)

# Set user-given values for upper and lower aperture edges.
# Validation should ensure they either both exist or are None.
if aper_lower is not None and aper_upper is not None:
new_row['aper_lower'] = aper_lower
new_row['aper_upper'] = aper_upper
aplo, aphi = new_row['aper_lower', 'aper_upper']

new_apnum = min(set(range(1, max(existing_apnums) + 2)) -
set(existing_apnums))
log.stdinfo(f"Adding new aperture {apnum} to {ad.filename} "
f"extension {ext.id}.")
new_row['number'] = new_apnum
ext.APERTURE.add_row(new_row)

# Print warning if new aperture is off the array
if _max + aphi < 0:
log.warning(f"New aperture is entirely off {too_low} of image.")
elif _min + aplo < 0:
log.warning(f"New aperture is partially off {too_low} of image.")
if _min + aplo > ext.data.shape[spataxis]:
log.warning(f"New aperture is entirely off {too_high} of image.")
elif _max + aphi > ext.data.shape[spataxis]:
log.warning(f"New aperture is partially off {too_high} of image.")

# Timestamp and update the filename
gt.mark_history(ad, primname=self.myself(), keyword=timestamp_key)
ad.update_filename(suffix=sfx, strip=True)

return adinputs


def determineDistortion(self, adinputs=None, **params):
"""
Maps the distortion on a detector by tracing lines perpendicular to the
Expand Down
62 changes: 62 additions & 0 deletions geminidr/core/tests/test_spect.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

import astrodata, gemini_instruments
from gempy.library import astromodels as am
from gempy.library.config.config import FieldValidationError
from geminidr.core import primitives_spect
from geminidr.f2.primitives_f2_longslit import F2Longslit
from geminidr.gnirs.primitives_gnirs_longslit import GNIRSLongslit
Expand Down Expand Up @@ -106,6 +107,67 @@ def test_find_apertures():
_p.findApertures()


@pytest.mark.preprocessed_data
def test_create_new_aperture(path_to_inputs):
ad = astrodata.open(os.path.join(path_to_inputs, 'S20060826S0305_2D.fits'))
p = GNIRSLongslit([ad])

# Test creating a new aperture
p.createNewAperture(aperture=1, shift=100)
assert ad[0].APERTURE[1]['c0'] == pytest.approx(471.745)
assert ad[0].APERTURE[1]['aper_lower'] == pytest.approx(-21.13415)
assert ad[0].APERTURE[1]['aper_upper'] == pytest.approx(23.07667)

# Create another aperature and test aper_lower & aper_upper parameters
p.createNewAperture(aperture=1, shift=-100, aper_lower=-10, aper_upper=10)
assert ad[0].APERTURE[2]['c0'] == pytest.approx(271.745)
assert ad[0].APERTURE[2]['aper_lower'] == pytest.approx(-10)
assert ad[0].APERTURE[2]['aper_upper'] == pytest.approx(10)

# Delete aperture in the midde, test that aperture number increments
del ad[0].APERTURE[1]
p.createNewAperture(aperture=1, shift=100)
assert ad[0].APERTURE[2]['c0'] == pytest.approx(471.745)


@pytest.mark.preprocessed_data
def test_create_new_aperture_warnings_and_errors(path_to_inputs, caplog):
ad = astrodata.open(os.path.join(path_to_inputs, 'S20060826S0305_2D.fits'))
p = GNIRSLongslit([ad])

# Check that only passing one 'aper' parameter raises a ValueError
with pytest.raises(ValueError):
p.createNewAperture(aperture=1, shift=100, aper_lower=10, aper_upper=None)
p.createNewAperture(aperture=1, shift=100, aper_lower=None, aper_upper=10)

# Check that aper_upper & aper_lower limits are respected
with pytest.raises(FieldValidationError):
p.createNewAperture(aperture=1, shift=10, aper_lower=-2, aper_upper=-1)
with pytest.raises(FieldValidationError):
p.createNewAperture(aperture=1, shift=10, aper_lower=1, aper_upper=2)
with pytest.raises(FieldValidationError):
p.createNewAperture(aperture=1, shift=100, aper_lower=5, aper_upper=10)
with pytest.raises(FieldValidationError):
p.createNewAperture(aperture=1, shift=100, aper_lower=-10, aper_upper=-5)

# Check that appropriate warnings are generated when creating apertures
# with either the 'center' or an edge off the end of the array. Do them in
# this order since the third and fourth also generate the warnings of the
# first two.
p.createNewAperture(aperture=1, shift=600, aper_lower=-5, aper_upper=400)
assert any('New aperture is partially off right of image.' in record.message
for record in caplog.records)
p.createNewAperture(aperture=1, shift=-300, aper_lower=-500, aper_upper=5)
assert any('New aperture is partially off left of image.' in record.message
for record in caplog.records)
p.createNewAperture(aperture=1, shift=1000)
assert any('New aperture is entirely off right of image.' in record.message
for record in caplog.records)
p.createNewAperture(aperture=1, shift=-1000)
assert any('New aperture is entirely off left of image.' in record.message
for record in caplog.records)


@pytest.mark.parametrize('in_vacuo', (False, True, None))
def test_get_spectrophotometry(path_to_outputs, in_vacuo):

Expand Down
1 change: 1 addition & 0 deletions geminidr/gemini/lookups/timestamp_keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"calculateSensitivity": "SENSFUNC",
"combineNodAndShuffleBeams": "NSCOMB",
"correctBackgroundToReference": "CORRBG",
"createNewAperture": "CRTNWAPT",
"makeSlitIllum": "MAKESILL",
"cutFootprints": "CUTSFP",
"darkCorrect": "DARKCORR",
Expand Down

0 comments on commit 9cbb9f2

Please sign in to comment.