Skip to content

Commit 02e840c

Browse files
committed
apply single-line solution with warning
changes to code in wavecal plot to allow for this
1 parent 2e0a0a4 commit 02e840c

2 files changed

Lines changed: 78 additions & 54 deletions

File tree

geminidr/core/primitives_spect.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,6 +2909,7 @@ def determineWavelengthSolution(self, adinputs=None, **params):
29092909
wavecal.update_wcs_with_solution(ext, fit1d, other, config)
29102910
else:
29112911
bad_solutions = []
2912+
single_line_solutions = []
29122913
for ext in ad:
29132914
input_data, fit1d = wavecal.get_automated_fit(
29142915
ext, uiparams, p=self, linelist=linelist, bad_bits=DQ.not_signal,
@@ -2921,6 +2922,8 @@ def determineWavelengthSolution(self, adinputs=None, **params):
29212922
title=f"{ad.filename}:{ext.id}",
29222923
absorption=absorption))
29232924
any_good = True
2925+
if fit1d.image.size == 1:
2926+
single_line_solutions.append(ext.id)
29242927
else: # no line matches so not an acceptable solution
29252928
bad_solutions.append(ext.id)
29262929

@@ -2935,6 +2938,16 @@ def determineWavelengthSolution(self, adinputs=None, **params):
29352938
raise RuntimeError(msg)
29362939
log.warning(msg)
29372940

2941+
if single_line_solutions:
2942+
msg = f"{ad.filename}: a single-line solution has been applied"
2943+
if len(ad) > 1:
2944+
if len(ad) > len(single_line_solutions):
2945+
msg += "in extensions " + ", ".join(str(i) for i in single_line_solutions)
2946+
else:
2947+
msg += " in all extensions"
2948+
log.warning(msg)
2949+
2950+
29382951
ad.update_filename(suffix=sfx, strip=True)
29392952
if figures:
29402953
plot_filename = ad.filename.replace('.fits', '.pdf')

gempy/library/wavecal.py

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ def find_solution(init_models, config, peaks=None, peak_weights=None,
814814

815815
# We allow this continue if we only matched 1 line but only 1 line is
816816
# expected
817-
if len(matched) > 1: # or (len(matched) * nlines_expected == 1 and len(peaks) <= 2):
817+
if len(matched) > 1 or (len(matched) * nlines_expected == 1 and len(peaks) <= 2):
818818
m_init = models.Chebyshev1D(
819819
degree=min(config["order"], (len(matched) + 1) // 2),
820820
domain=domain)
@@ -829,59 +829,69 @@ def find_solution(init_models, config, peaks=None, peak_weights=None,
829829
matched_peaks = peaks[matched]
830830
matched_arc_lines = arc_lines[matches[matched]]
831831
m_final = fit_it(m_init, matched_peaks, matched_arc_lines)
832-
print(repr(m_final))
832+
dw = abs(np.diff(m_final(m_final.domain))[0] / np.diff(m_final.domain)[0])
833833
#for p, l in zip(matched_peaks, matched_arc_lines):
834834
# print(f"{p:.2f} => {l:.2f}")
835835

836-
# We're close to the correct solution, perform a KDFit
837-
m_init = models.Chebyshev1D(m_final.degree, domain=domain)
838-
for p, v in zip(m_final.param_names, m_final.parameters):
839-
setattr(m_init, p, v)
840-
dw = abs(np.diff(m_final(m_final.domain))[0] / np.diff(m_final.domain)[0])
841-
fit_it = matching.KDTreeFitter(sigma=2 * abs(dw), maxsig=5,
842-
k=k, method='Nelder-Mead')
843-
m_final = fit_it(m_init, peaks, arc_lines, in_weights=peak_weights,
844-
ref_weights=arc_weights)
845-
logit(f'{repr(m_final)} {fit_it.statistic}')
846-
847-
# And then recalculate the matches
848-
match_radius = 4 * fwidth * abs(m_final.c1) / len_data # 2*fwidth pixels
849-
try:
850-
matched = matching.match_sources(m_final(peaks), arc_lines,
851-
radius=match_radius)
852-
incoords, outcoords = zip(*[(peaks[i], arc_lines[m])
853-
for i, m in enumerate(matched) if m > -1])
854-
# Probably incoords and outcoords as defined here should go to
855-
# the interactive fitter, but cull to derive the "best" model
856-
fit1d = fit_1D(outcoords, points=incoords, function="chebyshev",
857-
order=min(m_final.degree, (len(incoords) + 1) // 2),
858-
domain=m_final.domain,
859-
niter=config["niter"], sigma_lower=config["lsigma"],
860-
sigma_upper=config["hsigma"])
861-
fit1d.image = np.asarray(outcoords)
862-
except ValueError:
863-
log.warning("Line-matching failed")
864-
continue
865-
nmatched = np.sum(~fit1d.mask)
866-
logit(f"{filename} {repr(fit1d.model)} {nmatched} {fit1d.rms}")
867-
868-
# A posteriori check that the fit is within the bounds
869-
# (LinearLSQFitter does not allow bounds so we can't force
870-
# the model to be bounded).
871-
try:
872-
for p, v in zip(fit1d.model.param_names, fit1d.model.parameters):
873-
bounds = model_bounds.get(p, (-np.inf, np.inf))
874-
new_bounds = (np.asarray(bounds) +
875-
np.array([-0.5, 0.5]) * np.diff(bounds)[0])
876-
assert new_bounds[0] <= v <= new_bounds[1]
877-
except AssertionError:
878-
for p, v in zip(fit1d.model.param_names, fit1d.model.parameters):
879-
bounds = model_bounds.get(p, (-np.inf, np.inf))
880-
new_bounds = (np.asarray(bounds) +
881-
np.array([-0.5, 0.5]) * np.diff(bounds)[0])
882-
print(p, v, new_bounds)
883-
print("*** REJECTED ***")
884-
continue
836+
if len(matched) > 1:
837+
# We're close to the correct solution, perform a KDFit
838+
m_init = models.Chebyshev1D(m_final.degree, domain=domain)
839+
for p, v in zip(m_final.param_names, m_final.parameters):
840+
setattr(m_init, p, v)
841+
fit_it = matching.KDTreeFitter(sigma=2 * abs(dw), maxsig=5,
842+
k=k, method='Nelder-Mead')
843+
m_final = fit_it(m_init, peaks, arc_lines, in_weights=peak_weights,
844+
ref_weights=arc_weights)
845+
logit(f'{repr(m_final)} {fit_it.statistic}')
846+
847+
# And then recalculate the matches
848+
match_radius = 4 * fwidth * abs(m_final.c1) / len_data # 2*fwidth pixels
849+
try:
850+
matched = matching.match_sources(m_final(peaks), arc_lines,
851+
radius=match_radius)
852+
incoords, outcoords = zip(*[(peaks[i], arc_lines[m])
853+
for i, m in enumerate(matched) if m > -1])
854+
# Probably incoords and outcoords as defined here should go to
855+
# the interactive fitter, but cull to derive the "best" model
856+
fit1d = fit_1D(outcoords, points=incoords, function="chebyshev",
857+
order=min(m_final.degree, (len(incoords) + 1) // 2),
858+
domain=m_final.domain,
859+
niter=config["niter"], sigma_lower=config["lsigma"],
860+
sigma_upper=config["hsigma"])
861+
fit1d.image = np.asarray(outcoords)
862+
except ValueError:
863+
log.warning("Line-matching failed")
864+
continue
865+
nmatched = np.sum(~fit1d.mask)
866+
logit(f"{filename} {repr(fit1d.model)} {nmatched} {fit1d.rms}")
867+
868+
# A posteriori check that the fit is within the bounds
869+
# (LinearLSQFitter does not allow bounds so we can't force
870+
# the model to be bounded). We allow the tolerances to be twice
871+
# as large as they were during the piecewise fit, as this seems
872+
# to work well empirically.
873+
try:
874+
for p, v in zip(fit1d.model.param_names, fit1d.model.parameters):
875+
bounds = model_bounds.get(p, (-np.inf, np.inf))
876+
new_bounds = (np.asarray(bounds) +
877+
np.array([-0.5, 0.5]) * np.diff(bounds)[0])
878+
assert new_bounds[0] <= v <= new_bounds[1]
879+
except AssertionError:
880+
for p, v in zip(fit1d.model.param_names, fit1d.model.parameters):
881+
bounds = model_bounds.get(p, (-np.inf, np.inf))
882+
new_bounds = (np.asarray(bounds) +
883+
np.array([-0.5, 0.5]) * np.diff(bounds)[0])
884+
print(p, v, new_bounds)
885+
continue
886+
else:
887+
# Hack a fit1D object
888+
fit1d = fit_1D(np.arange(5), function="chebyshev", order=1,
889+
niter=0)
890+
fit1d._models = m_final
891+
fit1d.image = np.array(matched_arc_lines)
892+
fit1d.points = np.array(matched_peaks)
893+
fit1d.mask = np.array([False], dtype=bool)
894+
nmatched = 1
885895

886896
# Wavelength solution models need to be monotonic. Make that check.
887897
waves = fit1d.evaluate(np.arange(len_data))
@@ -1023,7 +1033,7 @@ def perform_piecewise_fit(model, peaks, arc_lines, pixel_start, kdsigma,
10231033
# automatically removes old (bad) match
10241034
matches[i] = m
10251035
found_new_matches = True
1026-
print(f"Pixel {p} => was {m_init(p):.4f} now {m_this(p):.4f} {arc_lines[m]}")
1036+
#print(f"Pixel {p} => was {m_init(p):.4f} now {m_this(p):.4f} {arc_lines[m]}")
10271037
try:
10281038
p_lo = peaks[matches > -1].min()
10291039
except ValueError:
@@ -1304,14 +1314,15 @@ def create_pdf_plot(input_data, peaks, arc_lines, title="",
13041314
data = input_data["spectrum"]
13051315
spacing = 0.01
13061316
vert_align = "bottom"
1307-
xmin, xmax = input_data["init_models"][0].domain
1317+
init_model = input_data["init_models"][0]
1318+
xmin, xmax = init_model.domain
13081319
pixels = np.arange(xmin, xmax + 1)
13091320
data_max = data.max()
13101321
spacing *= data_max
13111322
fig, ax = plt.subplots()
13121323
ax.plot(pixels, data, 'b-')
13131324
ax.set_ylim(0, data_max * 1.1)
1314-
if len(arc_lines) and np.diff(arc_lines)[0] / np.diff(peaks)[0] < 0:
1325+
if len(arc_lines) and init_model(xmin) > init_model(xmax):
13151326
ax.set_xlim(xmax + 1, xmin - 1)
13161327
else:
13171328
ax.set_xlim(xmin - 1, xmax + 1)

0 commit comments

Comments
 (0)